From 22bebbb342402cb66ff1a99efff351f0240594d2 Mon Sep 17 00:00:00 2001 From: Tuan Pham Date: Thu, 8 Sep 2022 09:58:02 -0500 Subject: [PATCH 1/6] add iOS library latest-legacy toggle switch --- src/components/Menu/VersionSwitcher/index.tsx | 70 +- src/components/Menu/index.tsx | 14 +- src/directory/directory.js | 474 ++++++++++++- .../analytics/android/escapehatch.mdx | 30 + .../android/getting-started/10_preReq.mdx | 3 + .../getting-started/12_amplifyConfig.mdx | 1 + .../android/getting-started/20_installLib.mdx | 13 + .../getting-started/30_initAnalytics.mdx | 95 +++ .../android/getting-started/40_record.mdx | 49 ++ .../analytics/android/identifyuser.mdx | 115 ++++ .../lib-legacy/analytics/android/record.mdx | 222 +++++++ .../lib-legacy/analytics/autotrack.mdx | 10 + .../analytics/existing-resources.mdx | 27 + .../flutter/getting-started/10_preReq.mdx | 5 + .../getting-started/12_amplifyConfig.mdx | 9 + .../flutter/getting-started/20_installLib.mdx | 18 + .../getting-started/30_initAnalytics.mdx | 72 ++ .../flutter/getting-started/40_record.mdx | 16 + .../analytics/flutter/identifyuser.mdx | 37 ++ .../lib-legacy/analytics/flutter/record.mdx | 89 +++ .../lib-legacy/analytics/ios/escapehatch.mdx | 38 ++ .../ios/getting-started/10_preReq.mdx | 3 + .../ios/getting-started/12_amplifyConfig.mdx | 1 + .../ios/getting-started/20_installLib.mdx | 40 ++ .../ios/getting-started/30_initAnalytics.mdx | 96 +++ .../ios/getting-started/40_record.mdx | 14 + .../lib-legacy/analytics/ios/identifyuser.mdx | 28 + .../lib-legacy/analytics/ios/record.mdx | 78 +++ .../lib-legacy/analytics/js/autotrack.mdx | 157 +++++ .../analytics/js/getting-started.mdx | 291 ++++++++ .../lib-legacy/analytics/js/personalize.mdx | 83 +++ .../lib-legacy/analytics/js/record.mdx | 55 ++ .../lib-legacy/analytics/js/storing.mdx | 67 ++ .../lib-legacy/analytics/js/streaming.mdx | 71 ++ .../native_common/getting-started/common.mdx | 109 +++ src/fragments/lib-legacy/android-sdk.mdx | 23 + src/fragments/lib-legacy/android.mdx | 15 + .../10_fetchAuthSession.mdx | 77 +++ .../lib-legacy/auth/android/common_prereq.mdx | 1 + .../android/delete_user/10_delete_user.mdx | 45 ++ .../device_features/10_rememberDevice.mdx | 45 ++ .../device_features/20_forgetDevice.mdx | 45 ++ .../device_features/30_fetchDevice.mdx | 51 ++ .../escapehatch/10_awsmobileclient_escape.mdx | 132 ++++ .../android/getting_started/10_preReq.mdx | 2 + .../getting_started/12_amplifyConfig.mdx | 1 + .../android/getting_started/20_installLib.mdx | 7 + .../android/getting_started/30_initAuth.mdx | 40 ++ .../getting_started/40_fetchSession.mdx | 47 ++ .../android/hub_events/10_listen_events.mdx | 120 ++++ .../password_management/10_reset_password.mdx | 46 ++ .../20_confirm_reset_password.mdx | 46 ++ .../30_change_password.mdx | 47 ++ .../auth/android/signin/10_signUp.mdx | 57 ++ .../auth/android/signin/20_confirmSignUp.mdx | 59 ++ .../auth/android/signin/30_signIn.mdx | 57 ++ .../android/signin/40_multi_factor_signup.mdx | 73 ++ .../signin/50_multi_factor_confirm_signin.mdx | 46 ++ .../android/signin_web_ui/10_cli_setup.mdx | 24 + .../20_platform_specific_setup.mdx | 203 ++++++ .../auth/android/signin_web_ui/30_signin.mdx | 49 ++ .../auth/android/signout/10_local_signout.mdx | 45 ++ .../android/signout/20_global_signout.mdx | 52 ++ .../social_signin_web_ui/10_cli_setup.mdx | 28 + .../social_signin_web_ui/20_signin.mdx | 47 ++ .../user_attributes/10_fetch_attributes.mdx | 47 ++ .../20_update_user_attribute.mdx | 105 +++ .../user_attributes/30_confirm_attribute.mdx | 45 ++ .../user_attributes/40_resend_code.mdx | 47 ++ .../auth/common/device_features/common.mdx | 99 +++ .../lib-legacy/auth/common/overview.mdx | 30 + .../lib-legacy/auth/common/sms/add_mfa.mdx | 23 + .../auth/common/sms/add_username.mdx | 17 + .../auth/common/sms/add_verification.mdx | 21 + .../lib-legacy/auth/common/sms/flows.mdx | 159 +++++ .../lib-legacy/auth/common/sms/update_mfa.mdx | 25 + .../auth/common/sms/update_username.mdx | 1 + .../auth/common/sms/update_verification.mdx | 23 + .../configure_auth_category.mdx | 87 +++ .../setup_auth_provider.mdx | 79 +++ .../lib-legacy/auth/existing-resources.mdx | 35 + .../10_fetchAuthSession.mdx | 78 +++ .../lib-legacy/auth/flutter/common_prereq.mdx | 1 + .../flutter/delete_user/10_delete_user.mdx | 10 + .../device_features/10_rememberDevice.mdx | 10 + .../device_features/20_forgetDevice.mdx | 32 + .../device_features/30_fetchDevice.mdx | 12 + .../10_existingResources.mdx | 50 ++ .../flutter/getting_started/10_preReq.mdx | 28 + .../getting_started/12_amplifyConfig.mdx | 1 + .../flutter/getting_started/20_installLib.mdx | 5 + .../flutter/getting_started/30_initAuth.mdx | 51 ++ .../getting_started/40_authenticator.mdx | 16 + .../getting_started/50_fetchSession.mdx | 19 + .../getting_started/60_developerPreview.mdx | 9 + .../flutter/hub_events/10_listen_events.mdx | 21 + .../10_managing_credentials.mdx | 35 + .../password_management/10_reset_password.mdx | 19 + .../20_confirm_reset_password.mdx | 13 + .../30_change_password.mdx | 12 + .../auth/flutter/signin/10_signUp.mdx | 27 + .../auth/flutter/signin/20_confirmSignUp.mdx | 23 + .../auth/flutter/signin/30_signIn.mdx | 25 + .../flutter/signin/40_multi_factor_signup.mdx | 29 + .../signin/50_multi_factor_confirm_signin.mdx | 19 + .../flutter/signin/60_runtime_auth_flow.mdx | 19 + .../flutter/signin_web_ui/10_cli_setup.mdx | 72 ++ .../20_platform_specific_setup.mdx | 120 ++++ .../auth/flutter/signin_web_ui/30_signin.mdx | 31 + .../signin_web_ui/40_private_session.mdx | 15 + .../signin_with_custom_flow/10_cli_setup.mdx | 203 ++++++ .../signin_with_custom_flow/20_signup.mdx | 28 + .../signin_with_custom_flow/30_signin.mdx | 28 + .../40_custom_challenge.mdx | 15 + ...custom_challenge_password_verification.mdx | 38 ++ .../auth/flutter/signout/10_local_signout.mdx | 9 + .../flutter/signout/20_global_signout.mdx | 9 + .../auth/flutter/sms/confirm_sign_in.mdx | 7 + .../auth/flutter/sms/confirm_sign_up.mdx | 8 + .../lib-legacy/auth/flutter/sms/sign_in.mdx | 11 + .../lib-legacy/auth/flutter/sms/sign_up.mdx | 18 + .../social_signin_web_ui/10_cli_setup.mdx | 28 + .../social_signin_web_ui/20_signin.mdx | 10 + .../user_attributes/10_fetch_attributes.mdx | 13 + .../20_update_user_attribute.mdx | 51 ++ .../user_attributes/30_confirm_attribute.mdx | 13 + .../user_attributes/40_resend_code.mdx | 13 + .../js/getting-started-steps-basic-auth.mdx | 34 + .../10_fetchAuthSession.mdx | 56 ++ .../auth/ios/delete_user/10_delete_user.mdx | 39 ++ .../ios/device_features/10_rememberDevice.mdx | 39 ++ .../ios/device_features/20_forgetDevice.mdx | 39 ++ .../ios/device_features/30_fetchDevice.mdx | 43 ++ .../escapehatch/10_awsmobileclient_escape.mdx | 101 +++ .../auth/ios/getting_started/10_preReq.mdx | 3 + .../ios/getting_started/12_amplifyConfig.mdx | 1 + .../ios/getting_started/20_installLib.mdx | 39 ++ .../auth/ios/getting_started/30_initAuth.mdx | 94 +++ .../ios/getting_started/40_fetchSession.mdx | 38 ++ .../auth/ios/hub_events/10_listen_events.mdx | 75 +++ .../password_management/10_reset_password.mdx | 69 ++ .../20_confirm_reset_password.mdx | 54 ++ .../30_change_password.mdx | 39 ++ .../lib-legacy/auth/ios/signin/10_signUp.mdx | 53 ++ .../auth/ios/signin/20_confirmSignUp.mdx | 39 ++ .../lib-legacy/auth/ios/signin/30_signIn.mdx | 39 ++ .../ios/signin/40_multi_factor_signup.mdx | 52 ++ .../signin/50_multi_factor_confirm_signin.mdx | 39 ++ .../auth/ios/signin_next_steps/10_signin.mdx | 58 ++ .../signin_next_steps/20_confirm_sms_mfa.mdx | 59 ++ .../30_confirm_custom_challenge.mdx | 57 ++ .../40_confirm_new_password.mdx | 57 ++ .../signin_next_steps/50_reset_password.mdx | 43 ++ .../signin_next_steps/60_confirm_signup.mdx | 41 ++ .../auth/ios/signin_next_steps/70_done.mdx | 1 + .../auth/ios/signin_web_ui/10_cli_setup.mdx | 24 + .../20_platform_specific_setup.mdx | 29 + .../auth/ios/signin_web_ui/30_signin.mdx | 41 ++ .../ios/signin_web_ui/40_private_session.mdx | 9 + .../signin_with_custom_flow/10_cli_setup.mdx | 178 +++++ .../ios/signin_with_custom_flow/20_signup.mdx | 53 ++ .../30_confirmSignup.mdx | 45 ++ .../ios/signin_with_custom_flow/40_signin.mdx | 49 ++ .../50_custom_challenge.mdx | 76 +++ .../auth/ios/signout/10_local_signout.mdx | 39 ++ .../auth/ios/signout/20_global_signout.mdx | 40 ++ .../ios/social_signin_web_ui/10_cli_setup.mdx | 28 + .../ios/social_signin_web_ui/20_signin.mdx | 41 ++ .../user_attributes/10_fetch_attributes.mdx | 39 ++ .../20_update_user_attribute.mdx | 49 ++ .../user_attributes/30_confirm_attribute.mdx | 39 ++ .../ios/user_attributes/40_resend_code.mdx | 39 ++ src/fragments/lib-legacy/auth/js/advanced.mdx | 456 +++++++++++++ src/fragments/lib-legacy/auth/js/customui.mdx | 171 +++++ .../lib-legacy/auth/js/delete_user.mdx | 32 + .../js/device_features/10_rememberDevice.mdx | 10 + .../js/device_features/20_forgetDevice.mdx | 10 + .../js/device_features/30_fetchDevice.mdx | 10 + .../lib-legacy/auth/js/emailpassword.mdx | 162 +++++ .../js/getting-started-steps-basic-auth.mdx | 34 + .../lib-legacy/auth/js/getting-started.mdx | 229 +++++++ .../auth/js/hub_events/10_listen_events.mdx | 38 ++ .../lib-legacy/auth/js/manageusers.mdx | 290 ++++++++ src/fragments/lib-legacy/auth/js/mfa.mdx | 220 ++++++ src/fragments/lib-legacy/auth/js/overview.mdx | 39 ++ .../auth/js/react-native-withoauth.mdx | 98 +++ src/fragments/lib-legacy/auth/js/social.mdx | 448 +++++++++++++ src/fragments/lib-legacy/auth/js/start.mdx | 137 ++++ .../lib-legacy/auth/js/switch-auth.mdx | 133 ++++ .../access_credentials/common.mdx | 17 + .../auth/native_common/delete_user/common.mdx | 34 + .../native_common/device_features/common.mdx | 75 +++ .../native_common/escape_hatch/common.mdx | 7 + .../existing_resources/common.mdx | 48 ++ .../native_common/getting_started/common.mdx | 116 ++++ .../native_common/guest_access/common.mdx | 23 + .../password_management/common.mdx | 48 ++ .../auth/native_common/signin/common.mdx | 185 ++++++ .../signin_next_steps/common.mdx | 41 ++ .../native_common/signin_web_ui/common.mdx | 67 ++ .../signin_with_custom_flow/common.mdx | 71 ++ .../auth/native_common/signout/common.mdx | 41 ++ .../social_signin_web_ui/common.mdx | 62 ++ .../native_common/user_attributes/common.mdx | 62 ++ .../js/js-configuration.mdx | 406 +++++++++++ .../lib-legacy/datastore/android/conflict.mdx | 82 +++ .../android/data-access/delete-snippet.mdx | 133 ++++ .../data-access/observe-update-snippet.mdx | 175 +++++ .../data-access/query-basic-snippet.mdx | 51 ++ .../data-access/query-pagination-snippet.mdx | 60 ++ .../query-predicate-multiple-snippet.mdx | 64 ++ .../query-predicate-or-snippet.mdx | 64 ++ .../data-access/query-predicate-snippet.mdx | 62 ++ .../query-sort-multiple-snippet.mdx | 62 ++ .../data-access/query-sort-snippet.mdx | 61 ++ .../android/data-access/save-snippet.mdx | 68 ++ .../android/data-access/update-snippet.mdx | 78 +++ .../datastore/android/datastore-events.mdx | 54 ++ .../outbox-mutation-failed.mdx | 10 + .../android/getting-started/10_preReq.mdx | 3 + .../android/getting-started/20_installLib.mdx | 25 + .../android/getting-started/40_codegen.mdx | 1 + .../android/getting-started/50_codegenCli.mdx | 10 + .../getting-started/60_initDataStore.mdx | 92 +++ .../getting-started/70_saveSnippet.mdx | 64 ++ .../getting-started/80_querySnippet.mdx | 53 ++ .../android/other-methods/10_clear.mdx | 49 ++ .../android/other-methods/15_start.mdx | 1 + .../android/other-methods/20_start.mdx | 46 ++ .../android/other-methods/30_stop.mdx | 46 ++ .../real-time/observe-query-snippet.mdx | 110 +++ .../android/real-time/observe-snippet.mdx | 55 ++ .../android/relational/delete-snippet.mdx | 61 ++ .../android/relational/query-snippet.mdx | 53 ++ .../android/relational/save-many-snippet.mdx | 138 ++++ .../android/relational/save-snippet.mdx | 106 +++ .../android/relational/updated-schema.mdx | 27 + .../setup-auth-rules/10_multiauth-snippet.mdx | 119 ++++ .../owner_based_auth_oidc.mdx | 29 + .../android/sync/10-installPlugin.mdx | 38 ++ .../android/sync/20-savePredicate.mdx | 46 ++ .../sync/30-savePredicateComparison.mdx | 80 +++ .../datastore/android/sync/40-clear.mdx | 68 ++ .../android/sync/50-selectiveSync.mdx | 407 ++++++++++++ .../lib-legacy/datastore/flutter/conflict.mdx | 52 ++ .../flutter/data-access/delete-snippet.mdx | 19 + .../data-access/observe-update-snippet.mdx | 56 ++ .../data-access/query-basic-snippet.mdx | 10 + .../data-access/query-pagination-snippet.mdx | 12 + .../query-predicate-multiple-snippet.mdx | 9 + .../query-predicate-or-snippet.mdx | 9 + .../data-access/query-predicate-snippet.mdx | 9 + .../query-sort-multiple-snippet.mdx | 12 + .../data-access/query-sort-snippet.mdx | 9 + .../flutter/data-access/save-snippet.mdx | 11 + .../flutter/data-access/update-snippet.mdx | 13 + .../datastore/flutter/datastore-events.mdx | 48 ++ .../flutter/getting-started/10_preReq.mdx | 14 + .../flutter/getting-started/20_installLib.mdx | 15 + .../30_platformIntegration.mdx | 1 + .../flutter/getting-started/40_codegen.mdx | 2 + .../flutter/getting-started/50_codegenCli.mdx | 15 + .../getting-started/50_initDataStore.mdx | 47 ++ .../getting-started/60_initDataStore.mdx | 47 ++ .../getting-started/60_saveSnippet.mdx | 5 + .../getting-started/70_querySnippet.mdx | 10 + .../getting-started/80_saveSnippet.mdx | 10 + .../flutter/other-methods/10_clear.mdx | 10 + .../flutter/other-methods/15_start.mdx | 1 + .../flutter/other-methods/20_start.mdx | 10 + .../flutter/other-methods/30_stop.mdx | 10 + .../real-time/observe-query-snippet.mdx | 48 ++ .../flutter/real-time/observe-snippet.mdx | 16 + .../flutter/relational/delete-snippet.mdx | 13 + .../flutter/relational/query-snippet.mdx | 11 + .../flutter/relational/save-many-snippet.mdx | 25 + .../flutter/relational/save-snippet.mdx | 18 + .../flutter/relational/updated-schema.mdx | 31 + .../setup-auth-rules/10_multiauth-snippet.mdx | 16 + .../owner_based_auth_oidc.mdx | 37 ++ .../flutter/sync/10-installPlugin.mdx | 42 ++ .../flutter/sync/20-savePredicate.mdx | 12 + .../sync/30-savePredicateComparison.mdx | 20 + .../datastore/flutter/sync/40-clear.mdx | 12 + .../flutter/sync/50-selectiveSync.mdx | 198 ++++++ .../lib-legacy/datastore/ios/auth-model.mdx | 146 ++++ .../lib-legacy/datastore/ios/conflict.mdx | 41 ++ .../ios/data-access/delete-snippet.mdx | 69 ++ .../data-access/observe-update-snippet.mdx | 67 ++ .../ios/data-access/query-basic-snippet.mdx | 73 ++ .../data-access/query-pagination-snippet.mdx | 29 + .../query-predicate-multiple-snippet.mdx | 72 ++ .../query-predicate-or-snippet.mdx | 41 ++ .../data-access/query-predicate-snippet.mdx | 35 + .../query-sort-multiple-snippet.mdx | 35 + .../ios/data-access/query-sort-snippet.mdx | 33 + .../ios/data-access/save-snippet.mdx | 44 ++ .../ios/data-access/update-snippet.mdx | 39 ++ .../datastore/ios/datastore-events.mdx | 47 ++ .../ios/getting-started/10_preReq.mdx | 2 + .../ios/getting-started/20_installLib.mdx | 41 ++ .../ios/getting-started/30_setupEnv.mdx | 12 + .../ios/getting-started/40_codegen.mdx | 16 + .../ios/getting-started/50_initDataStore.mdx | 99 +++ .../ios/getting-started/60_saveSnippet.mdx | 35 + .../ios/getting-started/70_querySnippet.mdx | 33 + .../datastore/ios/other-methods/10_clear.mdx | 10 + .../datastore/ios/other-methods/15_start.mdx | 1 + .../datastore/ios/other-methods/20_start.mdx | 10 + .../datastore/ios/other-methods/30_stop.mdx | 10 + .../ios/real-time/observe-query-snippet.mdx | 28 + .../ios/real-time/observe-snippet.mdx | 37 ++ .../ios/relational/delete-snippet.mdx | 47 ++ .../ios/relational/query-snippet.mdx | 57 ++ .../ios/relational/save-many-snippet.mdx | 67 ++ .../datastore/ios/relational/save-snippet.mdx | 54 ++ .../ios/relational/updated-schema.mdx | 24 + .../setup-auth-rules/10_multiauth-snippet.mdx | 64 ++ .../owner_based_auth_oidc.mdx | 27 + .../datastore/ios/sync/10-installPlugin.mdx | 15 + .../datastore/ios/sync/20-savePredicate.mdx | 34 + .../ios/sync/30-savePredicateComparison.mdx | 13 + .../datastore/ios/sync/40-clear.mdx | 41 ++ .../datastore/ios/sync/50-selectiveSync.mdx | 162 +++++ .../lib-legacy/datastore/js/conflict.mdx | 34 + .../js/data-access/delete-snippet.mdx | 23 + .../importing-datastore-snippet.mdx | 7 + .../js/data-access/observe-update-snippet.mdx | 55 ++ .../js/data-access/query-basic-snippet.mdx | 7 + .../data-access/query-pagination-snippet.mdx | 6 + .../query-predicate-multiple-snippet.mdx | 7 + .../query-predicate-or-snippet.mdx | 7 + .../data-access/query-predicate-snippet.mdx | 3 + .../data-access/query-single-item-snippet.mdx | 7 + .../query-sort-multiple-snippet.mdx | 5 + .../js/data-access/query-sort-snippet.mdx | 5 + .../datastore/js/data-access/save-snippet.mdx | 9 + .../js/data-access/update-snippet.mdx | 16 + .../datastore/js/datastore-events.mdx | 58 ++ .../js/datastore-events/model-synced.mdx | 13 + .../outbox-mutation-enqueued.mdx | 9 + .../outbox-mutation-processed.mdx | 12 + .../lib-legacy/datastore/js/examples.mdx | 329 +++++++++ .../js/getting-started/10_preReq.mdx | 5 + .../30_platformIntegration.mdx | 97 +++ .../js/getting-started/40_codegen.mdx | 22 + .../js/getting-started/50_initDataStore.mdx | 1 + .../js/getting-started/60_saveSnippet.mdx | 12 + .../js/getting-started/70_querySnippet.mdx | 8 + .../lib-legacy/datastore/js/other-methods.mdx | 27 + .../js/real-time/observe-query-snippet.mdx | 18 + .../js/real-time/observe-snippet.mdx | 38 ++ .../js/relational/delete-snippet.mdx | 4 + .../js/relational/query-many-snippet.mdx | 48 ++ .../datastore/js/relational/query-snippet.mdx | 10 + .../js/relational/save-many-snippet.mdx | 23 + .../datastore/js/relational/save-snippet.mdx | 16 + .../js/relational/updated-schema.mdx | 25 + .../setup-auth-rules/10_multiauth-snippet.mdx | 11 + .../20_function-auth-snippet.mdx | 23 + .../js/setup-auth-rules/multi-auth.mdx | 56 ++ .../datastore/js/sync/20-savePredicate.mdx | 10 + .../js/sync/30-savePredicateComparison.mdx | 16 + .../lib-legacy/datastore/js/sync/40-clear.mdx | 7 + .../datastore/js/sync/50-selectiveSync.mdx | 170 +++++ .../callout/datastore-clear-with-auth.mdx | 5 + .../datastore/native_common/codegen.mdx | 10 + .../datastore/native_common/conflict.mdx | 54 ++ .../datastore/native_common/data-access.mdx | 220 ++++++ .../datastore-events/model-synced.mdx | 11 + .../outbox-mutation-enqueued.mdx | 8 + .../outbox-mutation-processed.mdx | 11 + .../native_common/getting-started.mdx | 204 ++++++ .../datastore/native_common/how-it-works.mdx | 66 ++ .../datastore/native_common/other-methods.mdx | 62 ++ .../datastore/native_common/real-time.mdx | 47 ++ .../datastore/native_common/relational.mdx | 131 ++++ .../native_common/schema-updates.mdx | 27 + .../native_common/setup-auth-rules.mdx | 222 +++++++ .../datastore/native_common/setup-env-cli.mdx | 51 ++ .../datastore/native_common/setup-env.mdx | 19 + .../datastore/native_common/sort.mdx | 39 ++ .../native_common/sync-distributed-data.mdx | 47 ++ .../datastore/native_common/sync.mdx | 124 ++++ .../debugging/android/dev-menu/setup.mdx | 37 ++ .../debugging/android/dev-menu/usage.mdx | 3 + .../debugging/ios/dev-menu/setup.mdx | 15 + .../debugging/ios/dev-menu/usage.mdx | 1 + .../native_common/dev-menu/common.mdx | 46 ++ src/fragments/lib-legacy/flutter.mdx | 15 + .../geo/android/dev-preview-callout.mdx | 5 + .../lib-legacy/geo/android/escapehatch.mdx | 68 ++ .../geo/android/existing-resources.mdx | 53 ++ .../android/getting_started/10_pre_req.mdx | 2 + .../getting_started/20_cli_resources.mdx | 89 +++ .../getting_started/30_install_lib.mdx | 14 + .../android/getting_started/40_init_geo.mdx | 96 +++ .../geo/android/maps/10_install_adapter.mdx | 17 + .../geo/android/maps/20_display_map.mdx | 436 ++++++++++++ .../lib-legacy/geo/android/maps/30_styles.mdx | 90 +++ .../geo/android/search/10_search_by_text.mdx | 96 +++ .../search/20_search_by_coordinates.mdx | 82 +++ .../android/search/30_location_search_map.mdx | 1 + .../lib-legacy/geo/existing-resources.mdx | 49 ++ .../geo/ios/dev-preview-callout.mdx | 5 + .../lib-legacy/geo/ios/escapehatch.mdx | 111 ++++ .../lib-legacy/geo/ios/existing-resources.mdx | 31 + .../geo/ios/getting_started/10_pre_req.mdx | 2 + .../ios/getting_started/20_cli_resources.mdx | 15 + .../ios/getting_started/30_install_lib.mdx | 50 ++ .../geo/ios/getting_started/40_init_geo.mdx | 77 +++ .../geo/ios/maps/10_install_adapter.mdx | 23 + .../geo/ios/maps/20_display_map.mdx | 119 ++++ .../lib-legacy/geo/ios/maps/30_styles.mdx | 47 ++ .../geo/ios/search/10_search_by_text.mdx | 76 +++ .../ios/search/20_search_by_coordinates.mdx | 41 ++ .../geo/ios/search/30_location_search_map.mdx | 124 ++++ .../lib-legacy/geo/js/escapehatch.mdx | 91 +++ .../lib-legacy/geo/js/existing-resources.mdx | 39 ++ src/fragments/lib-legacy/geo/js/geofences.mdx | 344 ++++++++++ .../lib-legacy/geo/js/getting-started.mdx | 74 +++ .../lib-legacy/geo/js/google-migration.mdx | 230 +++++++ src/fragments/lib-legacy/geo/js/maps.mdx | 349 ++++++++++ src/fragments/lib-legacy/geo/js/search.mdx | 179 +++++ .../native_common/getting_started/common.mdx | 48 ++ .../geo/native_common/maps/common.mdx | 31 + .../geo/native_common/search/common.mdx | 31 + .../android/advanced-workflows/10_example.mdx | 65 ++ .../android/advanced-workflows/20_custom.mdx | 77 +++ .../android/advanced-workflows/30_nested.mdx | 95 +++ .../advanced-workflows/40_multiple.mdx | 54 ++ .../advanced-workflows/50_interceptor.mdx | 76 +++ .../graphqlapi/android/authz/10_userpool.mdx | 20 + .../graphqlapi/android/authz/20_oidc.mdx | 30 + .../graphqlapi/android/authz/21_oidc.mdx | 128 ++++ .../graphqlapi/android/authz/22_lambda.mdx | 30 + .../graphqlapi/android/authz/30_multi.mdx | 41 ++ .../graphqlapi/android/getting-started.mdx | 156 +++++ .../android/getting-started/10_preReq.mdx | 3 + .../getting-started/12_amplifyConfig.mdx | 1 + .../android/getting-started/20_installLib.mdx | 11 + .../android/getting-started/30_initapi.mdx | 89 +++ .../android/getting-started/40_codegen.mdx | 9 + .../android/getting-started/50_createtodo.mdx | 66 ++ .../graphqlapi/android/mutate-data.mdx | 66 ++ .../graphqlapi/android/query-data.mdx | 234 +++++++ .../graphqlapi/android/subscribe-data.mdx | 77 +++ .../graphqlapi/existing-resources.mdx | 26 + .../flutter/advanced-workflows/10_example.mdx | 8 + .../flutter/advanced-workflows/20_custom.mdx | 24 + .../flutter/advanced-workflows/30_nested.mdx | 32 + .../advanced-workflows/40_multiple.mdx | 44 ++ .../advanced-workflows/50_interceptor.mdx | 5 + .../lib-legacy/graphqlapi/flutter/authz.mdx | 139 ++++ .../graphqlapi/flutter/authz/10_userpool.mdx | 21 + .../graphqlapi/flutter/authz/20_oidc.mdx | 16 + .../graphqlapi/flutter/authz/21_oidc.mdx | 20 + .../graphqlapi/flutter/authz/22_lambda.mdx | 14 + .../flutter/authz/2X_add_plugin.mdx | 16 + .../graphqlapi/flutter/authz/30_multi.mdx | 15 + .../flutter/getting-started/10_preReq.mdx | 5 + .../getting-started/12_amplifyConfig.mdx | 1 + .../flutter/getting-started/20_installLib.mdx | 12 + .../flutter/getting-started/30_initapi.mdx | 37 ++ .../flutter/getting-started/40_codegen.mdx | 15 + .../flutter/getting-started/50_createtodo.mdx | 18 + .../graphqlapi/flutter/mutate-data.mdx | 36 + .../graphqlapi/flutter/query-data.mdx | 120 ++++ .../graphqlapi/flutter/subscribe-data.mdx | 61 ++ .../graphqlapi/graphql-from-node.mdx | 314 +++++++++ .../ios/advanced-workflows/10_example.mdx | 4 + .../ios/advanced-workflows/20_custom.mdx | 26 + .../ios/advanced-workflows/30_nested.mdx | 31 + .../ios/advanced-workflows/40_multiple.mdx | 70 ++ .../ios/advanced-workflows/50_interceptor.mdx | 18 + .../graphqlapi/ios/authz/10_userpool.mdx | 12 + .../graphqlapi/ios/authz/20_oidc.mdx | 27 + .../graphqlapi/ios/authz/21_oidc.mdx | 28 + .../graphqlapi/ios/authz/22_lambda.mdx | 27 + .../graphqlapi/ios/authz/30_multi.mdx | 6 + .../graphqlapi/ios/getting-started.mdx | 173 +++++ .../ios/getting-started/10_preReq.mdx | 3 + .../ios/getting-started/12_amplifyConfig.mdx | 1 + .../ios/getting-started/20_installLib.mdx | 39 ++ .../ios/getting-started/30_initapi.mdx | 94 +++ .../ios/getting-started/40_codegen.mdx | 15 + .../ios/getting-started/50_createtodo.mdx | 58 ++ .../lib-legacy/graphqlapi/ios/mutate-data.mdx | 81 +++ .../lib-legacy/graphqlapi/ios/query-data.mdx | 390 +++++++++++ .../graphqlapi/ios/subscribe-data.mdx | 114 ++++ .../lib-legacy/graphqlapi/js/authz.mdx | 66 ++ .../graphqlapi/js/cancel-request.mdx | 35 + .../graphqlapi/js/complex-objects.mdx | 85 +++ .../js/create-or-re-use-existing-backend.mdx | 149 +++++ .../lib-legacy/graphqlapi/js/delta-sync.mdx | 367 ++++++++++ .../graphqlapi/js/getting-started.mdx | 218 ++++++ .../lib-legacy/graphqlapi/js/mutate-data.mdx | 90 +++ .../lib-legacy/graphqlapi/js/offline.mdx | 5 + .../lib-legacy/graphqlapi/js/query-data.mdx | 156 +++++ .../graphqlapi/js/subscribe-data.mdx | 60 ++ .../advanced-workflows/common.mdx | 165 +++++ .../graphqlapi/native_common/authz/common.mdx | 327 +++++++++ .../graphqlapi/native_common/concepts.mdx | 30 + .../native_common/getting-started/common.mdx | 146 ++++ .../in-app-messaging/js/clear-messages.mdx | 12 + .../in-app-messaging/js/create-campaign.mdx | 60 ++ .../in-app-messaging/js/customize.mdx | 109 +++ .../in-app-messaging/js/display-message.mdx | 33 + .../in-app-messaging/js/getting-started.mdx | 126 ++++ .../in-app-messaging/js/identify-user.mdx | 45 ++ .../in-app-messaging/js/overview.mdx | 25 + .../in-app-messaging/js/prerequisites.mdx | 136 ++++ .../in-app-messaging/js/resolve-conflicts.mdx | 20 + .../js/respond-interaction-events.mdx | 79 +++ .../in-app-messaging/js/sync-messages.mdx | 10 + .../lib-legacy/info/ios/app-uninstall.mdx | 5 + .../lib-legacy/info/ios/data-information.mdx | 138 ++++ .../native_common/app-uninstall/common.mdx | 5 + .../native_common/data-information/common.mdx | 5 + .../lib-legacy/interactions/js/chatbot.mdx | 34 + .../interactions/js/getting-started.mdx | 83 +++ src/fragments/lib-legacy/ios-sdk.mdx | 23 + src/fragments/lib-legacy/ios-spm.mdx | 9 + src/fragments/lib-legacy/ios.mdx | 15 + src/fragments/lib-legacy/js.mdx | 16 + .../predictions/android/escapehatch.mdx | 43 ++ .../android/getting-started/20_preReq.mdx | 2 + .../android/getting-started/30_installLib.mdx | 15 + .../android/getting-started/40_init.mdx | 97 +++ .../android/getting-started/50_translate.mdx | 63 ++ .../android/getting-started/60_nextSteps.mdx | 6 + .../predictions/android/identify-entity.mdx | 281 ++++++++ .../predictions/android/identify-text.mdx | 164 +++++ .../predictions/android/interpret.mdx | 78 +++ .../predictions/android/label-image.mdx | 151 +++++ .../predictions/android/text-speech.mdx | 192 ++++++ .../predictions/android/translate.mdx | 150 +++++ .../predictions/ios/escapehatch.mdx | 54 ++ .../ios/getting-started/10_coreml.mdx | 1 + .../ios/getting-started/20_preReq.mdx | 2 + .../ios/getting-started/30_installLib.mdx | 23 + .../ios/getting-started/40_init.mdx | 79 +++ .../ios/getting-started/50_translate.mdx | 46 ++ .../ios/getting-started/60_nextSteps.mdx | 7 + .../predictions/ios/identify-entity.mdx | 182 +++++ .../predictions/ios/identify-text.mdx | 158 +++++ .../lib-legacy/predictions/ios/interpret.mdx | 82 +++ .../predictions/ios/label-image.mdx | 121 ++++ .../lib-legacy/predictions/ios/sample.mdx | 1 + .../predictions/ios/text-speech.mdx | 119 ++++ .../lib-legacy/predictions/ios/transcribe.mdx | 79 +++ .../lib-legacy/predictions/ios/translate.mdx | 85 +++ .../predictions/js/getting-started.mdx | 156 +++++ .../predictions/js/identify-entity.mdx | 197 ++++++ .../predictions/js/identify-text.mdx | 229 +++++++ .../lib-legacy/predictions/js/interpret.mdx | 42 ++ .../lib-legacy/predictions/js/intro.mdx | 26 + .../lib-legacy/predictions/js/label-image.mdx | 84 +++ .../lib-legacy/predictions/js/sample.mdx | 629 ++++++++++++++++++ .../lib-legacy/predictions/js/text-speech.mdx | 37 ++ .../lib-legacy/predictions/js/transcribe.mdx | 36 + .../lib-legacy/predictions/js/translate.mdx | 36 + .../native_common/getting-started/common.mdx | 112 ++++ .../project-setup/android/async/async.mdx | 70 ++ .../android/coroutines/coroutines.mdx | 125 ++++ .../create-application/10_createProject.mdx | 22 + .../android/create-application/20_gradle.mdx} | 6 +- .../30_provisionBackend.mdx | 30 + .../40_verifyAmplifyLibraries.mdx | 78 +++ .../project-setup/android/prereq/prereq.mdx | 2 + .../project-setup/android/rxjava/rxjava.mdx | 124 ++++ .../use-existing-resources.mdx | 95 +++ .../create-application/10_createProject.mdx | 7 + .../flutter/create-application/20_pubspec.mdx | 13 + .../30_provisionBackend.mdx | 52 ++ .../40_verifyAmplifyLibraries.mdx | 49 ++ .../create-application/50_nextSteps.mdx | 8 + .../create-application/60_dependencies.mdx | 35 + .../flutter/escape-hatch/escape-hatch.mdx | 84 +++ .../flutter/null-safety/null-safety.mdx | 38 ++ .../flutter/platform-setup/platform-setup.mdx | 105 +++ .../flutter/prereq/cliInstall.mdx | 5 + .../project-setup/flutter/prereq/prereq.mdx | 4 + .../project-setup/ios/combine/combine.mdx | 163 +++++ .../create-application/10_createProject.mdx | 18 + .../20_install_libraries.mdx | 85 +++ .../30_provisionBackend.mdx | 23 + .../31_provisionBackend.mdx | 13 + .../40_verifyAmplifyLibraries.mdx | 57 ++ .../project-setup/ios/prereq/prereq.mdx | 4 + .../use-existing-resources.mdx | 47 ++ .../create-application/50_nextSteps.mdx | 10 + .../create-application/common.mdx | 79 +++ .../native_common/prereq/cliInstall.mdx | 5 + .../native_common/prereq/common_body.mdx | 72 ++ .../native_common/prereq/common_header.mdx | 5 + .../prereq/flutter_null_safety.mdx | 6 + .../pubsub/js/connection-states.mdx | 10 + .../lib-legacy/pubsub/js/getting-started.mdx | 112 ++++ .../lib-legacy/pubsub/js/publish.mdx | 20 + .../lib-legacy/pubsub/js/subunsub.mdx | 80 +++ .../push-notifications/js/getting-started.mdx | 240 +++++++ .../push-notifications/js/overview.mdx | 6 + .../push-notifications/js/reactnative.mdx | 5 + .../push-notifications/js/testing.mdx | 1 + .../js/working-with-api.mdx | 43 ++ .../restapi/android/authz/22_none_headers.mdx | 45 ++ .../lib-legacy/restapi/android/delete.mdx | 62 ++ .../lib-legacy/restapi/android/fetch.mdx | 177 +++++ .../restapi/android/getting-started.mdx | 100 +++ .../android/getting-started/10_preReq.mdx | 2 + .../getting-started/11_amplifyInit.mdx | 52 ++ .../android/getting-started/20_installLib.mdx | 12 + .../android/getting-started/30_initapi.mdx | 95 +++ .../android/getting-started/40_postTodo.mdx | 64 ++ .../lib-legacy/restapi/android/update.mdx | 71 ++ .../lib-legacy/restapi/existing-resources.mdx | 27 + .../lib-legacy/restapi/flutter/authz.mdx | 110 +++ .../lib-legacy/restapi/flutter/delete.mdx | 16 + .../lib-legacy/restapi/flutter/fetch.mdx | 71 ++ .../flutter/getting-started/10_preReq.mdx | 5 + .../getting-started/11_amplifyInit.mdx | 43 ++ .../flutter/getting-started/20_installLib.mdx | 13 + .../flutter/getting-started/30_initapi.mdx | 64 ++ .../flutter/getting-started/40_postTodo.mdx | 20 + .../lib-legacy/restapi/flutter/update.mdx | 20 + .../restapi/ios/authz/22_none_headers.mdx | 5 + .../lib-legacy/restapi/ios/delete.mdx | 46 ++ .../lib-legacy/restapi/ios/fetch.mdx | 146 ++++ .../restapi/ios/getting-started.mdx | 78 +++ .../restapi/ios/getting-started/10_preReq.mdx | 3 + .../ios/getting-started/11_amplifyInit.mdx | 50 ++ .../ios/getting-started/20_installLib.mdx | 40 ++ .../ios/getting-started/30_initapi.mdx | 96 +++ .../ios/getting-started/40_postTodo.mdx | 46 ++ .../lib-legacy/restapi/ios/update.mdx | 50 ++ src/fragments/lib-legacy/restapi/js/authz.mdx | 75 +++ .../lib-legacy/restapi/js/cancel.mdx | 35 + .../lib-legacy/restapi/js/delete.mdx | 47 ++ src/fragments/lib-legacy/restapi/js/fetch.mdx | 142 ++++ .../lib-legacy/restapi/js/getting-started.mdx | 111 ++++ .../lib-legacy/restapi/js/update.mdx | 118 ++++ .../restapi/native_common/authz/common.mdx | 175 +++++ .../native_common/getting-started/common.mdx | 87 +++ .../lib-legacy/ssr/js/getting-started.mdx | 246 +++++++ .../storage/android/configureaccess.mdx | 288 ++++++++ .../configureaccess/10_protected_upload.mdx | 73 ++ .../configureaccess/20_protected_download.mdx | 57 ++ .../configureaccess/30_private_upload.mdx | 54 ++ .../configureaccess/40_private_download.mdx | 20 + .../configureaccess/50_customization.mdx | 158 +++++ .../lib-legacy/storage/android/download.mdx | 183 +++++ .../storage/android/escapehatch.mdx | 20 + .../android/getting-started/10_preReq.mdx | 3 + .../getting-started/12_amplifyConfig.mdx | 1 + .../android/getting-started/20_installLib.mdx | 13 + .../getting-started/30_initStorage.mdx | 97 +++ .../android/getting-started/40_upload.mdx | 86 +++ .../lib-legacy/storage/android/list.mdx | 143 ++++ .../lib-legacy/storage/android/remove.mdx | 48 ++ .../lib-legacy/storage/android/upload.mdx | 258 +++++++ .../lib-legacy/storage/existing-resources.mdx | 31 + .../configureaccess/10_protected_upload.mdx | 35 + .../configureaccess/20_protected_download.mdx | 35 + .../configureaccess/30_private_upload.mdx | 20 + .../configureaccess/40_private_download.mdx | 19 + .../lib-legacy/storage/flutter/download.mdx | 48 ++ .../flutter/getting-started/10_preReq.mdx | 6 + .../getting-started/12_amplifyConfig.mdx | 1 + .../flutter/getting-started/20_installLib.mdx | 21 + .../getting-started/30_initStorage.mdx | 16 + .../flutter/getting-started/40_upload.mdx | 5 + .../lib-legacy/storage/flutter/list.mdx | 13 + .../storage/flutter/path-provider.mdx | 1 + .../lib-legacy/storage/flutter/remove.mdx | 12 + .../lib-legacy/storage/flutter/upload.mdx | 141 ++++ .../flutter/upload/upload-create-file.mdx | 33 + .../configureaccess/10_protected_upload.mdx | 46 ++ .../configureaccess/20_protected_download.mdx | 50 ++ .../ios/configureaccess/30_private_upload.mdx | 3 + .../configureaccess/40_private_download.mdx | 3 + .../ios/configureaccess/50_customization.mdx | 145 ++++ .../lib-legacy/storage/ios/download.mdx | 162 +++++ .../lib-legacy/storage/ios/escapehatch.mdx | 38 ++ .../storage/ios/getting-started/10_preReq.mdx | 3 + .../ios/getting-started/12_amplifyConfig.mdx | 1 + .../ios/getting-started/20_installLib.mdx | 40 ++ .../ios/getting-started/30_initStorage.mdx | 96 +++ .../storage/ios/getting-started/40_upload.mdx | 57 ++ src/fragments/lib-legacy/storage/ios/list.mdx | 43 ++ .../lib-legacy/storage/ios/remove.mdx | 37 ++ .../lib-legacy/storage/ios/upload.mdx | 141 ++++ .../lib-legacy/storage/js/autotrack.mdx | 19 + .../lib-legacy/storage/js/cancel-requests.mdx | 32 + .../lib-legacy/storage/js/configureaccess.mdx | 90 +++ src/fragments/lib-legacy/storage/js/copy.mdx | 117 ++++ .../lib-legacy/storage/js/download.mdx | 150 +++++ .../lib-legacy/storage/js/getting-started.mdx | 334 ++++++++++ src/fragments/lib-legacy/storage/js/list.mdx | 113 ++++ .../lib-legacy/storage/js/remove.mdx | 19 + .../lib-legacy/storage/js/upload.mdx | 174 +++++ .../native_common/configureaccess/common.mdx | 87 +++ .../native_common/getting-started/common.mdx | 128 ++++ .../troubleshooting/flutter/upgrading.mdx | 37 ++ .../troubleshooting/js/strict-mode.mdx | 32 + .../troubleshooting/js/upgrading.mdx | 98 +++ .../utilities/android/hub/10_listen_start.mdx | 1 + .../utilities/android/hub/20_listen_stop.mdx | 31 + src/fragments/lib-legacy/utilities/cache.mdx | 156 +++++ src/fragments/lib-legacy/utilities/i18n.mdx | 50 ++ .../utilities/ios/hub/10_listen_start.mdx | 1 + .../utilities/ios/hub/20_listen_stop.mdx | 26 + src/fragments/lib-legacy/utilities/js/hub.mdx | 203 ++++++ src/fragments/lib-legacy/utilities/logger.mdx | 62 ++ .../utilities/native_common/hub/common.mdx | 31 + .../lib-legacy/utilities/serviceworker.mdx | 233 +++++++ .../lib-legacy/xr/getting-started.mdx | 107 +++ src/fragments/lib-legacy/xr/sceneapi.mdx | 75 +++ src/fragments/lib/android.mdx | 10 - .../escapehatch/10_awsmobileclient_escape.mdx | 6 - .../escapehatch/10_awsmobileclient_escape.mdx | 6 - .../android/getting_started/10_intro.mdx | 7 - .../android/getting_started/20_goal.mdx | 1 - .../android/getting_started/30_preReq.mdx | 3 - .../android/getting_started/50_nextSteps.mdx | 18 - .../ios/getting_started/10_intro.mdx | 7 - .../ios/getting_started/20_goal.mdx | 1 - .../ios/getting_started/30_preReq.mdx | 9 - .../ios/getting_started/40_installLib.mdx | 37 -- .../ios/getting_started/50_nextSteps.mdx | 18 - .../native_common/getting_started/common.mdx | 47 -- .../lib/graphqlapi/ios/query-data.mdx | 6 - src/fragments/lib/ios.mdx | 9 - src/fragments/lib/storage/ios/escapehatch.mdx | 6 - .../autotrack/q/platform/[platform].mdx | 20 + .../escapehatch/q/platform/[platform].mdx | 12 + .../q/platform/[platform].mdx | 16 + .../getting-started/q/platform/[platform].mdx | 20 + .../identifyuser/q/platform/[platform].mdx | 16 + .../personalize/q/platform/[platform].mdx | 8 + .../record/q/platform/[platform].mdx | 20 + .../storing/q/platform/[platform].mdx | 8 + .../streaming/q/platform/[platform].mdx | 8 + .../q/platform/[platform].mdx | 16 + .../auth/advanced/q/platform/[platform].mdx | 8 + .../auth-events/q/platform/[platform].mdx | 22 + .../auth/customui/q/platform/[platform].mdx | 8 + .../delete_user/q/platform/[platform].mdx | 20 + .../device_features/q/platform/[platform].mdx | 20 + .../emailpassword/q/platform/[platform].mdx | 8 + .../escapehatch/q/platform/[platform].mdx | 12 + .../q/platform/[platform].mdx | 16 + .../getting-started/q/platform/[platform].mdx | 20 + .../guest_access/q/platform/[platform].mdx | 16 + .../manageusers/q/platform/[platform].mdx | 8 + .../q/platform/[platform].mdx | 10 + .../auth/mfa/q/platform/[platform].mdx | 8 + .../auth/overview/q/platform/[platform].mdx | 16 + .../q/platform/[platform].mdx | 16 + .../auth/signOut/q/platform/[platform].mdx | 16 + .../auth/signin/q/platform/[platform].mdx | 16 + .../q/platform/[platform].mdx | 8 + .../signin_web_ui/q/platform/[platform].mdx | 16 + .../q/platform/[platform].mdx | 12 + .../auth/sms_flows/q/platform/[platform].mdx | 8 + .../auth/social/q/platform/[platform].mdx | 20 + .../auth/start/q/platform/[platform].mdx | 8 + .../switch-auth/q/platform/[platform].mdx | 8 + .../user-attributes/q/platform/[platform].mdx | 16 + .../q/platform/[platform].mdx | 8 + .../auth-model/q/platform/[platform].mdx | 16 + .../conflict/q/platform/[platform].mdx | 20 + .../data-access/q/platform/[platform].mdx | 20 + .../q/platform/[platform].mdx | 124 ++++ .../examples/q/platform/[platform].mdx | 8 + .../getting-started/q/platform/[platform].mdx | 20 + .../how-it-works/q/platform/[platform].mdx | 20 + .../other-methods/q/platform/[platform].mdx | 20 + .../real-time/q/platform/[platform].mdx | 21 + .../relational/q/platform/[platform].mdx | 24 + .../schema-updates/q/platform/[platform].mdx | 20 + .../q/platform/[platform].mdx | 20 + .../datastore/sync/q/platform/[platform].mdx | 20 + .../dev-menu/q/platform/[platform].mdx | 12 + .../geo/escapehatch/q/platform/[platform].mdx | 16 + .../q/platform/[platform].mdx | 16 + .../geo/geofences/q/platform/[platform].mdx | 8 + .../getting-started/q/platform/[platform].mdx | 16 + .../q/platform/[platform].mdx | 8 + .../geo/maps/q/platform/[platform].mdx | 16 + .../geo/search/q/platform/[platform].mdx | 16 + .../q/platform/[platform].mdx | 24 + .../authz/q/platform/[platform].mdx | 20 + .../cancel-request/q/platform/[platform].mdx | 8 + .../concepts/q/platform/[platform].mdx | 20 + .../q/platform/[platform].mdx | 8 + .../q/platform/[platform].mdx | 16 + .../getting-started/q/platform/[platform].mdx | 20 + .../q/platform/[platform].mdx | 8 + .../mutate-data/q/platform/[platform].mdx | 20 + .../offline/q/platform/[platform].mdx | 8 + .../query-data/q/platform/[platform].mdx | 20 + .../subscribe-data/q/platform/[platform].mdx | 20 + .../clear-messages/q/platform/[platform].mdx | 8 + .../create-campaign/q/platform/[platform].mdx | 8 + .../customize/q/platform/[platform].mdx | 8 + .../display-message/q/platform/[platform].mdx | 8 + .../getting-started/q/platform/[platform].mdx | 8 + .../identify-user/q/platform/[platform].mdx | 8 + .../overview/q/platform/[platform].mdx | 8 + .../prerequisites/q/platform/[platform].mdx | 8 + .../q/platform/[platform].mdx | 8 + .../q/platform/[platform].mdx | 8 + .../sync-messages/q/platform/[platform].mdx | 8 + .../app-uninstall/q/platform/[platform].mdx | 8 + .../info/overview/q/platform/[platform].mdx | 12 + .../chatbot/q/platform/[platform].mdx | 8 + .../getting-started/q/platform/[platform].mdx | 8 + .../escapehatch/q/platform/[platform].mdx | 12 + .../getting-started/q/platform/[platform].mdx | 16 + .../identify-entity/q/platform/[platform].mdx | 16 + .../identify-text/q/platform/[platform].mdx | 16 + .../interpret/q/platform/[platform].mdx | 16 + .../intro/q/platform/[platform].mdx | 8 + .../label-image/q/platform/[platform].mdx | 16 + .../sample/q/platform/[platform].mdx | 8 + .../text-speech/q/platform/[platform].mdx | 16 + .../transcribe/q/platform/[platform].mdx | 12 + .../translate/q/platform/[platform].mdx | 16 + .../async/q/platform/[platform].mdx | 8 + .../combine/q/platform/[platform].mdx | 8 + .../coroutines/q/platform/[platform].mdx | 8 + .../q/platform/[platform].mdx | 16 + .../escape-hatch/q/platform/[platform].mdx | 9 + .../null-safety/q/platform/[platform].mdx | 9 + .../platform-setup/q/platform/[platform].mdx | 8 + .../prereq/q/platform/[platform].mdx | 40 ++ .../rxjava/q/platform/[platform].mdx | 8 + .../q/platform/[platform].mdx | 12 + .../getting-started/q/platform/[platform].mdx | 8 + .../pubsub/publish/q/platform/[platform].mdx | 8 + .../pubsub/subunsub/q/platform/[platform].mdx | 8 + .../getting-started/q/platform/[platform].mdx | 10 + .../overview/q/platform/[platform].mdx | 10 + .../testing/q/platform/[platform].mdx | 10 + .../q/platform/[platform].mdx | 10 + .../lib-legacy/q/platform/[platform].mdx | 24 + .../restapi/authz/q/platform/[platform].mdx | 20 + .../restapi/cancel/q/platform/[platform].mdx | 8 + .../restapi/delete/q/platform/[platform].mdx | 20 + .../q/platform/[platform].mdx | 16 + .../restapi/fetch/q/platform/[platform].mdx | 20 + .../getting-started/q/platform/[platform].mdx | 20 + .../restapi/update/q/platform/[platform].mdx | 20 + .../lib-legacy/ssr/q/platform/[platform].mdx | 8 + .../autotrack/q/platform/[platform].mdx | 8 + .../cancel-requests/q/platform/[platform].mdx | 8 + .../configureaccess/q/platform/[platform].mdx | 20 + .../storage/copy/q/platform/[platform].mdx | 8 + .../download/q/platform/[platform].mdx | 20 + .../escapehatch/q/platform/[platform].mdx | 12 + .../q/platform/[platform].mdx | 16 + .../getting-started/q/platform/[platform].mdx | 20 + .../storage/list/q/platform/[platform].mdx | 20 + .../overview/q/platform/[platform].mdx | 20 + .../storage/remove/q/platform/[platform].mdx | 20 + .../triggers/q/platform/[platform].mdx | 8 + .../storage/upload/q/platform/[platform].mdx | 20 + .../strict-mode/q/platform/[platform].mdx | 8 + .../upgrading/q/platform/[platform].mdx | 12 + .../utilities/cache/q/platform/[platform].mdx | 8 + .../utilities/hub/q/platform/[platform].mdx | 16 + .../utilities/i18n/q/platform/[platform].mdx | 8 + .../logger/q/platform/[platform].mdx | 8 + .../serviceworker/q/platform/[platform].mdx | 8 + .../getting-started/q/platform/[platform].mdx | 8 + .../xr/sceneapi/q/platform/[platform].mdx | 8 + .../getting-started/q/platform/[platform].mdx | 12 - 878 files changed, 45767 insertions(+), 221 deletions(-) create mode 100644 src/fragments/lib-legacy/analytics/android/escapehatch.mdx create mode 100644 src/fragments/lib-legacy/analytics/android/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/analytics/android/getting-started/12_amplifyConfig.mdx create mode 100644 src/fragments/lib-legacy/analytics/android/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/analytics/android/getting-started/30_initAnalytics.mdx create mode 100644 src/fragments/lib-legacy/analytics/android/getting-started/40_record.mdx create mode 100644 src/fragments/lib-legacy/analytics/android/identifyuser.mdx create mode 100644 src/fragments/lib-legacy/analytics/android/record.mdx create mode 100644 src/fragments/lib-legacy/analytics/autotrack.mdx create mode 100644 src/fragments/lib-legacy/analytics/existing-resources.mdx create mode 100644 src/fragments/lib-legacy/analytics/flutter/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/analytics/flutter/getting-started/12_amplifyConfig.mdx create mode 100644 src/fragments/lib-legacy/analytics/flutter/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/analytics/flutter/getting-started/30_initAnalytics.mdx create mode 100644 src/fragments/lib-legacy/analytics/flutter/getting-started/40_record.mdx create mode 100644 src/fragments/lib-legacy/analytics/flutter/identifyuser.mdx create mode 100644 src/fragments/lib-legacy/analytics/flutter/record.mdx create mode 100644 src/fragments/lib-legacy/analytics/ios/escapehatch.mdx create mode 100644 src/fragments/lib-legacy/analytics/ios/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/analytics/ios/getting-started/12_amplifyConfig.mdx create mode 100644 src/fragments/lib-legacy/analytics/ios/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/analytics/ios/getting-started/30_initAnalytics.mdx create mode 100644 src/fragments/lib-legacy/analytics/ios/getting-started/40_record.mdx create mode 100644 src/fragments/lib-legacy/analytics/ios/identifyuser.mdx create mode 100644 src/fragments/lib-legacy/analytics/ios/record.mdx create mode 100644 src/fragments/lib-legacy/analytics/js/autotrack.mdx create mode 100644 src/fragments/lib-legacy/analytics/js/getting-started.mdx create mode 100644 src/fragments/lib-legacy/analytics/js/personalize.mdx create mode 100644 src/fragments/lib-legacy/analytics/js/record.mdx create mode 100644 src/fragments/lib-legacy/analytics/js/storing.mdx create mode 100644 src/fragments/lib-legacy/analytics/js/streaming.mdx create mode 100644 src/fragments/lib-legacy/analytics/native_common/getting-started/common.mdx create mode 100644 src/fragments/lib-legacy/android-sdk.mdx create mode 100644 src/fragments/lib-legacy/android.mdx create mode 100644 src/fragments/lib-legacy/auth/android/access_credentials/10_fetchAuthSession.mdx create mode 100644 src/fragments/lib-legacy/auth/android/common_prereq.mdx create mode 100644 src/fragments/lib-legacy/auth/android/delete_user/10_delete_user.mdx create mode 100644 src/fragments/lib-legacy/auth/android/device_features/10_rememberDevice.mdx create mode 100644 src/fragments/lib-legacy/auth/android/device_features/20_forgetDevice.mdx create mode 100644 src/fragments/lib-legacy/auth/android/device_features/30_fetchDevice.mdx create mode 100644 src/fragments/lib-legacy/auth/android/escapehatch/10_awsmobileclient_escape.mdx create mode 100644 src/fragments/lib-legacy/auth/android/getting_started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/auth/android/getting_started/12_amplifyConfig.mdx create mode 100644 src/fragments/lib-legacy/auth/android/getting_started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/auth/android/getting_started/30_initAuth.mdx create mode 100644 src/fragments/lib-legacy/auth/android/getting_started/40_fetchSession.mdx create mode 100644 src/fragments/lib-legacy/auth/android/hub_events/10_listen_events.mdx create mode 100644 src/fragments/lib-legacy/auth/android/password_management/10_reset_password.mdx create mode 100644 src/fragments/lib-legacy/auth/android/password_management/20_confirm_reset_password.mdx create mode 100644 src/fragments/lib-legacy/auth/android/password_management/30_change_password.mdx create mode 100644 src/fragments/lib-legacy/auth/android/signin/10_signUp.mdx create mode 100644 src/fragments/lib-legacy/auth/android/signin/20_confirmSignUp.mdx create mode 100644 src/fragments/lib-legacy/auth/android/signin/30_signIn.mdx create mode 100644 src/fragments/lib-legacy/auth/android/signin/40_multi_factor_signup.mdx create mode 100644 src/fragments/lib-legacy/auth/android/signin/50_multi_factor_confirm_signin.mdx create mode 100644 src/fragments/lib-legacy/auth/android/signin_web_ui/10_cli_setup.mdx create mode 100644 src/fragments/lib-legacy/auth/android/signin_web_ui/20_platform_specific_setup.mdx create mode 100644 src/fragments/lib-legacy/auth/android/signin_web_ui/30_signin.mdx create mode 100644 src/fragments/lib-legacy/auth/android/signout/10_local_signout.mdx create mode 100644 src/fragments/lib-legacy/auth/android/signout/20_global_signout.mdx create mode 100644 src/fragments/lib-legacy/auth/android/social_signin_web_ui/10_cli_setup.mdx create mode 100644 src/fragments/lib-legacy/auth/android/social_signin_web_ui/20_signin.mdx create mode 100644 src/fragments/lib-legacy/auth/android/user_attributes/10_fetch_attributes.mdx create mode 100644 src/fragments/lib-legacy/auth/android/user_attributes/20_update_user_attribute.mdx create mode 100644 src/fragments/lib-legacy/auth/android/user_attributes/30_confirm_attribute.mdx create mode 100644 src/fragments/lib-legacy/auth/android/user_attributes/40_resend_code.mdx create mode 100644 src/fragments/lib-legacy/auth/common/device_features/common.mdx create mode 100644 src/fragments/lib-legacy/auth/common/overview.mdx create mode 100644 src/fragments/lib-legacy/auth/common/sms/add_mfa.mdx create mode 100644 src/fragments/lib-legacy/auth/common/sms/add_username.mdx create mode 100644 src/fragments/lib-legacy/auth/common/sms/add_verification.mdx create mode 100644 src/fragments/lib-legacy/auth/common/sms/flows.mdx create mode 100644 src/fragments/lib-legacy/auth/common/sms/update_mfa.mdx create mode 100644 src/fragments/lib-legacy/auth/common/sms/update_username.mdx create mode 100644 src/fragments/lib-legacy/auth/common/sms/update_verification.mdx create mode 100644 src/fragments/lib-legacy/auth/common/social_signin_web_ui/configure_auth_category.mdx create mode 100644 src/fragments/lib-legacy/auth/common/social_signin_web_ui/setup_auth_provider.mdx create mode 100644 src/fragments/lib-legacy/auth/existing-resources.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/access_credentials/10_fetchAuthSession.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/common_prereq.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/delete_user/10_delete_user.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/device_features/10_rememberDevice.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/device_features/20_forgetDevice.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/device_features/30_fetchDevice.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/existing_resources/10_existingResources.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/getting_started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/getting_started/12_amplifyConfig.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/getting_started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/getting_started/30_initAuth.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/getting_started/40_authenticator.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/getting_started/50_fetchSession.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/getting_started/60_developerPreview.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/hub_events/10_listen_events.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/managing_credentials/10_managing_credentials.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/password_management/10_reset_password.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/password_management/20_confirm_reset_password.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/password_management/30_change_password.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin/10_signUp.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin/20_confirmSignUp.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin/30_signIn.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin/40_multi_factor_signup.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin/50_multi_factor_confirm_signin.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin/60_runtime_auth_flow.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin_web_ui/10_cli_setup.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin_web_ui/20_platform_specific_setup.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin_web_ui/30_signin.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin_web_ui/40_private_session.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin_with_custom_flow/10_cli_setup.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin_with_custom_flow/20_signup.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin_with_custom_flow/30_signin.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin_with_custom_flow/40_custom_challenge.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signin_with_custom_flow/50_custom_challenge_password_verification.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signout/10_local_signout.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/signout/20_global_signout.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/sms/confirm_sign_in.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/sms/confirm_sign_up.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/sms/sign_in.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/sms/sign_up.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/social_signin_web_ui/10_cli_setup.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/social_signin_web_ui/20_signin.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/user_attributes/10_fetch_attributes.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/user_attributes/20_update_user_attribute.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/user_attributes/30_confirm_attribute.mdx create mode 100644 src/fragments/lib-legacy/auth/flutter/user_attributes/40_resend_code.mdx create mode 100644 src/fragments/lib-legacy/auth/getting_started/js/getting-started-steps-basic-auth.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/access_credentials/10_fetchAuthSession.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/delete_user/10_delete_user.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/device_features/10_rememberDevice.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/device_features/20_forgetDevice.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/device_features/30_fetchDevice.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/escapehatch/10_awsmobileclient_escape.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/getting_started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/getting_started/12_amplifyConfig.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/getting_started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/getting_started/30_initAuth.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/getting_started/40_fetchSession.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/hub_events/10_listen_events.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/password_management/10_reset_password.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/password_management/20_confirm_reset_password.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/password_management/30_change_password.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin/10_signUp.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin/20_confirmSignUp.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin/30_signIn.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin/40_multi_factor_signup.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin/50_multi_factor_confirm_signin.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_next_steps/10_signin.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_next_steps/20_confirm_sms_mfa.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_next_steps/30_confirm_custom_challenge.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_next_steps/40_confirm_new_password.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_next_steps/50_reset_password.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_next_steps/60_confirm_signup.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_next_steps/70_done.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_web_ui/10_cli_setup.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_web_ui/20_platform_specific_setup.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_web_ui/30_signin.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_web_ui/40_private_session.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_with_custom_flow/10_cli_setup.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_with_custom_flow/20_signup.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_with_custom_flow/30_confirmSignup.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_with_custom_flow/40_signin.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signin_with_custom_flow/50_custom_challenge.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signout/10_local_signout.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/signout/20_global_signout.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/social_signin_web_ui/10_cli_setup.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/social_signin_web_ui/20_signin.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/user_attributes/10_fetch_attributes.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/user_attributes/20_update_user_attribute.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/user_attributes/30_confirm_attribute.mdx create mode 100644 src/fragments/lib-legacy/auth/ios/user_attributes/40_resend_code.mdx create mode 100644 src/fragments/lib-legacy/auth/js/advanced.mdx create mode 100644 src/fragments/lib-legacy/auth/js/customui.mdx create mode 100644 src/fragments/lib-legacy/auth/js/delete_user.mdx create mode 100644 src/fragments/lib-legacy/auth/js/device_features/10_rememberDevice.mdx create mode 100644 src/fragments/lib-legacy/auth/js/device_features/20_forgetDevice.mdx create mode 100644 src/fragments/lib-legacy/auth/js/device_features/30_fetchDevice.mdx create mode 100644 src/fragments/lib-legacy/auth/js/emailpassword.mdx create mode 100644 src/fragments/lib-legacy/auth/js/getting-started-steps-basic-auth.mdx create mode 100644 src/fragments/lib-legacy/auth/js/getting-started.mdx create mode 100644 src/fragments/lib-legacy/auth/js/hub_events/10_listen_events.mdx create mode 100644 src/fragments/lib-legacy/auth/js/manageusers.mdx create mode 100644 src/fragments/lib-legacy/auth/js/mfa.mdx create mode 100644 src/fragments/lib-legacy/auth/js/overview.mdx create mode 100644 src/fragments/lib-legacy/auth/js/react-native-withoauth.mdx create mode 100644 src/fragments/lib-legacy/auth/js/social.mdx create mode 100644 src/fragments/lib-legacy/auth/js/start.mdx create mode 100644 src/fragments/lib-legacy/auth/js/switch-auth.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/access_credentials/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/delete_user/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/device_features/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/escape_hatch/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/existing_resources/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/getting_started/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/guest_access/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/password_management/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/signin/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/signin_next_steps/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/signin_web_ui/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/signin_with_custom_flow/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/signout/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/social_signin_web_ui/common.mdx create mode 100644 src/fragments/lib-legacy/auth/native_common/user_attributes/common.mdx create mode 100644 src/fragments/lib-legacy/client-configuration/js/js-configuration.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/conflict.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/data-access/delete-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/data-access/observe-update-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/data-access/query-basic-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/data-access/query-pagination-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/data-access/query-predicate-multiple-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/data-access/query-predicate-or-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/data-access/query-predicate-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/data-access/query-sort-multiple-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/data-access/query-sort-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/data-access/save-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/data-access/update-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/datastore-events.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/datastore-events/outbox-mutation-failed.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/getting-started/40_codegen.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/getting-started/50_codegenCli.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/getting-started/60_initDataStore.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/getting-started/70_saveSnippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/getting-started/80_querySnippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/other-methods/10_clear.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/other-methods/15_start.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/other-methods/20_start.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/other-methods/30_stop.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/real-time/observe-query-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/real-time/observe-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/relational/delete-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/relational/query-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/relational/save-many-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/relational/save-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/relational/updated-schema.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/setup-auth-rules/10_multiauth-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/setup-auth-rules/owner_based_auth_oidc.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/sync/10-installPlugin.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/sync/20-savePredicate.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/sync/30-savePredicateComparison.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/sync/40-clear.mdx create mode 100644 src/fragments/lib-legacy/datastore/android/sync/50-selectiveSync.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/conflict.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/data-access/delete-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/data-access/observe-update-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/data-access/query-basic-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/data-access/query-pagination-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/data-access/query-predicate-multiple-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/data-access/query-predicate-or-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/data-access/query-predicate-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/data-access/query-sort-multiple-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/data-access/query-sort-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/data-access/save-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/data-access/update-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/datastore-events.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/getting-started/30_platformIntegration.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/getting-started/40_codegen.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/getting-started/50_codegenCli.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/getting-started/50_initDataStore.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/getting-started/60_initDataStore.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/getting-started/60_saveSnippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/getting-started/70_querySnippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/getting-started/80_saveSnippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/other-methods/10_clear.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/other-methods/15_start.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/other-methods/20_start.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/other-methods/30_stop.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/real-time/observe-query-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/real-time/observe-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/relational/delete-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/relational/query-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/relational/save-many-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/relational/save-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/relational/updated-schema.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/setup-auth-rules/10_multiauth-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/setup-auth-rules/owner_based_auth_oidc.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/sync/10-installPlugin.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/sync/20-savePredicate.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/sync/30-savePredicateComparison.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/sync/40-clear.mdx create mode 100644 src/fragments/lib-legacy/datastore/flutter/sync/50-selectiveSync.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/auth-model.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/conflict.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/data-access/delete-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/data-access/observe-update-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/data-access/query-basic-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/data-access/query-pagination-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/data-access/query-predicate-multiple-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/data-access/query-predicate-or-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/data-access/query-predicate-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/data-access/query-sort-multiple-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/data-access/query-sort-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/data-access/save-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/data-access/update-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/datastore-events.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/getting-started/30_setupEnv.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/getting-started/40_codegen.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/getting-started/50_initDataStore.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/getting-started/60_saveSnippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/getting-started/70_querySnippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/other-methods/10_clear.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/other-methods/15_start.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/other-methods/20_start.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/other-methods/30_stop.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/real-time/observe-query-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/real-time/observe-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/relational/delete-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/relational/query-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/relational/save-many-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/relational/save-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/relational/updated-schema.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/setup-auth-rules/10_multiauth-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/setup-auth-rules/owner_based_auth_oidc.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/sync/10-installPlugin.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/sync/20-savePredicate.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/sync/30-savePredicateComparison.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/sync/40-clear.mdx create mode 100644 src/fragments/lib-legacy/datastore/ios/sync/50-selectiveSync.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/conflict.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/delete-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/importing-datastore-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/observe-update-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/query-basic-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/query-pagination-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/query-predicate-multiple-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/query-predicate-or-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/query-predicate-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/query-single-item-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/query-sort-multiple-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/query-sort-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/save-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/data-access/update-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/datastore-events.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/datastore-events/model-synced.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/datastore-events/outbox-mutation-enqueued.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/datastore-events/outbox-mutation-processed.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/examples.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/getting-started/30_platformIntegration.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/getting-started/40_codegen.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/getting-started/50_initDataStore.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/getting-started/60_saveSnippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/getting-started/70_querySnippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/other-methods.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/real-time/observe-query-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/real-time/observe-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/relational/delete-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/relational/query-many-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/relational/query-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/relational/save-many-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/relational/save-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/relational/updated-schema.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/setup-auth-rules/10_multiauth-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/setup-auth-rules/20_function-auth-snippet.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/setup-auth-rules/multi-auth.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/sync/20-savePredicate.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/sync/30-savePredicateComparison.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/sync/40-clear.mdx create mode 100644 src/fragments/lib-legacy/datastore/js/sync/50-selectiveSync.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/callout/datastore-clear-with-auth.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/codegen.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/conflict.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/data-access.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/datastore-events/model-synced.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/datastore-events/outbox-mutation-enqueued.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/datastore-events/outbox-mutation-processed.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/getting-started.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/how-it-works.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/other-methods.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/real-time.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/relational.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/schema-updates.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/setup-auth-rules.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/setup-env-cli.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/setup-env.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/sort.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/sync-distributed-data.mdx create mode 100644 src/fragments/lib-legacy/datastore/native_common/sync.mdx create mode 100644 src/fragments/lib-legacy/debugging/android/dev-menu/setup.mdx create mode 100644 src/fragments/lib-legacy/debugging/android/dev-menu/usage.mdx create mode 100644 src/fragments/lib-legacy/debugging/ios/dev-menu/setup.mdx create mode 100644 src/fragments/lib-legacy/debugging/ios/dev-menu/usage.mdx create mode 100644 src/fragments/lib-legacy/debugging/native_common/dev-menu/common.mdx create mode 100644 src/fragments/lib-legacy/flutter.mdx create mode 100644 src/fragments/lib-legacy/geo/android/dev-preview-callout.mdx create mode 100644 src/fragments/lib-legacy/geo/android/escapehatch.mdx create mode 100644 src/fragments/lib-legacy/geo/android/existing-resources.mdx create mode 100644 src/fragments/lib-legacy/geo/android/getting_started/10_pre_req.mdx create mode 100644 src/fragments/lib-legacy/geo/android/getting_started/20_cli_resources.mdx create mode 100644 src/fragments/lib-legacy/geo/android/getting_started/30_install_lib.mdx create mode 100644 src/fragments/lib-legacy/geo/android/getting_started/40_init_geo.mdx create mode 100644 src/fragments/lib-legacy/geo/android/maps/10_install_adapter.mdx create mode 100644 src/fragments/lib-legacy/geo/android/maps/20_display_map.mdx create mode 100644 src/fragments/lib-legacy/geo/android/maps/30_styles.mdx create mode 100644 src/fragments/lib-legacy/geo/android/search/10_search_by_text.mdx create mode 100644 src/fragments/lib-legacy/geo/android/search/20_search_by_coordinates.mdx create mode 100644 src/fragments/lib-legacy/geo/android/search/30_location_search_map.mdx create mode 100644 src/fragments/lib-legacy/geo/existing-resources.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/dev-preview-callout.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/escapehatch.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/existing-resources.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/getting_started/10_pre_req.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/getting_started/20_cli_resources.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/getting_started/30_install_lib.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/getting_started/40_init_geo.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/maps/10_install_adapter.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/maps/20_display_map.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/maps/30_styles.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/search/10_search_by_text.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/search/20_search_by_coordinates.mdx create mode 100644 src/fragments/lib-legacy/geo/ios/search/30_location_search_map.mdx create mode 100644 src/fragments/lib-legacy/geo/js/escapehatch.mdx create mode 100644 src/fragments/lib-legacy/geo/js/existing-resources.mdx create mode 100644 src/fragments/lib-legacy/geo/js/geofences.mdx create mode 100644 src/fragments/lib-legacy/geo/js/getting-started.mdx create mode 100644 src/fragments/lib-legacy/geo/js/google-migration.mdx create mode 100644 src/fragments/lib-legacy/geo/js/maps.mdx create mode 100644 src/fragments/lib-legacy/geo/js/search.mdx create mode 100644 src/fragments/lib-legacy/geo/native_common/getting_started/common.mdx create mode 100644 src/fragments/lib-legacy/geo/native_common/maps/common.mdx create mode 100644 src/fragments/lib-legacy/geo/native_common/search/common.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/10_example.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/20_custom.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/30_nested.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/40_multiple.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/50_interceptor.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/authz/10_userpool.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/authz/20_oidc.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/authz/21_oidc.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/authz/22_lambda.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/authz/30_multi.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/getting-started.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/getting-started/12_amplifyConfig.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/getting-started/30_initapi.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/getting-started/40_codegen.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/getting-started/50_createtodo.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/mutate-data.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/query-data.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/android/subscribe-data.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/existing-resources.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/10_example.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/20_custom.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/30_nested.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/40_multiple.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/authz.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/authz/10_userpool.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/authz/20_oidc.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/authz/21_oidc.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/authz/22_lambda.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/authz/2X_add_plugin.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/authz/30_multi.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/getting-started/12_amplifyConfig.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/getting-started/30_initapi.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/getting-started/40_codegen.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/getting-started/50_createtodo.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/mutate-data.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/query-data.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/flutter/subscribe-data.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/graphql-from-node.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/10_example.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/20_custom.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/30_nested.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/40_multiple.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/50_interceptor.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/authz/10_userpool.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/authz/20_oidc.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/authz/21_oidc.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/authz/22_lambda.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/authz/30_multi.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/getting-started.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/getting-started/12_amplifyConfig.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/getting-started/30_initapi.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/getting-started/40_codegen.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/getting-started/50_createtodo.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/mutate-data.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/query-data.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/ios/subscribe-data.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/js/authz.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/js/cancel-request.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/js/complex-objects.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/js/create-or-re-use-existing-backend.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/js/delta-sync.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/js/getting-started.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/js/mutate-data.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/js/offline.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/js/query-data.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/js/subscribe-data.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/native_common/advanced-workflows/common.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/native_common/authz/common.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/native_common/concepts.mdx create mode 100644 src/fragments/lib-legacy/graphqlapi/native_common/getting-started/common.mdx create mode 100644 src/fragments/lib-legacy/in-app-messaging/js/clear-messages.mdx create mode 100644 src/fragments/lib-legacy/in-app-messaging/js/create-campaign.mdx create mode 100644 src/fragments/lib-legacy/in-app-messaging/js/customize.mdx create mode 100644 src/fragments/lib-legacy/in-app-messaging/js/display-message.mdx create mode 100644 src/fragments/lib-legacy/in-app-messaging/js/getting-started.mdx create mode 100644 src/fragments/lib-legacy/in-app-messaging/js/identify-user.mdx create mode 100644 src/fragments/lib-legacy/in-app-messaging/js/overview.mdx create mode 100644 src/fragments/lib-legacy/in-app-messaging/js/prerequisites.mdx create mode 100644 src/fragments/lib-legacy/in-app-messaging/js/resolve-conflicts.mdx create mode 100644 src/fragments/lib-legacy/in-app-messaging/js/respond-interaction-events.mdx create mode 100644 src/fragments/lib-legacy/in-app-messaging/js/sync-messages.mdx create mode 100644 src/fragments/lib-legacy/info/ios/app-uninstall.mdx create mode 100644 src/fragments/lib-legacy/info/ios/data-information.mdx create mode 100644 src/fragments/lib-legacy/info/native_common/app-uninstall/common.mdx create mode 100644 src/fragments/lib-legacy/info/native_common/data-information/common.mdx create mode 100644 src/fragments/lib-legacy/interactions/js/chatbot.mdx create mode 100644 src/fragments/lib-legacy/interactions/js/getting-started.mdx create mode 100644 src/fragments/lib-legacy/ios-sdk.mdx create mode 100644 src/fragments/lib-legacy/ios-spm.mdx create mode 100644 src/fragments/lib-legacy/ios.mdx create mode 100644 src/fragments/lib-legacy/js.mdx create mode 100644 src/fragments/lib-legacy/predictions/android/escapehatch.mdx create mode 100644 src/fragments/lib-legacy/predictions/android/getting-started/20_preReq.mdx create mode 100644 src/fragments/lib-legacy/predictions/android/getting-started/30_installLib.mdx create mode 100644 src/fragments/lib-legacy/predictions/android/getting-started/40_init.mdx create mode 100644 src/fragments/lib-legacy/predictions/android/getting-started/50_translate.mdx create mode 100644 src/fragments/lib-legacy/predictions/android/getting-started/60_nextSteps.mdx create mode 100644 src/fragments/lib-legacy/predictions/android/identify-entity.mdx create mode 100644 src/fragments/lib-legacy/predictions/android/identify-text.mdx create mode 100644 src/fragments/lib-legacy/predictions/android/interpret.mdx create mode 100644 src/fragments/lib-legacy/predictions/android/label-image.mdx create mode 100644 src/fragments/lib-legacy/predictions/android/text-speech.mdx create mode 100644 src/fragments/lib-legacy/predictions/android/translate.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/escapehatch.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/getting-started/10_coreml.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/getting-started/20_preReq.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/getting-started/30_installLib.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/getting-started/40_init.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/getting-started/50_translate.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/getting-started/60_nextSteps.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/identify-entity.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/identify-text.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/interpret.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/label-image.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/sample.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/text-speech.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/transcribe.mdx create mode 100644 src/fragments/lib-legacy/predictions/ios/translate.mdx create mode 100644 src/fragments/lib-legacy/predictions/js/getting-started.mdx create mode 100644 src/fragments/lib-legacy/predictions/js/identify-entity.mdx create mode 100644 src/fragments/lib-legacy/predictions/js/identify-text.mdx create mode 100644 src/fragments/lib-legacy/predictions/js/interpret.mdx create mode 100644 src/fragments/lib-legacy/predictions/js/intro.mdx create mode 100644 src/fragments/lib-legacy/predictions/js/label-image.mdx create mode 100644 src/fragments/lib-legacy/predictions/js/sample.mdx create mode 100644 src/fragments/lib-legacy/predictions/js/text-speech.mdx create mode 100644 src/fragments/lib-legacy/predictions/js/transcribe.mdx create mode 100644 src/fragments/lib-legacy/predictions/js/translate.mdx create mode 100644 src/fragments/lib-legacy/predictions/native_common/getting-started/common.mdx create mode 100644 src/fragments/lib-legacy/project-setup/android/async/async.mdx create mode 100644 src/fragments/lib-legacy/project-setup/android/coroutines/coroutines.mdx create mode 100644 src/fragments/lib-legacy/project-setup/android/create-application/10_createProject.mdx rename src/fragments/{lib/devpreview/android/getting_started/40_installLib.mdx => lib-legacy/project-setup/android/create-application/20_gradle.mdx} (80%) create mode 100644 src/fragments/lib-legacy/project-setup/android/create-application/30_provisionBackend.mdx create mode 100644 src/fragments/lib-legacy/project-setup/android/create-application/40_verifyAmplifyLibraries.mdx create mode 100644 src/fragments/lib-legacy/project-setup/android/prereq/prereq.mdx create mode 100644 src/fragments/lib-legacy/project-setup/android/rxjava/rxjava.mdx create mode 100644 src/fragments/lib-legacy/project-setup/android/use-existing-resources/use-existing-resources.mdx create mode 100644 src/fragments/lib-legacy/project-setup/flutter/create-application/10_createProject.mdx create mode 100644 src/fragments/lib-legacy/project-setup/flutter/create-application/20_pubspec.mdx create mode 100644 src/fragments/lib-legacy/project-setup/flutter/create-application/30_provisionBackend.mdx create mode 100644 src/fragments/lib-legacy/project-setup/flutter/create-application/40_verifyAmplifyLibraries.mdx create mode 100644 src/fragments/lib-legacy/project-setup/flutter/create-application/50_nextSteps.mdx create mode 100644 src/fragments/lib-legacy/project-setup/flutter/create-application/60_dependencies.mdx create mode 100644 src/fragments/lib-legacy/project-setup/flutter/escape-hatch/escape-hatch.mdx create mode 100644 src/fragments/lib-legacy/project-setup/flutter/null-safety/null-safety.mdx create mode 100644 src/fragments/lib-legacy/project-setup/flutter/platform-setup/platform-setup.mdx create mode 100644 src/fragments/lib-legacy/project-setup/flutter/prereq/cliInstall.mdx create mode 100644 src/fragments/lib-legacy/project-setup/flutter/prereq/prereq.mdx create mode 100644 src/fragments/lib-legacy/project-setup/ios/combine/combine.mdx create mode 100644 src/fragments/lib-legacy/project-setup/ios/create-application/10_createProject.mdx create mode 100644 src/fragments/lib-legacy/project-setup/ios/create-application/20_install_libraries.mdx create mode 100644 src/fragments/lib-legacy/project-setup/ios/create-application/30_provisionBackend.mdx create mode 100644 src/fragments/lib-legacy/project-setup/ios/create-application/31_provisionBackend.mdx create mode 100644 src/fragments/lib-legacy/project-setup/ios/create-application/40_verifyAmplifyLibraries.mdx create mode 100644 src/fragments/lib-legacy/project-setup/ios/prereq/prereq.mdx create mode 100644 src/fragments/lib-legacy/project-setup/ios/use-existing-resources/use-existing-resources.mdx create mode 100644 src/fragments/lib-legacy/project-setup/native_common/create-application/50_nextSteps.mdx create mode 100644 src/fragments/lib-legacy/project-setup/native_common/create-application/common.mdx create mode 100644 src/fragments/lib-legacy/project-setup/native_common/prereq/cliInstall.mdx create mode 100644 src/fragments/lib-legacy/project-setup/native_common/prereq/common_body.mdx create mode 100644 src/fragments/lib-legacy/project-setup/native_common/prereq/common_header.mdx create mode 100644 src/fragments/lib-legacy/project-setup/native_common/prereq/flutter_null_safety.mdx create mode 100644 src/fragments/lib-legacy/pubsub/js/connection-states.mdx create mode 100644 src/fragments/lib-legacy/pubsub/js/getting-started.mdx create mode 100644 src/fragments/lib-legacy/pubsub/js/publish.mdx create mode 100644 src/fragments/lib-legacy/pubsub/js/subunsub.mdx create mode 100644 src/fragments/lib-legacy/push-notifications/js/getting-started.mdx create mode 100644 src/fragments/lib-legacy/push-notifications/js/overview.mdx create mode 100644 src/fragments/lib-legacy/push-notifications/js/reactnative.mdx create mode 100644 src/fragments/lib-legacy/push-notifications/js/testing.mdx create mode 100644 src/fragments/lib-legacy/push-notifications/js/working-with-api.mdx create mode 100644 src/fragments/lib-legacy/restapi/android/authz/22_none_headers.mdx create mode 100644 src/fragments/lib-legacy/restapi/android/delete.mdx create mode 100644 src/fragments/lib-legacy/restapi/android/fetch.mdx create mode 100644 src/fragments/lib-legacy/restapi/android/getting-started.mdx create mode 100644 src/fragments/lib-legacy/restapi/android/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/restapi/android/getting-started/11_amplifyInit.mdx create mode 100644 src/fragments/lib-legacy/restapi/android/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/restapi/android/getting-started/30_initapi.mdx create mode 100644 src/fragments/lib-legacy/restapi/android/getting-started/40_postTodo.mdx create mode 100644 src/fragments/lib-legacy/restapi/android/update.mdx create mode 100644 src/fragments/lib-legacy/restapi/existing-resources.mdx create mode 100644 src/fragments/lib-legacy/restapi/flutter/authz.mdx create mode 100644 src/fragments/lib-legacy/restapi/flutter/delete.mdx create mode 100644 src/fragments/lib-legacy/restapi/flutter/fetch.mdx create mode 100644 src/fragments/lib-legacy/restapi/flutter/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/restapi/flutter/getting-started/11_amplifyInit.mdx create mode 100644 src/fragments/lib-legacy/restapi/flutter/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/restapi/flutter/getting-started/30_initapi.mdx create mode 100644 src/fragments/lib-legacy/restapi/flutter/getting-started/40_postTodo.mdx create mode 100644 src/fragments/lib-legacy/restapi/flutter/update.mdx create mode 100644 src/fragments/lib-legacy/restapi/ios/authz/22_none_headers.mdx create mode 100644 src/fragments/lib-legacy/restapi/ios/delete.mdx create mode 100644 src/fragments/lib-legacy/restapi/ios/fetch.mdx create mode 100644 src/fragments/lib-legacy/restapi/ios/getting-started.mdx create mode 100644 src/fragments/lib-legacy/restapi/ios/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/restapi/ios/getting-started/11_amplifyInit.mdx create mode 100644 src/fragments/lib-legacy/restapi/ios/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/restapi/ios/getting-started/30_initapi.mdx create mode 100644 src/fragments/lib-legacy/restapi/ios/getting-started/40_postTodo.mdx create mode 100644 src/fragments/lib-legacy/restapi/ios/update.mdx create mode 100644 src/fragments/lib-legacy/restapi/js/authz.mdx create mode 100644 src/fragments/lib-legacy/restapi/js/cancel.mdx create mode 100644 src/fragments/lib-legacy/restapi/js/delete.mdx create mode 100644 src/fragments/lib-legacy/restapi/js/fetch.mdx create mode 100644 src/fragments/lib-legacy/restapi/js/getting-started.mdx create mode 100644 src/fragments/lib-legacy/restapi/js/update.mdx create mode 100644 src/fragments/lib-legacy/restapi/native_common/authz/common.mdx create mode 100644 src/fragments/lib-legacy/restapi/native_common/getting-started/common.mdx create mode 100644 src/fragments/lib-legacy/ssr/js/getting-started.mdx create mode 100644 src/fragments/lib-legacy/storage/android/configureaccess.mdx create mode 100644 src/fragments/lib-legacy/storage/android/configureaccess/10_protected_upload.mdx create mode 100644 src/fragments/lib-legacy/storage/android/configureaccess/20_protected_download.mdx create mode 100644 src/fragments/lib-legacy/storage/android/configureaccess/30_private_upload.mdx create mode 100644 src/fragments/lib-legacy/storage/android/configureaccess/40_private_download.mdx create mode 100644 src/fragments/lib-legacy/storage/android/configureaccess/50_customization.mdx create mode 100644 src/fragments/lib-legacy/storage/android/download.mdx create mode 100644 src/fragments/lib-legacy/storage/android/escapehatch.mdx create mode 100644 src/fragments/lib-legacy/storage/android/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/storage/android/getting-started/12_amplifyConfig.mdx create mode 100644 src/fragments/lib-legacy/storage/android/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/storage/android/getting-started/30_initStorage.mdx create mode 100644 src/fragments/lib-legacy/storage/android/getting-started/40_upload.mdx create mode 100644 src/fragments/lib-legacy/storage/android/list.mdx create mode 100644 src/fragments/lib-legacy/storage/android/remove.mdx create mode 100644 src/fragments/lib-legacy/storage/android/upload.mdx create mode 100644 src/fragments/lib-legacy/storage/existing-resources.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/configureaccess/10_protected_upload.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/configureaccess/20_protected_download.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/configureaccess/30_private_upload.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/configureaccess/40_private_download.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/download.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/getting-started/12_amplifyConfig.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/getting-started/30_initStorage.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/getting-started/40_upload.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/list.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/path-provider.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/remove.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/upload.mdx create mode 100644 src/fragments/lib-legacy/storage/flutter/upload/upload-create-file.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/configureaccess/10_protected_upload.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/configureaccess/20_protected_download.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/configureaccess/30_private_upload.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/configureaccess/40_private_download.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/configureaccess/50_customization.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/download.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/escapehatch.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/getting-started/10_preReq.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/getting-started/12_amplifyConfig.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/getting-started/20_installLib.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/getting-started/30_initStorage.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/getting-started/40_upload.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/list.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/remove.mdx create mode 100644 src/fragments/lib-legacy/storage/ios/upload.mdx create mode 100644 src/fragments/lib-legacy/storage/js/autotrack.mdx create mode 100644 src/fragments/lib-legacy/storage/js/cancel-requests.mdx create mode 100644 src/fragments/lib-legacy/storage/js/configureaccess.mdx create mode 100644 src/fragments/lib-legacy/storage/js/copy.mdx create mode 100644 src/fragments/lib-legacy/storage/js/download.mdx create mode 100644 src/fragments/lib-legacy/storage/js/getting-started.mdx create mode 100644 src/fragments/lib-legacy/storage/js/list.mdx create mode 100644 src/fragments/lib-legacy/storage/js/remove.mdx create mode 100644 src/fragments/lib-legacy/storage/js/upload.mdx create mode 100644 src/fragments/lib-legacy/storage/native_common/configureaccess/common.mdx create mode 100644 src/fragments/lib-legacy/storage/native_common/getting-started/common.mdx create mode 100644 src/fragments/lib-legacy/troubleshooting/flutter/upgrading.mdx create mode 100644 src/fragments/lib-legacy/troubleshooting/js/strict-mode.mdx create mode 100644 src/fragments/lib-legacy/troubleshooting/js/upgrading.mdx create mode 100644 src/fragments/lib-legacy/utilities/android/hub/10_listen_start.mdx create mode 100644 src/fragments/lib-legacy/utilities/android/hub/20_listen_stop.mdx create mode 100644 src/fragments/lib-legacy/utilities/cache.mdx create mode 100644 src/fragments/lib-legacy/utilities/i18n.mdx create mode 100644 src/fragments/lib-legacy/utilities/ios/hub/10_listen_start.mdx create mode 100644 src/fragments/lib-legacy/utilities/ios/hub/20_listen_stop.mdx create mode 100644 src/fragments/lib-legacy/utilities/js/hub.mdx create mode 100644 src/fragments/lib-legacy/utilities/logger.mdx create mode 100644 src/fragments/lib-legacy/utilities/native_common/hub/common.mdx create mode 100644 src/fragments/lib-legacy/utilities/serviceworker.mdx create mode 100644 src/fragments/lib-legacy/xr/getting-started.mdx create mode 100644 src/fragments/lib-legacy/xr/sceneapi.mdx delete mode 100644 src/fragments/lib/devpreview/android/getting_started/10_intro.mdx delete mode 100644 src/fragments/lib/devpreview/android/getting_started/20_goal.mdx delete mode 100644 src/fragments/lib/devpreview/android/getting_started/30_preReq.mdx delete mode 100644 src/fragments/lib/devpreview/android/getting_started/50_nextSteps.mdx delete mode 100644 src/fragments/lib/devpreview/ios/getting_started/10_intro.mdx delete mode 100644 src/fragments/lib/devpreview/ios/getting_started/20_goal.mdx delete mode 100644 src/fragments/lib/devpreview/ios/getting_started/30_preReq.mdx delete mode 100644 src/fragments/lib/devpreview/ios/getting_started/40_installLib.mdx delete mode 100644 src/fragments/lib/devpreview/ios/getting_started/50_nextSteps.mdx delete mode 100644 src/fragments/lib/devpreview/native_common/getting_started/common.mdx create mode 100644 src/pages/lib-legacy/analytics/autotrack/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/analytics/escapehatch/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/analytics/existing-resources/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/analytics/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/analytics/identifyuser/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/analytics/personalize/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/analytics/record/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/analytics/storing/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/analytics/streaming/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/access_credentials/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/advanced/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/auth-events/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/customui/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/delete_user/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/device_features/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/emailpassword/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/escapehatch/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/existing-resources/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/guest_access/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/manageusers/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/managing_credentials/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/mfa/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/overview/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/password_management/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/signOut/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/signin/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/signin_next_steps/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/signin_web_ui/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/signin_with_custom_flow/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/sms_flows/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/social/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/start/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/switch-auth/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/auth/user-attributes/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/client-configuration/configuring-amplify-categories/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/auth-model/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/conflict/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/data-access/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/datastore-events/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/examples/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/how-it-works/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/other-methods/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/real-time/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/relational/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/schema-updates/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/setup-auth-rules/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/datastore/sync/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/debugging/dev-menu/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/geo/escapehatch/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/geo/existing-resources/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/geo/geofences/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/geo/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/geo/google-migration/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/geo/maps/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/geo/search/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/graphqlapi/advanced-workflows/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/graphqlapi/authz/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/graphqlapi/cancel-request/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/graphqlapi/concepts/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/graphqlapi/create-or-re-use-existing-backend/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/graphqlapi/existing-resources/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/graphqlapi/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/graphqlapi/graphql-from-nodejs/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/graphqlapi/mutate-data/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/graphqlapi/offline/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/graphqlapi/query-data/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/graphqlapi/subscribe-data/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/in-app-messaging/clear-messages/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/in-app-messaging/create-campaign/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/in-app-messaging/customize/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/in-app-messaging/display-message/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/in-app-messaging/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/in-app-messaging/identify-user/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/in-app-messaging/overview/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/in-app-messaging/prerequisites/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/in-app-messaging/resolve-conflicts/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/in-app-messaging/respond-interaction-events/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/in-app-messaging/sync-messages/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/info/app-uninstall/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/info/overview/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/interactions/chatbot/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/interactions/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/predictions/escapehatch/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/predictions/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/predictions/identify-entity/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/predictions/identify-text/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/predictions/interpret/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/predictions/intro/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/predictions/label-image/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/predictions/sample/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/predictions/text-speech/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/predictions/transcribe/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/predictions/translate/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/project-setup/async/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/project-setup/combine/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/project-setup/coroutines/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/project-setup/create-application/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/project-setup/escape-hatch/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/project-setup/null-safety/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/project-setup/platform-setup/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/project-setup/prereq/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/project-setup/rxjava/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/project-setup/use-existing-resources/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/pubsub/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/pubsub/publish/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/pubsub/subunsub/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/push-notifications/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/push-notifications/overview/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/push-notifications/testing/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/push-notifications/working-with-api/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/restapi/authz/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/restapi/cancel/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/restapi/delete/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/restapi/existing-resources/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/restapi/fetch/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/restapi/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/restapi/update/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/ssr/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/autotrack/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/cancel-requests/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/configureaccess/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/copy/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/download/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/escapehatch/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/existing-resources/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/list/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/overview/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/remove/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/triggers/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/storage/upload/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/troubleshooting/strict-mode/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/troubleshooting/upgrading/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/utilities/cache/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/utilities/hub/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/utilities/i18n/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/utilities/logger/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/utilities/serviceworker/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/xr/getting-started/q/platform/[platform].mdx create mode 100644 src/pages/lib-legacy/xr/sceneapi/q/platform/[platform].mdx delete mode 100644 src/pages/lib/devpreview/getting-started/q/platform/[platform].mdx diff --git a/src/components/Menu/VersionSwitcher/index.tsx b/src/components/Menu/VersionSwitcher/index.tsx index 8dd6ccbae97..4d3b056f115 100644 --- a/src/components/Menu/VersionSwitcher/index.tsx +++ b/src/components/Menu/VersionSwitcher/index.tsx @@ -39,7 +39,7 @@ const Option = function({href, title, isActive}) { ); }; -export default function VersionSwitcher({url}) { +export function VersionSwitcher({url}) { let leftActive = true; let urlEnd; const filter = url.includes("/framework") @@ -81,3 +81,71 @@ export default function VersionSwitcher({url}) { ); } + + +const lib = directory["lib"].items; +const libLegacy = directory["lib-legacy"].items; +const libLegacyPaths = []; +const libPaths = []; +const libItemsAndPaths: [object, string[]][] = [ + [lib, libPaths], + [libLegacy, libLegacyPaths], +]; +for (const [dirItems, paths] of libItemsAndPaths) { + for (const [_, value] of Object.entries(dirItems)) { + const {items} = value; + items.forEach((item) => { + const {route, filters} = item; + filters.forEach((filter) => { + const path = route + "/q/framework/" + filter + "/"; + paths.push(path); + }); + paths.push(route); + }); + } +} +libLegacyPaths.push("/lib-legacy"); +libPaths.push("/lib"); + +export function LibVersionSwitcher({url}) { + let leftActive = true; + let urlEnd; + const filter = url.includes("/framework") + ? "q/framework" + url.split("/framework")[1] + : ""; + if (url.includes("/lib-legacy")) { + leftActive = false; + urlEnd = url.split("/lib-legacy")[1]; + } else { + urlEnd = url.split("/lib")[1]; + } + + const leftHref = "/lib" + urlEnd; + const leftOption = { + title: "Latest", + href: libPaths.includes(leftHref) ? leftHref : "/lib/" + filter, + }; + + const rightHref = "/lib-legacy" + urlEnd; + const rightOption = { + title: "Legacy", + href: libLegacyPaths.includes(rightHref) + ? rightHref + : "/lib-legacy/" + filter, + }; + + return ( + + + ); +} diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx index 1fcf231044d..bcd090b3f45 100644 --- a/src/components/Menu/index.tsx +++ b/src/components/Menu/index.tsx @@ -14,7 +14,7 @@ import ExternalLink from "../ExternalLink"; import {DISCORD} from "../../constants/img"; import RepoActions from "./RepoActions"; import FilterSelect from "./FilterSelect"; -import VersionSwitcher from "./VersionSwitcher"; +import { VersionSwitcher, LibVersionSwitcher } from "./VersionSwitcher"; type MenuProps = { filters: string[]; @@ -67,13 +67,12 @@ export default class Menu extends React.Component { render() { let showVersionSwitcher = false; - if ( - (this.props.url.startsWith("/ui") || - this.props.url.startsWith("/ui-legacy")) && - this.props.filterKey !== "react-native" && - this.props.filterKey !== "flutter" + let showLibVersionSwitcher = false + if ((this.props.url.startsWith("/ui") || this.props.url.startsWith("/ui-legacy")) && this.props.filterKey !== "react-native" && this.props.filterKey !== "flutter" ) { showVersionSwitcher = true; + } else if ((this.props.url.startsWith("/lib") || this.props.url.startsWith("/lib-legacy")) && this.props.filterKey == 'ios') { + showLibVersionSwitcher = true; } if (this.state.isOpen) { return ( @@ -95,6 +94,9 @@ export default class Menu extends React.Component { {showVersionSwitcher && ( )} + {showLibVersionSwitcher && ( + + )} + + +```java +AWSPinpointAnalyticsPlugin plugin = (AWSPinpointAnalyticsPlugin) + Amplify.Analytics.getPlugin("awsPinpointAnalyticsPlugin"); +AnalyticsClient analyticsClient = plugin.getEscapeHatch(); +``` + + + + +```kotlin +val plugin = Amplify.Analytics.getPlugin("awsPinpointAnalyticsPlugin") +val analyticsClient = (plugin as AWSPinpointAnalyticsPlugin).escapeHatch +``` + + + + +```java +AWSPinpointAnalyticsPlugin plugin = (AWSPinpointAnalyticsPlugin) + RxAmplify.Analytics.getPlugin("awsPinpointAnalyticsPlugin"); +AnalyticsClient analyticsClient = plugin.getEscapeHatch(); +``` + + + diff --git a/src/fragments/lib-legacy/analytics/android/getting-started/10_preReq.mdx b/src/fragments/lib-legacy/analytics/android/getting-started/10_preReq.mdx new file mode 100644 index 00000000000..e8afed152d1 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/android/getting-started/10_preReq.mdx @@ -0,0 +1,3 @@ +* [Install and configure Amplify CLI](https://docs.amplify.aws/cli/start/install) +* An Android application targeting Android API level 16 (Android 4.1) or above + * For a full example of creating Android project, please follow the [project setup walkthrough](/lib/project-setup/create-application) diff --git a/src/fragments/lib-legacy/analytics/android/getting-started/12_amplifyConfig.mdx b/src/fragments/lib-legacy/analytics/android/getting-started/12_amplifyConfig.mdx new file mode 100644 index 00000000000..373ec0b90c1 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/android/getting-started/12_amplifyConfig.mdx @@ -0,0 +1 @@ +Upon completion, `amplifyconfiguration.json` should be updated to reference provisioned backend analytics resources. Note that these files should already be a part of your project if you followed the [Project setup walkthrough](/lib/project-setup/create-application). diff --git a/src/fragments/lib-legacy/analytics/android/getting-started/20_installLib.mdx b/src/fragments/lib-legacy/analytics/android/getting-started/20_installLib.mdx new file mode 100644 index 00000000000..25db19763bd --- /dev/null +++ b/src/fragments/lib-legacy/analytics/android/getting-started/20_installLib.mdx @@ -0,0 +1,13 @@ +Expand **Gradle Scripts**, open **build.gradle (Module: app)**. You will already have configured Amplify by following the steps in the Project Setup walkthrough. + +Add Analytics by adding these libraries into the dependencies block: + +```groovy +dependencies { + // Add these lines in `dependencies` + implementation 'com.amplifyframework:aws-analytics-pinpoint:ANDROID_VERSION' + implementation 'com.amplifyframework:aws-auth-cognito:ANDROID_VERSION' +} +``` + +Click **Sync Now**. diff --git a/src/fragments/lib-legacy/analytics/android/getting-started/30_initAnalytics.mdx b/src/fragments/lib-legacy/analytics/android/getting-started/30_initAnalytics.mdx new file mode 100644 index 00000000000..0858bf5de4c --- /dev/null +++ b/src/fragments/lib-legacy/analytics/android/getting-started/30_initAnalytics.mdx @@ -0,0 +1,95 @@ +To initialize the Amplify Auth and Analytics categories you call `Amplify.addPlugin()` method for each category. To complete initialization call `Amplify.configure()`. + +Add the following code to your `onCreate()` method in your application class: + + + + +```java +Amplify.addPlugin(new AWSCognitoAuthPlugin()); +Amplify.addPlugin(new AWSPinpointAnalyticsPlugin(this)); +``` + +Your class will look like this: + +```java +public class MyAmplifyApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + try { + // Add these lines to add the AWSCognitoAuthPlugin and AWSPinpointAnalyticsPlugin plugins + Amplify.addPlugin(new AWSCognitoAuthPlugin()); + Amplify.addPlugin(new AWSPinpointAnalyticsPlugin(this)); + Amplify.configure(getApplicationContext()); + + Log.i("MyAmplifyApp", "Initialized Amplify"); + } catch (AmplifyException error) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error); + } + } +} +``` + + + + +```kotlin +Amplify.addPlugin(AWSCognitoAuthPlugin()) +Amplify.addPlugin(AWSPinpointAnalyticsPlugin(this)) +``` + +Your class will look like this: + +```kotlin +class MyAmplifyApp : Application() { + override fun onCreate() { + super.onCreate() + + try { + // Add these lines to add the AWSCognitoAuthPlugin and AWSPinpointAnalyticsPlugin plugins + Amplify.addPlugin(AWSCognitoAuthPlugin()) + Amplify.addPlugin(AWSPinpointAnalyticsPlugin(this)) + Amplify.configure(applicationContext) + + Log.i("MyAmplifyApp", "Initialized Amplify") + } catch (error: AmplifyException) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error) + } + } +} +``` + + + + +```java +RxAmplify.addPlugin(new AWSCognitoAuthPlugin()); +RxAmplify.addPlugin(new AWSPinpointAnalyticsPlugin(this)); +``` + +Your class will look like this: + +```java +public class MyAmplifyApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + try { + // Add these lines to add the AWSCognitoAuthPlugin and AWSPinpointAnalyticsPlugin plugins + RxAmplify.addPlugin(new AWSCognitoAuthPlugin()); + RxAmplify.addPlugin(new AWSPinpointAnalyticsPlugin(this)); + RxAmplify.configure(getApplicationContext()); + + Log.i("MyAmplifyApp", "Initialized Amplify"); + } catch (AmplifyException error) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error); + } + } +} +``` + + + diff --git a/src/fragments/lib-legacy/analytics/android/getting-started/40_record.mdx b/src/fragments/lib-legacy/analytics/android/getting-started/40_record.mdx new file mode 100644 index 00000000000..36797cecc61 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/android/getting-started/40_record.mdx @@ -0,0 +1,49 @@ +To record an event, create an `AnalyticsEvent` and call `Amplify.Analytics.recordEvent()` to send it: + + + + +```java +AnalyticsEvent event = AnalyticsEvent.builder() + .name("PasswordReset") + .addProperty("Channel", "SMS") + .addProperty("Successful", true) + .addProperty("ProcessDuration", 792) + .addProperty("UserAge", 120.3) + .build(); + +Amplify.Analytics.recordEvent(event); +``` + + + + +```kotlin +val event = AnalyticsEvent.builder() + .name("PasswordReset") + .addProperty("Channel", "SMS") + .addProperty("Successful", true) + .addProperty("ProcessDuration", 792) + .addProperty("UserAge", 120.3) + .build() + +Amplify.Analytics.recordEvent(event) +``` + + + + +```java +AnalyticsEvent event = AnalyticsEvent.builder() + .name("PasswordReset") + .addProperty("Channel", "SMS") + .addProperty("Successful", true) + .addProperty("ProcessDuration", 792) + .addProperty("UserAge", 120.3) + .build(); + +RxAmplify.Analytics.recordEvent(event); +``` + + + diff --git a/src/fragments/lib-legacy/analytics/android/identifyuser.mdx b/src/fragments/lib-legacy/analytics/android/identifyuser.mdx new file mode 100644 index 00000000000..62ec4083cd8 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/android/identifyuser.mdx @@ -0,0 +1,115 @@ +This call sends information that you have specified about the user to Amazon Pinpoint. This could be for an unauthenticated or an authenticated user. + +In addition, `customProperties` and `userAttributes` can also be provided when invoking `identifyUser`. The Amazon Pinpoint console makes that data available as part of the criteria for segment creation. Attributes passed in via `customProperties` will appear under **Custom Endpoint Attributes**, while `userAttributes` will appear under **Custom User Attributes**. See the [Pinpoint documentation](https://docs.aws.amazon.com/pinpoint/latest/userguide/segments-building.html#choosecriteria) for more information on segment creation. + +You can get the current user's ID from the Amplify Auth category as shown below. Be sure you have it added and setup per the [Auth category documentation](/lib/auth/getting-started). + +If you have asked for location access and received permission, you can also provide that in `UserProfile.Location`. + + + + +```java +UserProfile.Location location = UserProfile.Location.builder() + .latitude(47.606209) + .longitude(-122.332069) + .postalCode("98122") + .city("Seattle") + .region("WA") + .country("USA") + .build(); + +AnalyticsProperties customProperties = AnalyticsProperties.builder() + .add("property1", "Property value") + .build(); + +AnalyticsProperties userAttributes = AnalyticsProperties.builder() + .add("someUserAttribute", "User attribute value") + .build(); + +AWSPinpointUserProfile profile = AWSPinpointUserProfile.builder() + .name("test-user") + .email("user@test.com") + .plan("test-plan") + .location(location) + .customProperties(customProperties) + .userAttributes(userAttributes) + .build(); + +String userId = Amplify.Auth.getCurrentUser().getUserId(); + +Amplify.Analytics.identifyUser(userId, profile); +``` + + + + +```kotlin +val location = UserProfile.Location.builder() + .latitude(47.606209) + .longitude(-122.332069) + .postalCode("98122") + .city("Seattle") + .region("WA") + .country("USA") + .build(); + +val customProperties = AnalyticsProperties.builder() + .add("property1", "Property value") + .build(); + +val userAttributes = AnalyticsProperties.builder() + .add("someUserAttribute", "User attribute value") + .build(); + +val profile = AWSPinpointUserProfile.builder() + .name("test-user") + .email("user@test.com") + .plan("test-plan") + .location(location) + .customProperties(customProperties) + .userAttributes(userAttributes) + .build(); + +val userId = Amplify.Auth.getCurrentUser().getUserId(); + +Amplify.Analytics.identifyUser(userId, profile); +``` + + + + +```java +UserProfile.Location location = UserProfile.Location.builder() + .latitude(47.606209) + .longitude(-122.332069) + .postalCode("98122") + .city("Seattle") + .region("WA") + .country("USA") + .build(); + +AnalyticsProperties customProperties = AnalyticsProperties.builder() + .add("property1", "Property value") + .build(); + +AnalyticsProperties userAttributes = AnalyticsProperties.builder() + .add("someUserAttribute", "User attribute value") + .build(); + +AWSPinpointUserProfile profile = AWSPinpointUserProfile.builder() + .name("test-user") + .email("user@test.com") + .plan("test-plan") + .location(location) + .customProperties(customProperties) + .userAttributes(userAttributes) + .build(); + +String userId = RxAmplify.Auth.getCurrentUser().getUserId(); + +RxAmplify.Analytics.identifyUser(userId, profile); +``` + + + diff --git a/src/fragments/lib-legacy/analytics/android/record.mdx b/src/fragments/lib-legacy/analytics/android/record.mdx new file mode 100644 index 00000000000..9306f3b8824 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/android/record.mdx @@ -0,0 +1,222 @@ +## Record event + +The Amplify analytics plugin also makes it easy to record custom events within the app. The plugin handles retry logic in the event the device loses network connectivity and automatically batches requests to reduce network bandwidth. + + + + +```java +AnalyticsEvent event = AnalyticsEvent.builder() + .name("PasswordReset") + .addProperty("Channel", "SMS") + .addProperty("Successful", true) + .addProperty("ProcessDuration", 792) + .addProperty("UserAge", 120.3) + .build(); + +Amplify.Analytics.recordEvent(event); +``` + + + + +```kotlin +val event = AnalyticsEvent.builder() + .name("PasswordReset") + .addProperty("Channel", "SMS") + .addProperty("Successful", true) + .addProperty("ProcessDuration", 792) + .addProperty("UserAge", 120.3) + .build() + +Amplify.Analytics.recordEvent(event) +``` + + + + +```java +AnalyticsEvent event = AnalyticsEvent.builder() + .name("PasswordReset") + .addProperty("Channel", "SMS") + .addProperty("Successful", true) + .addProperty("ProcessDuration", 792) + .addProperty("UserAge", 120.3) + .build(); + +RxAmplify.Analytics.recordEvent(event); +``` + + + + +## Flush events + +Events have default configuration to flush out to the network every 30 seconds. If you would like to change this, update `amplifyconfiguration.json` with the value in milliseconds you would like for `autoFlushEventsInterval`. This configuration will flush events every 10 seconds: + +```json +{ + "UserAgent": "aws-amplify-cli/2.0", + "Version": "1.0", + "analytics": { + "plugins": { + "awsPinpointAnalyticsPlugin": { + "pinpointAnalytics": { + "appId": "AppID", + "region": "Region" + }, + "pinpointTargeting": { + "region": "Region" + }, + "autoFlushEventsInterval": 10000 + } + } + } +} +``` + +To manually flush events, call: + + + + +```java +Amplify.Analytics.flushEvents(); +``` + + + + +```kotlin +Amplify.Analytics.flushEvents() +``` + + + + +```java +RxAmplify.Analytics.flushEvents(); +``` + + + + +## Global Properties + +You can register global properties which will be sent along with all invocations of `Amplify.Analytics.recordEvent`. + + + + +```java +Amplify.Analytics.registerGlobalProperties( + AnalyticsProperties.builder() + .add("AppStyle", "DarkMode") + .build()); +``` + + + + +```kotlin +Amplify.Analytics.registerGlobalProperties( + AnalyticsProperties.builder() + .add("AppStyle", "DarkMode") + .build()) +``` + + + + +```java +RxAmplify.Analytics.registerGlobalProperties( + AnalyticsProperties.builder() + .add("AppStyle", "DarkMode") + .build()); +``` + + + + +To unregister a global property, call `Amplify.Analytics.unregisterGlobalProperties()`: + + + + +```java +Amplify.Analytics.unregisterGlobalProperties("AppStyle", "OtherProperty"); +``` + + + + +```kotlin +Amplify.Analytics.unregisterGlobalProperties("AppStyle", "OtherProperty") +``` + + + + +```java +RxAmplify.Analytics.unregisterGlobalProperties("AppStyle", "OtherProperty"); +``` + + + + +## Disable Analytics + +To disable analytics, call: + + + + +```java +Amplify.Analytics.disable(); +``` + + + + +```kotlin +Amplify.Analytics.disable() +``` + + + + +```java +RxAmplify.Analytics.disable(); +``` + + + + +## Enable Analytics + +To re-enable, call: + + + + +```java +Amplify.Analytics.enable(); +``` + + + + +```kotlin +Amplify.Analytics.enable() +``` + + + + +```java +RxAmplify.Analytics.enable(); +``` + + + + diff --git a/src/fragments/lib-legacy/analytics/autotrack.mdx b/src/fragments/lib-legacy/analytics/autotrack.mdx new file mode 100644 index 00000000000..f3a6d6c0cad --- /dev/null +++ b/src/fragments/lib-legacy/analytics/autotrack.mdx @@ -0,0 +1,10 @@ +The Amplify analytics plugin records when an application opens and closes. This session information can be viewed either from your local computer's terminal or the AWS Console for Pinpoint. + +To view this from the Console: + +```bash +amplify console analytics +``` + +1. On the Pinpoint Console under **Analytics**, choose **Events**. +2. Enable filters, you can select `Session Start` and `Session Stop` events to filter on session events. diff --git a/src/fragments/lib-legacy/analytics/existing-resources.mdx b/src/fragments/lib-legacy/analytics/existing-resources.mdx new file mode 100644 index 00000000000..4a965220e83 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/existing-resources.mdx @@ -0,0 +1,27 @@ +Existing Amazon Pinpoint resources can be used with the Amplify Libraries by referencing your **Application ID** and **Region** in your `amplifyconfiguration.json` file. + +```dart +{ + "analytics": { + "plugins": { + "awsPinpointAnalyticsPlugin": { + "pinpointAnalytics": { + "appId": "[APP ID]", + "region": "[REGION]" + }, + "pinpointTargeting": { + "region": "[REGION]" + } + } + } + } +} +``` + +- **pinpointAnalytics** + - **appId**: Amazon Pinpoint application ID + - **region**: AWS Region where the resources are provisioned (e.g. `us-east-1`) +- **pinpointTargeting** + - **region**: AWS Region where the resources are provisioned (e.g. `us-east-1`) + +Note that before you can add an AWS resource to your application, the application must have the Amplify libraries installed. If you need to perform this step, see [Install Amplify Libraries](/lib/project-setup/create-application#n2-install-amplify-libraries). diff --git a/src/fragments/lib-legacy/analytics/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-legacy/analytics/flutter/getting-started/10_preReq.mdx new file mode 100644 index 00000000000..7829d1f7a1a --- /dev/null +++ b/src/fragments/lib-legacy/analytics/flutter/getting-started/10_preReq.mdx @@ -0,0 +1,5 @@ +* [Install and configure Amplify CLI](https://docs.amplify.aws/cli/start/install) +* A Flutter application targeting Flutter SDK >= 2.10.0 (stable version) with Amplify libraries integrated + * An iOS configuration targeting at least iOS 11.0 + * An Android configuration targeting at least Android API level 21 (Android 5.0) or above + * For a full example please follow the [project setup walkthrough](/lib/project-setup/create-application) diff --git a/src/fragments/lib-legacy/analytics/flutter/getting-started/12_amplifyConfig.mdx b/src/fragments/lib-legacy/analytics/flutter/getting-started/12_amplifyConfig.mdx new file mode 100644 index 00000000000..d971d8e1951 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/flutter/getting-started/12_amplifyConfig.mdx @@ -0,0 +1,9 @@ +Upon completion, you can see the result as the following that is going to be providing you a link to reach your analytics data: + +```console +✔ All resources are updated in the cloud + +Pinpoint URL to track events https:///analytics/overview +``` + +In addition, `amplifyconfiguration.dart` will be updated to reference provisioned backend analytics resources. Note that these files should already be a part of your project if you followed the [Project setup walkthrough](/lib/project-setup/create-application). diff --git a/src/fragments/lib-legacy/analytics/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-legacy/analytics/flutter/getting-started/20_installLib.mdx new file mode 100644 index 00000000000..aa83790678b --- /dev/null +++ b/src/fragments/lib-legacy/analytics/flutter/getting-started/20_installLib.mdx @@ -0,0 +1,18 @@ +In your Flutter project directory, open **pubspec.yaml**. + +> You will already have configured Amplify by following the steps in the project setup. + +Add Analytics by adding these libraries into your dependencies block: + +```yaml +environment: + sdk: ">=2.15.0 <3.0.0" + +dependencies: + + # Should already be added during Project Setup walkthrough + amplify_flutter: ^0.6.0 + + # Add these lines in `dependencies` if you have not added it earlier during the Project Setup + amplify_analytics_pinpoint: ^0.6.0 +``` diff --git a/src/fragments/lib-legacy/analytics/flutter/getting-started/30_initAnalytics.mdx b/src/fragments/lib-legacy/analytics/flutter/getting-started/30_initAnalytics.mdx new file mode 100644 index 00000000000..07a75531e16 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/flutter/getting-started/30_initAnalytics.mdx @@ -0,0 +1,72 @@ +Add the Auth and Analytics plugin, along with any other plugins you may have added as described in the **Project Setup** section; + +```dart +Future _configureAmplify() async { + + // Add Pinpoint and Cognito Plugins, or any other plugins you want to use + final analyticsPlugin = AmplifyAnalyticsPinpoint(); + final authPlugin = AmplifyAuthCognito(); + await Amplify.addPlugins([authPlugin, analyticsPlugin]); + ... +} +``` + +Make sure that the amplifyconfiguration.dart file generated in the project setup is included and sent to Amplify.configure: + +```dart +import 'amplifyconfiguration.dart'; + +... + Future _configureAmplify() async { + ... + // Once Plugins are added, configure Amplify + // Note: Amplify can only be configured once. + try { + await Amplify.configure(amplifyconfig); + } on AmplifyAlreadyConfiguredException { + safePrint("Tried to reconfigure Amplify; this can occur when your app restarts on Android."); + } + } +... +``` + +Your class will look like this: + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_analytics_pinpoint/amplify_analytics_pinpoint.dart'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; + +import 'amplifyconfiguration.dart'; + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void initState() { + super.initState(); + _configureAmplify(); + } + + Future _configureAmplify() async { + + // Add any Amplify plugins you want to use + final analyticsPlugin = AmplifyAnalyticsPinpoint(); + final authPlugin = AmplifyAuthCognito(); + // You can use addPlugin if you are going to be adding only one plugin + // await Amplify.addPlugin(authPlugin); + await Amplify.addPlugins([authPlugin, analyticsPlugin]); + + // Once Plugins are added, configure Amplify + // Note: Amplify can only be configured once. + try { + await Amplify.configure(amplifyconfig); + } on AmplifyAlreadyConfiguredException { + safePrint("Tried to reconfigure Amplify; this can occur when your app restarts on Android."); + } + } +} +``` diff --git a/src/fragments/lib-legacy/analytics/flutter/getting-started/40_record.mdx b/src/fragments/lib-legacy/analytics/flutter/getting-started/40_record.mdx new file mode 100644 index 00000000000..89cf72cf12e --- /dev/null +++ b/src/fragments/lib-legacy/analytics/flutter/getting-started/40_record.mdx @@ -0,0 +1,16 @@ +## Record events + +To record an event, create an `AnalyticsEvent` and call `Amplify.Analytics.recordEvent()`: + +```dart +Future trackEventsWithProperties() async { + final event = AnalyticsEvent('test'); + + event.properties.addBoolProperty('boolKey', true); + event.properties.addDoubleProperty('doubleKey', 10.0); + event.properties.addIntProperty('intKey', 10); + event.properties.addStringProperty('stringKey', 'stringValue'); + + await Amplify.Analytics.recordEvent(event: event); +} +``` diff --git a/src/fragments/lib-legacy/analytics/flutter/identifyuser.mdx b/src/fragments/lib-legacy/analytics/flutter/identifyuser.mdx new file mode 100644 index 00000000000..bea1fa995cc --- /dev/null +++ b/src/fragments/lib-legacy/analytics/flutter/identifyuser.mdx @@ -0,0 +1,37 @@ +This call sends information that you have specified about a user to Amazon Pinpoint. This could be for an unauthenticated (guest) or an authenticated user. + +You can get the current user's ID from the Amplify Auth category as shown per the Auth category documentation. Be sure to have it ready before you set it as shown below (Check out the [Authentication Getting Started](/lib/auth/getting-started) guide for detailed explanation). + +If you have asked for location access and received permission, you can also provide that in `AnalyticsUserProfileLocation` + +```dart +Future addAnalyticsWithLocation( + String userId, + String name, + String email, + String phoneNumber, + int age, +) async { + final location = AnalyticsUserProfileLocation() + ..latitude = 47.606209 + ..longitude = -122.332069 + ..postalCode = '98122' + ..city = 'Seattle' + ..region = 'WA' + ..country = 'USA'; + + final properties = AnalyticsProperties(); + properties.addStringProperty('phoneNumber', phoneNumber); + properties.addIntProperty('age', age); + + final userProfile = AnalyticsUserProfile(); + userProfile.name = name; + userProfile.email = email; + userProfile.location = location; + + await Amplify.Analytics.identifyUser( + userId: userId, + userProfile: userProfile, + ); +} +``` diff --git a/src/fragments/lib-legacy/analytics/flutter/record.mdx b/src/fragments/lib-legacy/analytics/flutter/record.mdx new file mode 100644 index 00000000000..17dccdcb498 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/flutter/record.mdx @@ -0,0 +1,89 @@ +## Record event + +The Amplify analytics plugin also makes it easy to record custom events within the app. The plugin handles retry logic in the event the device looses network connectivity and automatically batches requests to reduce network bandwidth. + +```dart +Future recordCustomEvent() async { + final event = AnalyticsEvent('PasswordReset'); + + event.properties + ..addStringProperty('Channel', 'SMS') + ..addBoolProperty('Successful', true); + + // You can also add the properties one by one like the following + event.properties.addIntProperty('ProcessDuration', 792); + event.properties.addDoubleProperty('doubleKey', 120.3); + + await Amplify.Analytics.recordEvent(event: event); +} +``` + +## Flush events + +Events have default configuration to flush out to the network every 30 seconds. If you would like to change this, update `amplifyconfiguration.dart` with the value in milliseconds you would like for `autoFlushEventsInterval`. This configuration will flush events every 10 seconds: + +```json +{ + "UserAgent": "aws-amplify-cli/2.0", + "Version": "1.0", + "analytics": { + "plugins": { + "awsPinpointAnalyticsPlugin": { + "pinpointAnalytics": { + "appId": "AppID", + "region": "Region" + }, + "pinpointTargeting": { + "region": "Region" + }, + "autoFlushEventsInterval": 10000 + } + } + } +} +``` + +To manually flush events, call: + +```dart +Amplify.Analytics.flushEvents(); +``` + +## Global Properties + +You can register global properties which will be sent along with all invocations of `Amplify.Analytics.recordEvent`. + +```dart +Future registerGlobalProperties() async { + final properties = AnalyticsProperties(); + properties.addStringProperty('AppStyle', 'DarkMode'); + await Amplify.Analytics.registerGlobalProperties(globalProperties: properties); +} +``` + +To unregister a global property, call `Amplify.Analytics.unregisterGlobalProperties()`: + +```dart +Future unregisterGlobalProperties() async { + await Amplify.Analytics.unregisterGlobalProperties( + propertyNames: ['AppStyle', 'OtherProperty'], + ); +} +``` + +## Disable Analytics + +To disable analytics, call: + +```dart +Amplify.Analytics.disable(); +``` + +## Enable Analytics + +To re-enable, call: + +```dart +Amplify.Analytics.enable(); +``` + diff --git a/src/fragments/lib-legacy/analytics/ios/escapehatch.mdx b/src/fragments/lib-legacy/analytics/ios/escapehatch.mdx new file mode 100644 index 00000000000..af3b311b3c5 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/ios/escapehatch.mdx @@ -0,0 +1,38 @@ +For advanced use cases where Amplify does not provide the functionality, you can retrieve the escape hatch to access the underlying Amazon Pinpoint client. + +Add the following imports: + + + + + +```swift +import AWSPinpointAnalyticsPlugin // Imports the Amplify plugin interface +import AWSPinpoint // Imports the AWSPinpoint client escape hatch +``` + + + + + +```swift +import AmplifyPlugins // Imports the Amplify plugin interface +import AWSPinpoint // Imports the AWSPinpoint client escape hatch +``` + + + + + +Then retrieve the escape hatch with this code: + +```swift +func getEscapeHatch() { + do { + let plugin = try Amplify.Analytics.getPlugin(for: "awsPinpointAnalyticsPlugin") as! AWSPinpointAnalyticsPlugin + let awsPinpoint = plugin.getEscapeHatch() + } catch { + print("Get escape hatch failed with error - \(error)") + } +} +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/analytics/ios/getting-started/10_preReq.mdx b/src/fragments/lib-legacy/analytics/ios/getting-started/10_preReq.mdx new file mode 100644 index 00000000000..4045851d866 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/ios/getting-started/10_preReq.mdx @@ -0,0 +1,3 @@ +* [Install and configure Amplify CLI](https://docs.amplify.aws/cli/start/install) +* An iOS application targeting at least iOS 11.0 with Amplify libraries integrated + * For a full example of creating a iOS Xcode project, please follow [project setup walkthrough](/lib/project-setup/create-application) diff --git a/src/fragments/lib-legacy/analytics/ios/getting-started/12_amplifyConfig.mdx b/src/fragments/lib-legacy/analytics/ios/getting-started/12_amplifyConfig.mdx new file mode 100644 index 00000000000..373ec0b90c1 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/ios/getting-started/12_amplifyConfig.mdx @@ -0,0 +1 @@ +Upon completion, `amplifyconfiguration.json` should be updated to reference provisioned backend analytics resources. Note that these files should already be a part of your project if you followed the [Project setup walkthrough](/lib/project-setup/create-application). diff --git a/src/fragments/lib-legacy/analytics/ios/getting-started/20_installLib.mdx b/src/fragments/lib-legacy/analytics/ios/getting-started/20_installLib.mdx new file mode 100644 index 00000000000..cc2f24a991e --- /dev/null +++ b/src/fragments/lib-legacy/analytics/ios/getting-started/20_installLib.mdx @@ -0,0 +1,40 @@ + + + + +import ios0 from "/src/fragments/lib/ios-spm.mdx"; + + + +3. Lastly, choose **AWSPinpointAnalyticsPlugin**, **AWSCognitoAuthPlugin**, and **Amplify**. Then click **Add Package**. + + + + + +To install the Amplify Analytics and Authentication to your application, **add both "AmplifyPlugins/AWSPinpointAnalyticsPlugin" and "AmplifyPlugins/AWSCognitoAuthPlugin" to your `Podfile`** (Because IAM credential is required to access AWS Pinpoint Service, `"AWSCognitoAuthPlugin"` also needs to be installed). Your `Podfile` should look similar to: + +```bash +target 'MyAmplifyApp' do + use_frameworks! + pod 'Amplify' + pod 'AmplifyPlugins/AWSPinpointAnalyticsPlugin' + pod 'AmplifyPlugins/AWSCognitoAuthPlugin' +end +``` + +To install, download and resolve these pods, **execute the command**: + +```bash +pod install --repo-update +``` + +Now you can **open your project** by opening the `.xcworkspace` file using the following command: + +```bash +xed . +``` + + + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/analytics/ios/getting-started/30_initAnalytics.mdx b/src/fragments/lib-legacy/analytics/ios/getting-started/30_initAnalytics.mdx new file mode 100644 index 00000000000..a5f9325239d --- /dev/null +++ b/src/fragments/lib-legacy/analytics/ios/getting-started/30_initAnalytics.mdx @@ -0,0 +1,96 @@ +To initialize the Amplify Analytics and Authentication categories, we are required to use the `Amplify.add()` method for each category we want. When we are done calling `add()` on each category, we finish configuring Amplify by calling `Amplify.configure()`. + +**Add the following imports** to the top of your `AppDelegate.swift` file: + + + + + +```swift +import Amplify +import AWSPinpointAnalyticsPlugin +``` + + + + + +```swift +import Amplify +import AmplifyPlugins +``` + + + + + +**Add the following code** + + + + + +Add to your AppDelegate's `application:didFinishLaunchingWithOptions` method + +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + do { + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.add(plugin: AWSPinpointAnalyticsPlugin()) + try Amplify.configure() + print("Amplify configured with Auth and Analytics plugins") + } catch { + print("Failed to initialize Amplify with \(error)") + } + + return true +} +``` + + + + + +Create a custom `AppDelegate`, and add to your `application:didFinishLaunchingWithOptions` method +```swift +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + do { + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.add(plugin: AWSPinpointAnalyticsPlugin()) + try Amplify.configure() + print("Amplify configured with Auth and Analytics plugins") + } catch { + print("Failed to initialize Amplify with \(error)") + } + + return true + } +} +``` + +Then in the `App` scene, use `UIApplicationDelegateAdaptor` property wrapper to use your custom `AppDelegate` +```swift +@main +struct MyAmplifyApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} +``` + + + + + +Upon building and running this application you should see the following in your console window: + +```console +Amplify configured with Auth and Analytics plugin +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/analytics/ios/getting-started/40_record.mdx b/src/fragments/lib-legacy/analytics/ios/getting-started/40_record.mdx new file mode 100644 index 00000000000..6c9e438832e --- /dev/null +++ b/src/fragments/lib-legacy/analytics/ios/getting-started/40_record.mdx @@ -0,0 +1,14 @@ +To record an event, specify your event using `BasicAnalyticsEvent` and call `Amplify.Analytics.record()` + +```swift +func recordEvents() { + let properties: AnalyticsProperties = [ + "eventPropertyStringKey": "eventPropertyStringValue", + "eventPropertyIntKey": 123, + "eventPropertyDoubleKey": 12.34, + "eventPropertyBoolKey": true + ] + let event = BasicAnalyticsEvent(name: "eventName", properties: properties) + Amplify.Analytics.record(event: event) +} +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/analytics/ios/identifyuser.mdx b/src/fragments/lib-legacy/analytics/ios/identifyuser.mdx new file mode 100644 index 00000000000..9659262daf4 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/ios/identifyuser.mdx @@ -0,0 +1,28 @@ +This call sends information that you have specified about the user to Amazon Pinpoint. This could be an authenticated user. If the user is signed in through [Amplify.Auth.signIn](/lib/auth/signin), then you can retrieve the current user and use it with Analytics. You can also provide location information in `AnalyticsUserProfile.Location`. + +```swift +func identifyUser() { + + guard let user = Amplify.Auth.getCurrentUser() else { + print("Could not get user, perhaps the user is not signed in") + return + } + + let location = AnalyticsUserProfile.Location(latitude: 47.606209, + longitude: -122.332069, + postalCode: "98122", + city: "Seattle", + region: "WA", + country: "USA") + + let properties: AnalyticsProperties = ["phoneNumber": "+11234567890", "age": 25] + + let userProfile = AnalyticsUserProfile(name: username, + email: "name@example.com", + location: location, + properties: properties) + + Amplify.Analytics.identifyUser(user.userId, withProfile: userProfile) + +} +``` diff --git a/src/fragments/lib-legacy/analytics/ios/record.mdx b/src/fragments/lib-legacy/analytics/ios/record.mdx new file mode 100644 index 00000000000..4d99feb7355 --- /dev/null +++ b/src/fragments/lib-legacy/analytics/ios/record.mdx @@ -0,0 +1,78 @@ +## Record Event + +The Amplify Analytics plugin provides a simple interface to record custom events within your app. The plugin handles retry logic in the event the device looses network connectivity, and automatically batches requests to reduce network bandwidth. + +```swift +func recordEvents() { + let properties: AnalyticsProperties = [ + "eventPropertyStringKey": "eventPropertyStringValue", + "eventPropertyIntKey": 123, + "eventPropertyDoubleKey": 12.34, + "eventPropertyBoolKey": true + ] + let event = BasicAnalyticsEvent(name: "eventName", properties: properties) + Amplify.Analytics.record(event: event) +} +``` + +## Flush Events + +Events have default configuration to flush out to the network every 60 seconds. If you would like to change this, update `amplifyconfiguration.json` with the value you would like for `autoFlushEventsInterval` like so: + +```json +{ + "UserAgent": "aws-amplify-cli/2.0", + "Version": "1.0", + "analytics": { + "plugins": { + "awsPinpointAnalyticsPlugin": { + "pinpointAnalytics": { + "appId": "AppID", + "region": "Region" + }, + "pinpointTargeting": { + "region": "Region" + }, + "autoFlushEventsInterval": 60 + } + } + } +} +``` + +> **Note**: If you set `autoFlushEventsInterval` to 0, you are responsible for calling `Amplify.Analytics.flushEvents()` to submit the recorded events to the backend. + +## Global Properties + +You can register properties which will be used across all `Amplify.Analytics.record(event:)` calls. + +```swift +let globalProperties: AnalyticsProperties = ["globalPropertyKey": "value"] +Amplify.Analytics.registerGlobalProperties(globalProperties) +``` + +To unregister global properties call `Amplify.Analytics.unregisterGlobalProperties()`: + +```swift +// when called with no arguments, it unregisters all global properties +Amplify.Analytics.unregisterGlobalProperties() + +// or you can specify properties to unregister +Amplify.Analytics.unregisterGlobalProperties(["globalPropertyKey1", "globalPropertyKey2"]) +``` + +## Disable Analytics + +Analytics are sent to the backend automatically (i.e. it's enabled by default). To disable it call: + +```swift +Amplify.Analytics.disable() +``` + +## Enable Analytics + +To re-enable it call: + +```swift +Amplify.Analytics.enable() +``` diff --git a/src/fragments/lib-legacy/analytics/js/autotrack.mdx b/src/fragments/lib-legacy/analytics/js/autotrack.mdx new file mode 100644 index 00000000000..ab2a9f7ca2e --- /dev/null +++ b/src/fragments/lib-legacy/analytics/js/autotrack.mdx @@ -0,0 +1,157 @@ +Analytics Auto Tracking helps you to automatically track user behaviors like sessions start/stop, page view change and web events like clicking, mouseover. + +## Session Tracking + +You can track the session both in a web app or a React Native app by using Analytics. A web session can be defined in different ways. To keep it simple we define that the web session is active when the page is not hidden and inactive when the page is hidden. +A session in the React Native app is active when the app is in the foreground and inactive when the app is in the background. + +For example: +```javascript +Analytics.autoTrack('session', { + // REQUIRED, turn on/off the auto tracking + enable: true, + // OPTIONAL, the attributes of the event, you can either pass an object or a function + // which allows you to define dynamic attributes + attributes: { + attr: 'attr' + }, + // when using function + // attributes: () => { + // const attr = somewhere(); + // return { + // myAttr: attr + // } + // }, + // OPTIONAL, the service provider, by default is the Amazon Pinpoint + provider: 'AWSPinpoint' +}); +``` + +When the page is loaded, the Analytics module will send an event to the Amazon Pinpoint Service. +```javascript +{ + eventType: '_session_start', + attributes: { + attr: 'attr' + } +} +``` + +To keep backward compatibility, the auto tracking of the session is enabled by default. You can turn it off by: +```javascript +Analytics.configure({ + // OPTIONAL - Allow recording session events. Default is true. + autoSessionRecord: false, +}); +``` +or +```javascript +Analytics.autoTrack('session', { + enable: false +}); + +// Note: this must be called before Amplify.configure() or Analytics.configure() to cancel the session_start event +``` + +## Page View Tracking + +If you want to track which page/url in your webapp is the most frequently viewed one, you can use this feature. It will automatically send events containing url information when the page is visited. + +To turn it on: +```javascript +Analytics.autoTrack('pageView', { + // REQUIRED, turn on/off the auto tracking + enable: true, + // OPTIONAL, the event name, by default is 'pageView' + eventName: 'pageView', + // OPTIONAL, the attributes of the event, you can either pass an object or a function + // which allows you to define dynamic attributes + attributes: { + attr: 'attr' + }, + // when using function + // attributes: () => { + // const attr = somewhere(); + // return { + // myAttr: attr + // } + // }, + // OPTIONAL, by default is 'multiPageApp' + // you need to change it to 'SPA' if your app is a single-page app like React + type: 'multiPageApp', + // OPTIONAL, the service provider, by default is the Amazon Pinpoint + provider: 'AWSPinpoint', + // OPTIONAL, to get the current page url + getUrl: () => { + // the default function + return window.location.origin + window.location.pathname; + } +}); +``` +Note: This is not supported in React Native. + +## Page Event Tracking + +If you want to track user interactions with elements on the page, you can use this feature. All you need to do is attach the specified selectors to your dom element and turn on the auto tracking. + +To turn it on: +```javascript +Analytics.autoTrack('event', { + // REQUIRED, turn on/off the auto tracking + enable: true, + // OPTIONAL, events you want to track, by default is 'click' + events: ['click'], + // OPTIONAL, the prefix of the selectors, by default is 'data-amplify-analytics-' + // in order to avoid collision with the user agent, according to https://www.w3schools.com/tags/att_global_data.asp + // always put 'data' as the first prefix + selectorPrefix: 'data-amplify-analytics-', + // OPTIONAL, the service provider, by default is the Amazon Pinpoint + provider: 'AWSPinpoint', + // OPTIONAL, the default attributes of the event, you can either pass an object or a function + // which allows you to define dynamic attributes + attributes: { + attr: 'attr' + } + // when using function + // attributes: () => { + // const attr = somewhere(); + // return { + // myAttr: attr + // } + // } +}); +``` + +For example: +```html + + + + ); +} +``` +### Facebook Sign-in (React Native - Expo) + +```javascript +import Expo from 'expo'; +import React from 'react'; +import { Amplify, Auth } from 'aws-amplify'; + +const App = () => { + const signIn = async () => { + const { type, token, expires } = await Expo.Facebook.logInWithReadPermissionsAsync('YOUR_FACEBOOK_APP_ID', { + permissions: ['public_profile'], + }); + if (type === 'success') { + // sign in with federated identity + Auth.federatedSignIn('facebook', { token, expires_at: expires}, { name: 'USER_NAME' }) + .then(credentials => { + console.log('get aws credentials', credentials); + }).catch(e => { + console.log(e); + }); + } + } + + // ... + + return ( + + + + ); +} +``` + +### Retrieve JWT Tokens + +After the federated login, you can retrieve related JWT tokens from the local cache using the *Cache* module: + +#### Browser sample + +```javascript +import { Cache } from 'aws-amplify'; + +// Run this after the sign-in +const federatedInfo = Cache.getItem('federatedInfo'); +const { token } = federatedInfo; +``` + +### React Native sample + +```javascript +import { Cache } from 'aws-amplify'; + +// inside an async function +// Run this after the sign-in +const federatedInfo = await Cache.getItem('federatedInfo'); +const { token } = federatedInfo; +``` + +### Token Refresh + +By default, Amplify will automatically refresh the tokens for Google and Facebook, so that your AWS credentials will be valid at all times. But if you are using another federated provider, you will need to provide your own token refresh method: + + +Note: Automatic token refresh for Google and Facebook is not supported in React Native. Automatic token refresh is supported when used with Cognito User pool. + + +#### JWT Token Refresh sample + +```javascript +import { Auth } from 'aws-amplify'; + +function refreshToken() { + // refresh the token here and get the new token info + // ...... + + return new Promise(res, rej => { + const data = { + token, // the token from the provider + expires_at, // the timestamp for the expiration + identity_id, // optional, the identityId for the credentials + } + res(data); + }); +} + +Auth.configure({ + refreshHandlers: { + 'developer': refreshToken // the property could be 'google', 'facebook', 'amazon', 'developer', OpenId domain + } +}) +``` + +### Federate with Auth0 + +You can use `Auth0` as one of the providers of your Cognito Identity Pool. This will allow users authenticated via Auth0 have access to your AWS resources. + +Step 1. [Follow Auth0 integration instructions for Cognito Federated Identity Pools](https://auth0.com/docs/integrations/integrating-auth0-amazon-cognito-mobile-apps) + +Step 2. Login with `Auth0`, then use the id token returned to get AWS credentials from `Cognito Federated Identity Pools` using `Auth.federatedSignIn`: + +```js +const { idToken, domain, name, email, phoneNumber } = getFromAuth0(); // get the user credentials and info from auth0 +const { exp } = decodeJWTToken(idToken); // Please decode the id token in order to get the expiration time + +Auth.federatedSignIn( + domain, // The Auth0 Domain, + { + token: idToken, // The id token from Auth0 + // expires_at means the timestamp when the token provided expires, + // here we can derive it from the expiresIn parameter provided, + // then convert its unit from second to millisecond, and add the current timestamp + expires_at: exp * 1000 // the expiration timestamp + }, + { + // the user object, you can put whatever property you get from the Auth0 + // for example: + name, // the user name + email, // Optional, the email address + phoneNumber, // Optional, the phone number + } +).then(cred => { + console.log(cred); +}); +``` + +Step 3. Get the current user and current Credentials: + +```js +Auth.currentAuthenticatedUser().then(user => console.log(user)); +Auth.currentCredentials().then(creds => console.log(creds)); +// Auth.currentSession() does not currently support federated identities. Please store the auth0 session info manually(for example, store tokens into the local storage). +``` + +Step 4. You can pass a refresh handler to the Auth module to refresh the id token from `Auth0`: + +```js +function refreshToken() { + // refresh the token here and get the new token info + // ...... + + return new Promise(res, rej => { + const data = { + token, // the token from the provider + expires_at, // the timestamp when the token expires (in milliseconds) + identity_id, // optional, the identityId for the credentials + } + res(data); + }); +} + +Auth.configure({ + refreshHandlers: { + 'your_auth0_domain': refreshToken + } +}) +``` + +## Lambda Triggers + +The CLI allows you to configure [Lambda Triggers](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html) for your AWS Cognito User Pool. These enable you to add custom functionality to your registration and authentication flows. [Read more](/cli/function/function) + +### Pre Authentication and Pre Sign-up Lambda triggers + +If you have a Pre Sign-up or Pre Authentication Lambda trigger enabled, you can pass `validationData` as one the properties for `signUp` or `signIn`. This metadata can be used to implement additional validations around authentication, such as restricting the types of user accounts that can be registered. + +```js +Auth.signIn({ + username, // Required, the username + password, // Optional, the password + validationData, // Optional, an object of key-value pairs which can contain any key and will be passed to your Lambda trigger as-is. +}).then(user => console.log(user)) + .catch(err => console.log(err)); +``` + +### Auto Sign-in Lambda triggers + +If you have `autoSignIn` enabled, you can pass `validationData` as property for `autoSignIn` parameter. It is used + to implement additional validations for authentication. For example, you might choose to allow or disallow + user sign-up based on the user's domain. The Lambda trigger receives the validation data and + uses it in the validation process. + +```js +Auth.signUp({ + username, + password, + attributes: { + email, // optional + phone_number, // optional - E.164 number convention + // other custom attributes + }, + autoSignIn: { // optional - enables auto sign in after user is confirmed + enabled: true, + validationData, // optional + } +}) +``` + +### Passing metadata to other Lambda triggers + +Many Cognito Lambda Triggers also accept unsanitized key/value pairs in the form of a `clientMetadata` attribute. To configure a static set of key/value pairs, you can define a `clientMetadata` key in the `Auth.configure` function. You can also pass a `clientMetadata` parameter to the various `Auth` functions which result in Cognito Lambda Trigger execution. + +These functions include: + +- `Auth.changePassword` +- `Auth.completeNewPassword` +- `Auth.confirmSignIn` +- `Auth.confirmSignUp` +- `Auth.forgotPasswordSubmit` +- `Auth.resendSignUp` +- `Auth.sendCustomChallengeAnswer` +- `Auth.signIn` +- `Auth.signUp` (as a function parameter and part of `autoSignIn` parameter) +- `Auth.updateUserAttributes` +- `Auth.verifyUserAttribute` + +Please note that some of triggers which accept a `validationData` attribute will use `clientMetadata` as the value for `validationData`. Exercise caution with using `clientMetadata` when you are relying on `validationData`. + +## Working with AWS service objects + +You can use AWS *Service Interface Objects* to work AWS Services in authenticated State. You can call methods on any AWS Service interface object by passing your credentials from `Auth` object to the service call constructor: + +```javascript +import Route53 from 'aws-sdk/clients/route53'; + +Auth.currentCredentials() + .then(credentials => { + const route53 = new Route53({ + apiVersion: '2013-04-01', + credentials: Auth.essentialCredentials(credentials) + }); + + // more code working with route53 object + // route53.changeResourceRecordSets(); + }) +``` + +Full API Documentation for Service Interface Objects is available [here](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/_index.html). + + + +Note: To work with Service Interface Objects, your Amazon Cognito users' [IAM role](https://docs.aws.amazon.com/cognito/latest/developerguide/iam-roles.html) must have the appropriate permissions to call the requested services. + + + +## API reference + +For the complete API documentation for Authentication module, visit our [API Reference](https://aws-amplify.github.io/amplify-js/api/classes/authclass.html) diff --git a/src/fragments/lib-legacy/auth/js/customui.mdx b/src/fragments/lib-legacy/auth/js/customui.mdx new file mode 100644 index 00000000000..ec1799accb0 --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/customui.mdx @@ -0,0 +1,171 @@ +import callout from '/src/fragments/ui/auth/custom-ui-callout-js.mdx'; + +### Customize UI theme + + + +For web frameworks, you can use CSS Variables as described [here](/ui/customization/theming). + +For React Native, you must override properties defined in AmplifyTheme.js [here](https://github.com/aws-amplify/amplify-js/blob/main/packages/aws-amplify-react-native/src/AmplifyTheme.ts) + +```javascript +import { AmplifyTheme } from 'aws-amplify-react-native'; + +const MySectionHeader = Object.assign({}, AmplifyTheme.sectionHeader, { background: 'orange' }); +const MyTheme = Object.assign({}, AmplifyTheme, { sectionHeader: MySectionHeader }); + + +``` + +### Create your own UI + +To customize the default auth experience even more, you can create your own auth UI. To do this, your component will leverage the following *Authenticator* properties: + +``` +- authState +- authData +- onStateChange +``` + +The following example creates an 'Always On' Auth UI, which continuously shows the current auth state in your app. + +```javascript +import { Authenticator, SignIn, SignUp, ConfirmSignUp, Greetings } from 'aws-amplify-react'; + +const AlwaysOn = (props) => { + return ( +
+
I am always here to show current auth state: {props.authState}
+ +
+ ) +} + +handleAuthStateChange(state) { + if (state === 'signedIn') { + /* Do something when the user has signed-in */ + } +} + +render() { + return ( + + + + + + + + ) +} +``` + +## Composing your own Authenticator + +`Authenticator` is designed as a container for a number of Auth components. Each component does a single job, e.g., SignIn, SignUp, etc. By default, all of these elements are visible depending on the authentication state. + +If you want to replace some or all of the Authenticator elements, you need to set `hideDefault={true}`, so the component doesn't render its default view. Then you can pass in your own set of child components that listen to `authState` and decide what to do. + +You can also pass the child components you want to use. For example, the following Authenticator configuration only renders *Greetings* component which has a *Sign Out* button: + +```javascript + + + +``` + +### Customize greeting message + +The *Greetings* component has two states: signedIn, and signedOut. To customize the greeting message, set properties `inGreeting` and `outGreeting` using a string or function. + +```javascript + + 'Hello ' + username} + outGreeting="Please sign in..." + /> + +``` +### Customize Error Messages + +During authentication flows, Amplify handles error messages returned from the server. Amplify provides a simple way of customizing those error messages with a `messageMap` callback. + +The function takes the original message as arguments and then outputs the desired message. Check `AmplifyMessageMap.js` file to see how Amplify makes the map function. + +```javascript +const map = (message) => { + if (/incorrect.*username.*password/i.test(message)) { + return 'Incorrect username or password'; + } + + return message; +} + + +``` + +You may notice in `AmplifyMessageMap.js` it also handles internationalization. This topic is covered in our [I18n Guide](/lib/utilities/i18n). + +### Customize Text Labels + +You can change the text for the labels (like 'Sign In' and 'Sign Up') on the built-in user interface by providing your own dictionary to the localization engine: + +```javascript +import { I18n } from 'aws-amplify'; + +const authScreenLabels = { + en: { + 'Sign Up': 'Create new account', + 'Sign Up Account': 'Create a new account' + } +}; + +I18n.setLanguage('en'); +I18n.putVocabularies(authScreenLabels); +``` + +### Customize Initial authState + +You can change the initial auth state for your Authenticator. By default the initial state is `signIn` which will shows the `signIn` component. +If you want the `signUp` component shows first, you can do: +```javascript + +``` + +## Customize withAuthenticator HOC + +The `withAuthenticator` HOC gives you some nice default authentication screens out-of-box. If you want to use your own components rather than provided default components, you can pass the list of customized components to `withAuthenticator`: + +```javascript +import React, { Component } from 'react'; +import { ConfirmSignIn, ConfirmSignUp, ForgotPassword, RequireNewPassword, SignIn, SignUp, VerifyContact, withAuthenticator } from 'aws-amplify-react'; + +class App extends Component { + render() { + ... + } +} + +// This is my custom Sign in component +class MySignIn extends SignIn { + render() { + ... + } +} + +export default withAuthenticator(App, false, [ + , + , + , + , + , + , + +]); +``` + +If you would like to add custom styling to the UI components you can pass a custom theme object as a parameter to the withAuthenticator HOC using the instructions [above](#customize-ui-theme): + +```javascript +export default withAuthenticator(App, false, [], null, MyTheme); +``` diff --git a/src/fragments/lib-legacy/auth/js/delete_user.mdx b/src/fragments/lib-legacy/auth/js/delete_user.mdx new file mode 100644 index 00000000000..87fc910478b --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/delete_user.mdx @@ -0,0 +1,32 @@ +Invoke the `deleteUser` API to delete a user from the Auth category. This action will also sign your user out. + +If your application uses a Cognito User Pool, which is the default configuration for the Amazon Cognito plugin, this action will delete the user from the Cognito User Pool. It will have no effect if you are federating with a Cognito Identity Pool alone. + + + + Depending on your application, data associated with your user may be stored in + other resources such as Amazon DynamoDB or AWS S3. It is recommended that you + carefully evaluate this data and implement code to remove or otherwise + sanitize it, if necessary, prior to giving users the ability to delete their + own user record. + + + + + + Before invoking the “delete user” API, you can first delete associated user data from the GraphQL API. For example, if you are using Amplify CLI's [GraphQL transformer](https://docs.amplify.aws/cli-legacy/graphql-transformer/overview/) to persist user data via [owner based access control](https://docs.amplify.aws/cli/graphql/authorization-rules/#per-user--owner-based-data-access), you could follow [these instructions](https://gist.github.com/aws-amplify-ops/27954c421bd72930874d48c15c284807) to delete associated user data. + + This allows you to address any guidelines that require your app to delete data associated with a user who deletes their account. + + + +```javascript +async function deleteUser() { + try { + const result = await Auth.deleteUser(); + console.log(result); + } catch (error) { + console.log('Error deleting user', error); + } +} +``` diff --git a/src/fragments/lib-legacy/auth/js/device_features/10_rememberDevice.mdx b/src/fragments/lib-legacy/auth/js/device_features/10_rememberDevice.mdx new file mode 100644 index 00000000000..10590bbaf0c --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/device_features/10_rememberDevice.mdx @@ -0,0 +1,10 @@ +```javascript +async function rememberDevice() { + try{ + const result = await Auth.rememberDevice(); + console.log(result) + }catch (error) { + console.log('Error remembering device', error) + } +} +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/auth/js/device_features/20_forgetDevice.mdx b/src/fragments/lib-legacy/auth/js/device_features/20_forgetDevice.mdx new file mode 100644 index 00000000000..1d7b6a0edd5 --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/device_features/20_forgetDevice.mdx @@ -0,0 +1,10 @@ +```javascript +async function forgetDevice() { + try{ + const result = await Auth.forgetDevice(); + console.log(result) + }catch (error) { + console.log('Error forgetting device', error) + } +} +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/auth/js/device_features/30_fetchDevice.mdx b/src/fragments/lib-legacy/auth/js/device_features/30_fetchDevice.mdx new file mode 100644 index 00000000000..caa4203a5f3 --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/device_features/30_fetchDevice.mdx @@ -0,0 +1,10 @@ +```javascript +async function fetchDevices() { + try { + const result = await Auth.fetchDevices(); + console.log(result); + } catch (err) { + console.log('Error fetching devices', err); + } +} +``` diff --git a/src/fragments/lib-legacy/auth/js/emailpassword.mdx b/src/fragments/lib-legacy/auth/js/emailpassword.mdx new file mode 100644 index 00000000000..4e5537eba86 --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/emailpassword.mdx @@ -0,0 +1,162 @@ +## Sign-up + +Create a new user in the Amazon Cognito UserPool by passing the new user's email address, password, and other attributes to `Auth.signUp`. + +```javascript +import { Auth } from 'aws-amplify'; + +async function signUp() { + try { + const { user } = await Auth.signUp({ + username, + password, + attributes: { + email, // optional + phone_number, // optional - E.164 number convention + // other custom attributes + }, + autoSignIn: { // optional - enables auto sign in after user is confirmed + enabled: true, + } + }); + console.log(user); + } catch (error) { + console.log('error signing up:', error); + } +} +``` + +The `Auth.signUp` promise returns a data object of type [`ISignUpResult`](https://github.com/aws-amplify/amplify-js/blob/4644b4322ee260165dd756ca9faeb235445000e3/packages/amazon-cognito-identity-js/index.d.ts#L136-L139) with a [`CognitoUser`](https://github.com/aws-amplify/amplify-js/blob/4644b4322ee260165dd756ca9faeb235445000e3/packages/amazon-cognito-identity-js/index.d.ts#L48). `CognitoUser` contains a `userSub` which is a unique identifier of the authenticated user; the `userSub` is not the same as the `username`. + +```js +{ + user: CognitoUser; + userConfirmed: boolean; + userSub: string; +} +``` + +### Auto sign in after sign up + +If you enabled `autoSignIn`, the `sign up` function will dispatch `autoSignIn` hub event after successful confirmation. +If authentication was successful, the event will contain `CognitoUser` in data object. If auto sign in failed, it will dispatch `autoSignIn_failure` event. + +```js +import { Hub } from 'aws-amplify'; + +function listenToAutoSignInEvent() { + Hub.listen('auth', ({ payload }) => { + const { event } = payload; + if (event === 'autoSignIn') { + const user = payload.data; + // assign user + } else if (event === 'autoSignIn_failure') { + // redirect to sign in page + } + }) +} + +``` + +### Confirm sign up + +If you enabled multi-factor auth, confirm the sign-up after retrieving a confirmation code from the user. + +```js +import { Auth } from 'aws-amplify'; + +async function confirmSignUp() { + try { + await Auth.confirmSignUp(username, code); + } catch (error) { + console.log('error confirming sign up', error); + } +} +``` + +### Re-send sign up confirmation code + +If user didn't get a confirmation code, you can use `resendSignUp` function to send a new one. +```js +import { Auth } from 'aws-amplify'; + +async function resendConfirmationCode() { + try { + await Auth.resendSignUp(username); + console.log('code resent successfully'); + } catch (err) { + console.log('error resending code: ', err); + } +} +``` + +### Custom Attributes + +To create a custom attribute during your sign-up process, add it to the attributes field of the signUp method prepended with `custom:`. + +```js +Auth.signUp({ + username, + password, + attributes: { + email, + 'custom:favorite_flavor': 'Cookie Dough' // custom attribute, not standard + } +}) +``` + +> Amazon Cognito does not dynamically create custom attributes on sign up. In order to use a custom attribute, the attribute must be first created in the user pool. To open the User Pool to create custom attributes using the Amplify ClI, run `amplify console auth`. If you are not using the Amplify CLI, you can view the user pool by visiting the AWS console and opening the Amazon Cognito dashboard. + +## Sign-in + +When signing in with user name and password, you will pass in the username and the password to the `signIn` method of the Auth class. + +```javascript +import { Auth } from 'aws-amplify'; + +async function signIn() { + try { + const user = await Auth.signIn(username, password); + } catch (error) { + console.log('error signing in', error); + } +} +``` + +## Sign-out + +```javascript +import { Auth } from 'aws-amplify'; + +async function signOut() { + try { + await Auth.signOut(); + } catch (error) { + console.log('error signing out: ', error); + } +} +``` + +[Amazon Cognito now supports token revocation](https://aws.amazon.com/about-aws/whats-new/2021/06/amazon-cognito-now-supports-targeted-sign-out-through-refresh-token-revocation/) and Amplify (from version 4.1.0) will revoke Amazon Cognito tokens if the application is online. This means Cognito refresh token cannot be used anymore to generate new Access and Id Tokens. + +Access and Id Tokens are short-lived (60 minutes by default but can be set from 5 minutes to 1 day). After revocation these tokens cannot be used with Cognito User Pools anymore, however they are still valid when used with other services like AppSync or API Gateway. + +For limiting subsequent calls to these other services after invalidating tokens, we recommend lowering token expiration time for your app client in the Cognito User Pools console. If you are using the Amplify CLI this can be accessed by running `amplify console auth`. + +Token revocation is enabled automatically on new Amazon Cognito User Pools, however existing User Pools must enable this feature, [using the Cognito Console or AWS CLI](https://docs.aws.amazon.com/cognito/latest/developerguide/token-revocation.html) + +### Global sign-out + +By doing this, you sign out users from all devices. It also invalidates all refresh tokens issued to a user. The user's current access and Id tokens remain valid until their expiry. Access and Id tokens expire one hour after they are issued. + +```js +import { Auth } from 'aws-amplify'; + +async function signOut() { + try { + await Auth.signOut({ global: true }); + } catch (error) { + console.log('error signing out: ', error); + } +} +``` diff --git a/src/fragments/lib-legacy/auth/js/getting-started-steps-basic-auth.mdx b/src/fragments/lib-legacy/auth/js/getting-started-steps-basic-auth.mdx new file mode 100644 index 00000000000..3d6a17980a5 --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/getting-started-steps-basic-auth.mdx @@ -0,0 +1,34 @@ + + + +Install the necessary dependencies by running the following command: + +```sh +npm install aws-amplify +``` + + + + +Install the necessary dependencies by running the following command: + +```sh +npm install aws-amplify amazon-cognito-identity-js @react-native-community/netinfo @react-native-async-storage/async-storage +``` + +You will also need to install the pod dependencies for iOS: + +```sh +npx pod-install +``` + + + +Install the necessary dependencies by running the following command: + +```sh +npm install aws-amplify @react-native-community/netinfo @react-native-async-storage/async-storage +``` + + + diff --git a/src/fragments/lib-legacy/auth/js/getting-started.mdx b/src/fragments/lib-legacy/auth/js/getting-started.mdx new file mode 100644 index 00000000000..97d2d1d435f --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/getting-started.mdx @@ -0,0 +1,229 @@ +> Prerequisite: [Install and configure](/cli/start/install) the Amplify CLI + +## Authentication with Amplify + +The Amplify Framework uses [Amazon Cognito](https://aws.amazon.com/cognito/) as the main authentication provider. Amazon Cognito is a robust user directory service that handles user registration, authentication, account recovery & other operations. In this tutorial, you'll learn how to add authentication to your application using Amazon Cognito and username/password login. + +## Create authentication service +To start from scratch, run the following command in your project's root folder: + +> If you want to re-use an existing authentication resource from AWS (e.g. Amazon Cognito UserPool or Identity Pool) refer to [this section](/lib/auth/start#re-use-existing-authentication-resource). + +```bash +amplify add auth +``` + +```console +? Do you want to use the default authentication and security configuration? Default configuration +? How do you want users to be able to sign in? Username +? Do you want to configure advanced settings? No, I am done. +``` + +To deploy the service, run the `push` command: + +```bash +amplify push +``` + +Now, the authentication service has been deployed and you can start using it. To view the deployed services in your project at any time, go to Amplify Console by running the following command: + +```bash +amplify console +``` + +## Configure your application + +Add Amplify to your app: + +import all0 from "/src/fragments/lib/auth/js/getting-started-steps-basic-auth.mdx"; + + + +In your app's entry point (i.e. __App.js__, __index.js__, or __main.js__), import and load the configuration file: + +```javascript +import { Amplify, Auth } from 'aws-amplify'; +import awsconfig from './aws-exports'; +Amplify.configure(awsconfig); +``` + +## Enable sign-up, sign-in, and sign-out + +There are two ways to add authentication capabilities to your application. + +1. [Use pre-built UI components](#option-1-use-pre-built-ui-components) + +2. [Call Authentication APIs manually](#option-2-call-authentication-apis-manually) + +## Option 1: Use pre-built UI components + +Creating the login flow can be quite difficult and time consuming to get right. Amplify Framework has authentication UI components you can use that will provide the entire authentication flow for you, using your configuration specified in your __aws-exports.js__ file. + +Amplify has pre-built UI components for React, Vue, Angular and React Native. + + + + +First, install the `@aws-amplify/ui-react` library as well as `aws-amplify` if you have not already: + +```sh +npm install aws-amplify @aws-amplify/ui-react +``` + +Next, open __src/App.js__ and add the `withAuthenticator` component. + +**withAuthenticator** + +import all1 from "/src/fragments/ui/auth/react/withauthenticator.mdx"; + + + + + + +First, install the `@aws-amplify/ui-vue` library as well as `aws-amplify` if you have not already: + +```bash +npm install aws-amplify @aws-amplify/ui-vue +``` + +Next, open __src/App.js__ and add the `Authenticator` component. + +**Authenticator** + +The `Authenticator` component offers a simple way to add authentication flows into your app. This component encapsulates an authentication workflow in the framework of your choice and is backed by the cloud resources set up in your Auth cloud resources. You’ll also notice that `user` and `signOut` are passed to the inner template. + +```html + + + +``` + + + + +First, install the `@aws-amplify/ui-components` library as well as `aws-amplify` if you have not already: + +```bash +npm install aws-amplify @aws-amplify/ui-components +``` + +Now open __src/main.js__ and add the following below your last import: + +```js +import '@aws-amplify/ui-components'; +import { + applyPolyfills, + defineCustomElements, +} from '@aws-amplify/ui-components/loader'; +import Vue from 'vue'; + +Vue.config.ignoredElements = [/amplify-\w*/]; + + +applyPolyfills().then(() => { + defineCustomElements(window); +}); +``` + +Next, open __src/App.js__ and add the `amplify-authenticator` component. + +**amplify-authenticator** + +The `amplify-authenticator` component offers a simple way to add authentication flows into your app. This component encapsulates an authentication workflow in the framework of your choice and is backed by the cloud resources set up in your Auth cloud resources. You’ll also notice the `amplify-sign-out` component. This is an optional component if you’d like to render a sign out button. + +```html + +``` + + + + +First, install the `@aws-amplify/ui-angular` library as well as `aws-amplify` if you have not already: + +```bash +npm install aws-amplify @aws-amplify/ui-angular +``` + +Now open __app.module.ts__ and add the Amplify imports and configuration: + +```js +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser';; +import { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular'; + +import { AppComponent } from './app.component'; +import awsconfig from '../aws-exports'; + +Amplify.configure(awsconfig); + +@NgModule({ + declarations: [AppComponent], + imports: [BrowserModule, AmplifyAuthenticatorModule], + providers: [], + bootstrap: [AppComponent], +}) +export class AppModule {} +``` + +Next, import the default theme inside __styles.css__: + +```css +@import '~@aws-amplify/ui-angular/theme.css'; +```` + +Next, open __app.component.html__ and add the `amplify-authenticator` component. + +**amplify-authenticator** + +The `amplify-authenticator` component offers a simple way to add authentication flows into your app. This component encapsulates an authentication workflow in the framework of your choice and is backed by the cloud resources set up in your Auth cloud resources. You’ll also notice that `user` and `signOut` are provided to the inner template. + +```html + + +

Welcome {{ user.username }}!

+ +
+
+``` + +
+
+ +## Option 2: Call Authentication APIs manually + +Follow the instructions in the [Sign in, Sign up and Sign out](/lib/auth/emailpassword) to learn about how to integrate these authentication flows in your application with the Auth APIs. + +## Summary + +To implement authentication flows using Amplify you can either use the Amplify UI libraries or call authentication methods directly on the `Auth` class. + +`Auth` has over 30 methods including [`signUp`](/lib/auth/emailpassword#sign-up), [`signIn`](/lib/auth/emailpassword#sign-in), [`forgotPassword`](/lib/auth/manageusers#forgot-password), and [`signOut`](/lib/auth/emailpassword#sign-out) that allow you full control over all aspects of the user authentication flow. + +Check out the complete API [here](https://aws-amplify.github.io/amplify-js/api/classes/authclass.html). diff --git a/src/fragments/lib-legacy/auth/js/hub_events/10_listen_events.mdx b/src/fragments/lib-legacy/auth/js/hub_events/10_listen_events.mdx new file mode 100644 index 00000000000..0642ab66ec3 --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/hub_events/10_listen_events.mdx @@ -0,0 +1,38 @@ +```javascript +import { Hub, Logger } from 'aws-amplify'; + +const logger = new Logger('My-Logger'); + +const listener = (data) => { + switch (data.payload.event) { + case 'signIn': + logger.info('user signed in'); + break; + case 'signUp': + logger.info('user signed up'); + break; + case 'signOut': + logger.info('user signed out'); + break; + case 'signIn_failure': + logger.error('user sign in failed'); + break; + case 'tokenRefresh': + logger.info('token refresh succeeded'); + break; + case 'tokenRefresh_failure': + logger.error('token refresh failed'); + break; + case 'autoSignIn': + logger.info('Auto Sign In after Sign Up succeeded'); + break; + case 'autoSignIn_failure': + logger.error('Auto Sign In after Sign Up failed'); + break; + case 'configured': + logger.info('the Auth module is configured'); + } +} + +Hub.listen('auth', listener); +``` diff --git a/src/fragments/lib-legacy/auth/js/manageusers.mdx b/src/fragments/lib-legacy/auth/js/manageusers.mdx new file mode 100644 index 00000000000..4de98e6c614 --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/manageusers.mdx @@ -0,0 +1,290 @@ +This section covers the client-side development experience for password and user management. To learn more about exposing administration actions to your application, click [here](/cli/auth/admin). + +## Password operations + +### Change password + +```javascript +import { Auth } from 'aws-amplify'; + +Auth.currentAuthenticatedUser() + .then(user => { + return Auth.changePassword(user, 'oldPassword', 'newPassword'); + }) + .then(data => console.log(data)) + .catch(err => console.log(err)); +``` + +### Forgot password + +```javascript +import { Auth } from 'aws-amplify'; + +// Send confirmation code to user's email +Auth.forgotPassword(username) + .then(data => console.log(data)) + .catch(err => console.log(err)); + +// Collect confirmation code and new password, then +Auth.forgotPasswordSubmit(username, code, new_password) + .then(data => console.log(data)) + .catch(err => console.log(err)); +``` + +### Complete new password + +The user is asked to provide the new password and required attributes during the first sign-in attempt if a valid user directory is created in Amazon Cognito. During this scenario, the following method can be called to process the new password entered by the user. + +```js +import { Auth } from 'aws-amplify'; + +Auth.signIn(username, password) +.then(user => { + if (user.challengeName === 'NEW_PASSWORD_REQUIRED') { + const { requiredAttributes } = user.challengeParam; // the array of required attributes, e.g ['email', 'phone_number'] + Auth.completeNewPassword( + user, // the Cognito User Object + newPassword, // the new password + // OPTIONAL, the required attributes + { + email: 'xxxx@example.com', + phone_number: '1234567890' + } + ).then(user => { + // at this time the user is logged in if no MFA required + console.log(user); + }).catch(e => { + console.log(e); + }); + } else { + // other situations + } +}).catch(e => { + console.log(e); +}); +``` + +## Account recovery verification + +Either the phone number or the email address is required for account recovery. You can let the user verify those attributes by: +```javascript +// To initiate the process of verifying the attribute like 'phone_number' or 'email' +Auth.verifyCurrentUserAttribute(attr) +.then(() => { + console.log('a verification code is sent'); +}).catch((e) => { + console.log('failed with error', e); +}); + +// To verify attribute with the code +Auth.verifyCurrentUserAttributeSubmit(attr, 'the_verification_code') +.then(() => { + console.log('phone_number verified'); +}).catch(e => { + console.log('failed with error', e); +}); +``` + +## Retrieve current authenticated user + +You can call `Auth.currentAuthenticatedUser()` to get the current authenticated user object. + +```javascript +import { Auth } from 'aws-amplify'; + +Auth.currentAuthenticatedUser({ + bypassCache: false // Optional, By default is false. If set to true, this call will send a request to Cognito to get the latest user data +}).then(user => console.log(user)) +.catch(err => console.log(err)); +``` +This method can be used to check if a user is logged in when the page is loaded. It will throw an error if there is no user logged in. +This method should be called after the Auth module is configured or the user is logged in. To ensure that you can listen on the auth events `configured` or `signIn`. [Learn how to listen on auth events.](/lib/utilities/hub#authentication-events) + +### Retrieve attributes for current authenticated user + +You can also access the user's attributes like their email address, phone number, sub, or any other attributes that are associated with them from the user object returned by `Auth.currentAuthenticatedUser`. + +```js +const { attributes } = await Auth.currentAuthenticatedUser(); +``` + +## Retrieve current session + +`Auth.currentSession()` returns a `CognitoUserSession` object which contains JWT `accessToken`, `idToken`, and `refreshToken`. + +This method will automatically refresh the `accessToken` and `idToken` if tokens are expired and a valid `refreshToken` presented. So you can use this method to refresh the session if needed. + +```javascript +import { Auth } from 'aws-amplify'; + +Auth.currentSession() + .then(data => console.log(data)) + .catch(err => console.log(err)); +``` + +## Managing user attributes + +You can pass user attributes during sign up: + +```javascript +Auth.signUp({ + 'username': 'jdoe', + 'password': 'mysecurerandompassword#123', + 'attributes': { + 'email': 'me@domain.com', + 'phone_number': '+12128601234', // E.164 number convention + 'given_name': 'Jane', + 'family_name': 'Doe', + 'nickname': 'Jane' + } +}); +``` + +You can retrieve user attributes: + +```javascript +let user = await Auth.currentAuthenticatedUser(); + +const { attributes } = user; +``` + +You can update user attributes: + +```javascript +let user = await Auth.currentAuthenticatedUser(); + +let result = await Auth.updateUserAttributes(user, { + 'email': 'me@anotherdomain.com', + 'family_name': 'Lastname' +}); +console.log(result); // SUCCESS +``` + +You can delete user attributes: + +```javascript +let user = await Auth.currentAuthenticatedUser(); + +let result = await Auth.deleteUserAttributes(user, [ + 'family_name' +]); +console.log(result); // SUCCESS +``` + +> You can find a list of all custom attributes here. + +If you change the email address, the user will receive a confirmation code. In your app, you can confirm the verification code: + +```javascript +let result = await Auth.verifyCurrentUserAttributeSubmit('email', 'abc123'); +``` + +## Managing security tokens + +> When using Authentication with AWS Amplify, you don't need to refresh Amazon Cognito tokens manually. The tokens are automatically refreshed by the library when necessary. + +Security Tokens like *IdToken* or *AccessToken* are stored in *localStorage* for the browser and in *AsyncStorage* for React Native. If you want to store those tokens in a more secure place or you are using Amplify in server side, then you can provide your own `storage` object to store those tokens. + +For example: +```ts +class MyStorage { + // the promise returned from sync function + static syncPromise = null; + // set item with the key + static setItem(key: string, value: string): string; + // get item with the key + static getItem(key: string): string; + // remove item with the key + static removeItem(key: string): void; + // clear out the storage + static clear(): void; + // If the storage operations are async(i.e AsyncStorage) + // Then you need to sync those items into the memory in this method + static sync(): Promise { + if (!MyStorage.syncPromise) { + MyStorage.syncPromise = new Promise((res, rej) => {}); + } + return MyStorage.syncPromise; + } +} + +// tell Auth to use your storage object +Auth.configure({ + storage: MyStorage +}); +``` + +Here is the example of how to use AsyncStorage as your storage object which will show you how to sync items from AsyncStorage into Memory: +```javascript +import { AsyncStorage } from 'react-native'; + +const MYSTORAGE_KEY_PREFIX = '@MyStorage:'; +let dataMemory = {}; + +/** @class */ +class MyStorage { + static syncPromise = null; + /** + * This is used to set a specific item in storage + */ + static setItem(key, value) { + AsyncStorage.setItem(MYSTORAGE_KEY_PREFIX + key, value); + dataMemory[key] = value; + return dataMemory[key]; + } + + /** + * This is used to get a specific key from storage + */ + static getItem(key) { + return Object.prototype.hasOwnProperty.call(dataMemory, key) ? dataMemory[key] : undefined; + } + + /** + * This is used to remove an item from storage + */ + static removeItem(key) { + AsyncStorage.removeItem(MYSTORAGE_KEY_PREFIX + key); + return delete dataMemory[key]; + } + + /** + * This is used to clear the storage + */ + static clear() { + dataMemory = {}; + return dataMemory; + } + + /** + * Will sync the MyStorage data from AsyncStorage to storageWindow MyStorage + */ + static sync() { + if (!MyStorage.syncPromise) { + MyStorage.syncPromise = new Promise((res, rej) => { + AsyncStorage.getAllKeys((errKeys, keys) => { + if (errKeys) rej(errKeys); + const memoryKeys = keys.filter((key) => key.startsWith(MYSTORAGE_KEY_PREFIX)); + AsyncStorage.multiGet(memoryKeys, (err, stores) => { + if (err) rej(err); + stores.map((result, index, store) => { + const key = store[index][0]; + const value = store[index][1]; + const memoryKey = key.replace(MYSTORAGE_KEY_PREFIX, ''); + dataMemory[memoryKey] = value; + }); + res(); + }); + }); + }); + } + return MyStorage.syncPromise; + } +} + +Auth.configure({ + storage: MyStorage +}); +``` + +To learn more about tokens, please visit [Amazon Cognito Developer Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html). diff --git a/src/fragments/lib-legacy/auth/js/mfa.mdx b/src/fragments/lib-legacy/auth/js/mfa.mdx new file mode 100644 index 00000000000..7b648a05f99 --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/mfa.mdx @@ -0,0 +1,220 @@ + +Note: If you create or update an SMS MFA configuration for your Cognito user pool, the Cognito service will send a test SMS message to an internal number in order to verify your configuration. You will be charged for these test messages by Amazon SNS. + +For information about Amazon SNS pricing, see [Worldwide SMS Pricing](https://aws.amazon.com/sns/sms-pricing/). + + +MFA (Multi-factor authentication increases security for your app by adding an authentication method and not relying solely on the username (or alias) and password. AWS Amplify uses Amazon Cognito to provide MFA. Please see [Amazon Cognito Developer Guide](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-mfa.html) for more information about setting up MFA in Amazon Cognito. + +Once you enable MFA on Amazon Cognito, you can configure your app to work with MFA. + +## Setup TOTP + +With TOTP (Time-based One-time Password), your app user is challenged to complete authentication using a time-based one-time (TOTP) password after their username and password have been verified. + +You can setup TOTP for a user in your app: + +```javascript +import { Auth } from 'aws-amplify'; + +// To setup TOTP, first you need to get a `authorization code` from Amazon Cognito +// `user` is the current Authenticated user +Auth.setupTOTP(user).then((code) => { + // You can directly display the `code` to the user or convert it to a QR code to be scanned. + // E.g., use following code sample to render a QR code with `qrcode.react` component: + // import QRCode from 'qrcode.react'; + // const str = "otpauth://totp/AWSCognito:"+ username + "?secret=" + code + "&issuer=" + issuer; + // +}); + +// ... + +// Then you will have your TOTP account in your TOTP-generating app (like Google Authenticator) +// Use the generated one-time password to verify the setup +Auth.verifyTotpToken(user, challengeAnswer) + .then(() => { + // don't forget to set TOTP as the preferred MFA method + Auth.setPreferredMFA(user, 'TOTP'); + // ... + }) + .catch((e) => { + // Token is not verified + }); +``` + +## Setup MFA type + +Multiple MFA types supported by Amazon Cognito. You can set the preferred method in your code: + +```javascript +import { Auth } from 'aws-amplify'; + +// You can select preferred mfa type, for example: +// Select TOTP as preferred +Auth.setPreferredMFA(user, 'TOTP') + .then((data) => { + console.log(data); + // ... + }) + .catch((e) => {}); + +// Select SMS as preferred +Auth.setPreferredMFA(user, 'SMS'); + +// Select no-mfa +Auth.setPreferredMFA(user, 'NOMFA'); +``` + +## Retrieve current preferred MFA type + +You can get current preferred MFA type in your code: + +```javascript +import { Auth } from 'aws-amplify'; + +// Will retrieve the current mfa type from cache +Auth.getPreferredMFA(user, { + // Optional, by default is false. + // If set to true, it will get the MFA type from server side instead of from local cache. + bypassCache: false +}).then((data) => { + console.log('Current preferred MFA type is: ' + data); +}); +``` + +## Allow users to select MFA type + +When working with multiple MFA Types, you can let the app user select the desired authentication method. `SelectMFAType` UI Component, which is provided with `aws-amplify-react` package, renders a list of available MFA types. + +```javascript +import { Amplify } from 'aws-amplify'; +import awsconfig from './aws-exports'; +import { SelectMFAType } from 'aws-amplify-react'; + +Amplify.configure(awsconfig); + +// Please have at least TWO types +// Please make sure you set it properly according to your Cognito User pool +const MFATypes = { + SMS: true, // if SMS enabled in your user pool + TOTP: true, // if TOTP enabled in your user pool + Optional: true, // if MFA is set to optional in your user pool +} + +class App extends Component { + // ... + render() { + return ( + // ... + + ) + } +} + +export default withAuthenticator(App, true); +``` + +## Advanced use cases + +### Sign-in with custom auth challenges + +When signing in with user name and password, you will either sign in directly or be asked to pass some challenges before getting authenticated. + +The `user` object returned from `Auth.signIn` will contain `challengeName` and `challengeParam` if the user needs to pass those challenges. You can call corresponding functions based on those two parameters. + +ChallengeName: + +- `SMS_MFA`: The user needs to input the code received from SMS message. You can submit the code by `Auth.confirmSignIn`. +- `SOFTWARE_TOKEN_MFA`: The user needs to input the OTP(one time password). You can submit the code by `Auth.confirmSignIn`. +- `NEW_PASSWORD_REQUIRED`: This happens when the user account is created through the Cognito console. The user needs to input the new password and required attributes. You can submit those data by `Auth.completeNewPassword`. +- `MFA_SETUP`: This happens when the MFA method is TOTP(the one time password) which requires the user to go through some steps to generate those passwords. You can start the setup process by `Auth.setupTOTP`. + +The following code is only for demonstration purpose: + +```javascript +import { Auth } from 'aws-amplify'; + +async function signIn() { + try { + const user = await Auth.signIn(username, password); + if ( + user.challengeName === 'SMS_MFA' || + user.challengeName === 'SOFTWARE_TOKEN_MFA' + ) { + // You need to get the code from the UI inputs + // and then trigger the following function with a button click + const code = getCodeFromUserInput(); + // If MFA is enabled, sign-in should be confirmed with the confirmation code + const loggedUser = await Auth.confirmSignIn( + user, // Return object from Auth.signIn() + code, // Confirmation code + mfaType // MFA Type e.g. SMS_MFA, SOFTWARE_TOKEN_MFA + ); + } else if (user.challengeName === 'NEW_PASSWORD_REQUIRED') { + const { requiredAttributes } = user.challengeParam; // the array of required attributes, e.g ['email', 'phone_number'] + // You need to get the new password and required attributes from the UI inputs + // and then trigger the following function with a button click + // For example, the email and phone_number are required attributes + const { username, email, phone_number } = getInfoFromUserInput(); + const loggedUser = await Auth.completeNewPassword( + user, // the Cognito User Object + newPassword, // the new password + // OPTIONAL, the required attributes + { + email, + phone_number + } + ); + } else if (user.challengeName === 'MFA_SETUP') { + // This happens when the MFA method is TOTP + // The user needs to setup the TOTP before using it + // More info please check the Enabling MFA part + Auth.setupTOTP(user); + } else { + // The user directly signs in + console.log(user); + } + } catch (err) { + if (err.code === 'UserNotConfirmedException') { + // The error happens if the user didn't finish the confirmation step when signing up + // In this case you need to resend the code and confirm the user + // About how to resend the code and confirm the user, please check the signUp part + } else if (err.code === 'PasswordResetRequiredException') { + // The error happens when the password is reset in the Cognito console + // In this case you need to call forgotPassword to reset the password + // Please check the Forgot Password part. + } else if (err.code === 'NotAuthorizedException') { + // The error happens when the incorrect password is provided + } else if (err.code === 'UserNotFoundException') { + // The error happens when the supplied username/email does not exist in the Cognito user pool + } else { + console.log(err); + } + } +} +``` + +### Sign-in with custom validation data for Lambda Trigger + +You can also pass an object which has the username, password and validationData which is sent to a PreAuthentication Lambda trigger + +```js +try { + const user = await Auth.signIn({ + username, // Required, the username + password, // Optional, the password + validationData // Optional, an arbitrary key-value pair map which can contain any key and will be passed to your PreAuthentication Lambda trigger as-is. It can be used to implement additional validations around authentication + }); + console.log('user is signed in!', user); +} catch (error) { + console.log('error signing in:', error); +} +``` + +### Forcing Email Uniqueness in Cognito User Pools + +When your Cognito User Pool sign-in options are set to "_Username_", and "_Also allow sign in with verified email address_", the _signUp()_ method creates a new user account every time it's called, without validating email uniqueness. In this case you will end up having multiple user pool identities and all previously created accounts will have their _email_verified_ attribute changed to _false_. + +To enforce Cognito User Pool signups with a unique email, you need to change your User Pool's _Attributes_ setting in [Amazon Cognito console](https://console.aws.amazon.com/cognito) as the following: + +![cup](/images/cognito_user_pool_settings.png) diff --git a/src/fragments/lib-legacy/auth/js/overview.mdx b/src/fragments/lib-legacy/auth/js/overview.mdx new file mode 100644 index 00000000000..9eef22b77db --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/overview.mdx @@ -0,0 +1,39 @@ +*Authentication* is a process to validate **who you are** (abbreviated as *AuthN*). The system which does this validation is referred to as an **Identity Provider** or **IdP**. This can be your own self-hosted IdP or a cloud service. Oftentimes, this IdP is a social provider such as Facebook, Google, or Amazon. + +*Authorization* is the process of validating **what you can access** (abbreviated as *AuthZ*). This is sometimes done by looking at tokens with custom logic, predefined rules, or signed requests with policies. + +## Authentication with AWS + +In the Amplify ecosystem, the most common Authentication method is either using Amazon Cognito User Pools independently or with a social provider to validate the identity of the user (known as *Federation*). + +[Amazon Cognito User Pools](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html) is a full-featured user directory service to handle user registration, authentication, and account recovery. [Amazon Cognito Federated Identities or Identity Pools](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-identity.html) on the other hand, is a way to authorize your users to use AWS services. + +Amplify interfaces with User Pools to store your user information, including federation with other OpenID providers like Facebook & Google, and it leverages Federated Identities to manage user access to AWS Resources, for example allowing a user to upload a file (to an S3 bucket). The Amplify CLI automates the access control policies for these AWS resources as well as provides [fine grained access controls via GraphQL](https://docs.amplify.aws/cli/graphql/authorization-rules) for protecting data in your APIs. + +Authorization is often done in one of two ways: + +1. Clients pass the tokens to the backend that perform custom logic to allow or deny actions +1. Clients sign the requests and the backend validates the signature, allowing or denying actions depending on predefined policy. The predefined rules are known as [IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html) policies and automatically configured by the Amplify CLI. + +The first mode is a common authorization method for REST or GraphQL APIs, while the second mode is necessary for interfacing with AWS services such as S3, Pinpoint, Sumerian, and others. + +## Sign-up and sign-in + +For many apps, user sign-up and sign-in is all that is required. Once authenticated the app can talk to an API to access and mutate data. In this case, you can simply create a User Pool by running `amplify add auth` using the Amplify CLI and selecting the default setup. In your application you can use `Auth.signUp` and `Auth.signIn` (or an Amplify UI component) to complete this process and retrieve tokens. The Amplify client will refresh the tokens calling `Auth.currentSession` if they are no longer valid. + +![Image](/images/SimpleAuthZ.png) + +## Social Provider Federation + +Many apps also support login with social providers such as Facebook, Google Sign-In, or Login With Amazon. The preferred way to do this is via an OAuth redirect which lets users login using their social media account and a corresponding user is created in User Pools. With this design you do not need to include an SDK for the social provider in your app. Set this up by running `amplify add auth` and selecting the social provider option. Upon completion you can use `Auth.federatedSignIn()` in your application to either show a pre-built "Hosted UI" or pass in a provider name (e.g. `Auth.federatedSignIn({provider: 'Facebook'})`) to interface directly and build out your own UI. + +![Image](/images/SocialAuthZ.png) + +You can also get credentials directly from Identity Pools by passing tokens from a provider directly to `Auth.federatedSignIn()`. However you will have to use that provider's SDK directly in your app and manage token refresh and auth flows manually. + + +## Accessing AWS services + +Some apps need to use AWS services which require [signing requests](https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html). Examples of this would be storing images or videos on S3, or sending analytics to Pinpoint or Kinesis. Amplify automatically signs requests with short term credentials from a Cognito Identity Pool which automatically expire, rotate, and refresh by the Amplify client libraries. Setting up your backend with `amplify add auth` and calling `Auth.signIn` will automatically do this for you as well after the client authenticates. The diagram below shows how JWT tokens are returned from User Pools and AWS credentials from Identity Pools. You can access these at any time with `Auth.currentSession()` and `Auth.currentCredentials()`. + +![Image](/images/AWSAuthZ.png) diff --git a/src/fragments/lib-legacy/auth/js/react-native-withoauth.mdx b/src/fragments/lib-legacy/auth/js/react-native-withoauth.mdx new file mode 100644 index 00000000000..6c7246f361e --- /dev/null +++ b/src/fragments/lib-legacy/auth/js/react-native-withoauth.mdx @@ -0,0 +1,98 @@ +**withOAuth Higher-Order Component (HOC)** + +With React Native & Expo, you can also use the `withOAuth` HOC to launch the Cognito Hosted UI experience. Just wrap your app's main component with our HOC. Doing so, will pass the following `props` available to your component: + +- `oAuthUser`: If the sign was successful, this object will have the user from the user pool. +- `oAuthError`: In case of an error, the string with the error as given by the Cognito Hosted UI. +- `hostedUISignIn`: A callback function to trigger the hosted UI sign in flow, this will show the Cognito Hosted UI. +- `signOut`: A callback function to trigger the hosted UI sign out flow. + + + +The following `props` are used for building a custom UI with buttons if you do not want to show the Cognito UI, however it will still create a User Pool entry once the OAuth flow has completed. + + + +- `facebookSignIn`: A callback function to trigger the hosted UI sign in flow for Facebook, this will show the Facebook login page. +- `googleSignIn`: A callback function to trigger the hosted UI sign in flow for Google, this will show the Google login page. +- `amazonSignIn`: A callback function to trigger the hosted UI sign in flow for LoginWithAmazon, this will show the LoginWithAmazon login page. +- `customProviderSignIn`: A callback function to trigger the hosted UI sign in flow for an OIDC provider, this will show the OIDC provider login page. This function expects a string with the **provider name** specified when [adding the OIDC IdP to your User Pool](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-oidc-idp.html#cognito-user-pools-oidc-idp-step-2). + +To use `withOAuth` in your React Native application first install the appropriate dependencies: + + + + +```sh +yarn add aws-amplify-react-native aws-amplify @react-native-picker/picker +``` + + + + +```sh +yarn add aws-amplify-react-native aws-amplify @react-native-picker/picker +``` + +You will also need to install the pod dependencies for iOS: + +```sh +npx pod-install +``` + + + +The following code snippet shows an example of its possible usage: + + + +Although `urlOpener` is left out of this sample for brevity, it is still recommended to use the custom example from above in this `withOAuth` sample. + + + +```javascript +import React from 'react'; +import { Button, Text, View } from 'react-native'; +import { Amplify, Auth, Hub } from 'aws-amplify'; +import { withOAuth } from "aws-amplify-react-native"; +import awsconfig from './aws-exports'; + +Amplify.configure(awsconfig); + +function App(props) { + const { + oAuthUser, + oAuthError, + hostedUISignIn, + facebookSignIn, + googleSignIn, + amazonSignIn, + customProviderSignIn, + signOut, + } = props; + + return ( + + User: {oAuthUser ? JSON.stringify(oAuthUser.attributes) : 'None'} + {oAuthUser ? ( + + + + + + ); +} +``` + + + You can also use the Authenticator UI component to add social sign in flow to your application. Visit Social Provider section to learn more. + + +### Deploying to Amplify Console + +To deploy your app to Amplify Console with continuous deployment of the frontend and backend, please follow [these instructions](https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html#creating-a-new-backend-environment-with-authentication-parameters). + +### Full Samples + + + + + +```js +import React, { useEffect, useState } from 'react'; +import { Amplify, Auth, Hub } from 'aws-amplify'; +import awsconfig from './aws-exports'; + +Amplify.configure(awsconfig); + +function App() { + const [user, setUser] = useState(null); + + useEffect(() => { + Hub.listen('auth', ({ payload: { event, data } }) => { + switch (event) { + case 'signIn': + case 'cognitoHostedUI': + getUser().then(userData => setUser(userData)); + break; + case 'signOut': + setUser(null); + break; + case 'signIn_failure': + case 'cognitoHostedUI_failure': + console.log('Sign in failure', data); + break; + } + }); + + getUser().then(userData => setUser(userData)); + }, []); + + function getUser() { + return Auth.currentAuthenticatedUser() + .then(userData => userData) + .catch(() => console.log('Not signed in')); + } + + return ( +
+

User: {user ? JSON.stringify(user.attributes) : 'None'}

+ {user ? ( + + ) : ( + + )} +
+ ); +} + +export default App; +``` +
+ + + + + +**Note for iOS Apps** + +In order for Amplify to listen for data from Cognito when linking back to your app, you will need to setup the `Linking` module in `AppDelegate.m` (see [React Native docs](https://reactnative.dev/docs/linking#enabling-deep-links) for more information): + +```objectivec +#import + +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + options:(NSDictionary *)options +{ + return [RCTLinkingManager application:application openURL:url options:options]; +} +``` + + +**In-App Browser Setup (optional, but recommended)** + +By default, Amplify will open the Cognito Hosted UI in Safari/Chrome, but you can override that behavior by providing a custom `urlOpener`. The sample below uses [react-native-inappbrowser-reborn](https://github.com/proyecto26/react-native-inappbrowser), but you can use any other in-app browser available. + +**Sample** + +```js +import React, { useEffect, useState } from 'react'; +import { Button, Linking, Text, View } from 'react-native'; +import { Amplify, Auth, Hub } from 'aws-amplify'; +import InAppBrowser from 'react-native-inappbrowser-reborn'; +import awsconfig from './aws-exports'; + +async function urlOpener(url, redirectUrl) { + await InAppBrowser.isAvailable(); + const { type, url: newUrl } = await InAppBrowser.openAuth(url, redirectUrl, { + showTitle: false, + enableUrlBarHiding: true, + enableDefaultShare: false, + ephemeralWebSession: false, + }); + + if (type === 'success') { + Linking.openURL(newUrl); + } +} + +Amplify.configure({ + ...awsconfig, + oauth: { + ...awsconfig.oauth, + urlOpener, + }, +}); + +function App() { + const [user, setUser] = useState(null); + + useEffect(() => { + Hub.listen('auth', ({ payload: { event, data } }) => { + switch (event) { + case 'signIn': + case 'cognitoHostedUI': + getUser().then(userData => setUser(userData)); + break; + case 'signOut': + setUser(null); + break; + case 'signIn_failure': + case 'cognitoHostedUI_failure': + console.log('Sign in failure', data); + break; + } + }); + + getUser().then(userData => setUser(userData)); + }, []); + + function getUser() { + return Auth.currentAuthenticatedUser() + .then(userData => userData) + .catch(() => console.log('Not signed in')); + } + + return ( + + User: {user ? JSON.stringify(user.attributes) : 'None'} + {user ? ( + + + + +`}) +export class AppComponent implements OnInit, OnDestroy { + subscription; + + ngOnInit() { + //Subscribe to changes + this.subscription = DataStore.observe(Post).subscribe((msg) => { + console.log(msg.model, msg.opType, msg.element); + }); + } + + ngOnDestroy() { + if (!this.subscription) return; + this.subscription.unsubscribe(); + } + + public async loadPosts() { + let posts = await DataStore.query(Post, (c) => c.rating("gt", 4)); + console.log(posts); + } + + public onCreate() { + DataStore.save( + new Post({ + title: `New title ${Date.now()}`, + rating: (function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min)) + min; + })(1, 7), + status: PostStatus.ACTIVE, + }) + ); + } + + public onDeleteAll() { + DataStore.delete(Post, Predicates.ALL); + } +} +``` + + + + +```javascript + + + +``` + + + + +```jsx +/** + * React Native DataStore Sample App + */ + +import React, { Component } from "react"; +import { Text, StyleSheet, ScrollView } from "react-native"; + +import { Amplify, DataStore, Predicates } from "aws-amplify"; +import { Post, PostStatus, Comment } from "./src/models"; + +import awsConfig from "./aws-exports"; +Amplify.configure(awsConfig); +let subscription; + +class App extends Component { + constructor(props) { + super(props); + this.state = { + posts: [], + }; + } + + componentDidMount() { + this.onQuery(); + subscription = DataStore.observe(Post).subscribe((msg) => { + console.log("SUBSCRIPTION_UPDATE", msg); + this.onQuery(); + }); + } + + componentWillUnmount() { + subscription.unsubscribe(); + } + + onCreatePost() { + DataStore.save( + new Post({ + title: `New Post ${Date.now()}`, + rating: (function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + // The maximum is exclusive and the minimum is inclusive + return Math.floor(Math.random() * (max - min)) + min; + })(5, 10), + status: PostStatus.ACTIVE, + }) + ); + } + + async onCreatePostAndComments() { + const post = new Post({ + title: `New Post with comments ${Date.now()}`, + rating: 5, + status: PostStatus.ACTIVE, + }); + + await DataStore.save(post); + + for (let i = 0; i < 2; i++) { + DataStore.save( + new Comment({ + content: `New comment ${Date.now()}`, + post, + }) + ); + } + } + + onQuery = async () => { + const posts = await DataStore.query(Post, (c) => c.rating("gt", 2)); + console.log("QUERY_POSTS_RESULT", posts); + const comments = await DataStore.query(Comment); + this.setState({ posts }); + console.log("QUERY_COMMENTS_RESULT", comments); + }; + + onDelete = async () => { + const deletedPosts = await DataStore.delete(Post, Predicates.ALL); + console.log("DELETE_RESULT", deletedPosts); + }; + + render() { + return ( + + + Create Post + + + Create Post & Comments + + + Query Posts + + + Delete All Posts + + {this.state.posts.map((post, i) => ( + {`${post.title} ${post.rating}`} + ))} + + ); + } +} + +const styles = StyleSheet.create({ + scrollview: { + paddingTop: 40, + flex: 1, + }, + container: { + alignItems: "center", + }, + text: { + fontSize: 20, + textAlign: "center", + margin: 10, + }, +}); + +export default App; +``` + +
+ +## API Reference + +For the complete API documentation for DataStore, visit our [API Reference](https://aws-amplify.github.io/amplify-js/api/classes/datastore.html) diff --git a/src/fragments/lib-legacy/datastore/js/getting-started/10_preReq.mdx b/src/fragments/lib-legacy/datastore/js/getting-started/10_preReq.mdx new file mode 100644 index 00000000000..f50081ddc48 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/getting-started/10_preReq.mdx @@ -0,0 +1,5 @@ +* Install [Amplify CLI](/cli) version 4.21.0 or later by running: + +import all0 from "/src/fragments/cli-install-block.mdx"; + + diff --git a/src/fragments/lib-legacy/datastore/js/getting-started/30_platformIntegration.mdx b/src/fragments/lib-legacy/datastore/js/getting-started/30_platformIntegration.mdx new file mode 100644 index 00000000000..0c96a187c3c --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/getting-started/30_platformIntegration.mdx @@ -0,0 +1,97 @@ +The fastest way to get started is using the `amplify-app` npx script. + + +
+ + + + +Start with [Create React app](https://create-react-app.dev): + +```bash +npx create-react-app amplify-datastore --use-npm +cd amplify-datastore +npx amplify-app@latest +``` + + + + +Start with the [React Native CLI](https://reactnative.dev/docs/getting-started): + +```bash +npx react-native init AmplifyDataStoreRN +cd AmplifyDataStoreRN +npx amplify-app@latest +npm install aws-amplify @react-native-community/netinfo @react-native-async-storage/async-storage +``` + +You will also need to install the pod dependencies for iOS: + +```sh +npx pod-install +``` + +**Use SQLite for enhanced performance (optional)** + +React Native CLI apps can now use SQLite with DataStore. SQLite offers considerable performance advantages over the default "AsyncStorage" database. + +To enable SQLite with DataStore for enhanced local database performance, follow the steps below: + +```sh +npx react-native init AmplifyDataStoreRN +cd AmplifyDataStoreRN +npx amplify-app@latest +npm install aws-amplify @aws-amplify/datastore-storage-adapter react-native-sqlite-storage @react-native-community/netinfo @react-native-async-storage/async-storage +npx pod-install +``` + +Then configure the SQLite storage adapter with DataStore in your app: + +```js +import { DataStore } from 'aws-amplify'; +import { SQLiteAdapter } from '@aws-amplify/datastore-storage-adapter/SQLiteAdapter'; + +DataStore.configure({ + storageAdapter: SQLiteAdapter +}); +``` + + + + +Start with the [Expo](https://docs.expo.dev/): + +```bash +expo init AmplifyDataStoreExpo +cd AmplifyDataStoreExpo +npx amplify-app@latest +expo install aws-amplify @react-native-community/netinfo @react-native-async-storage/async-storage +``` + +**Use SQLite for enhanced performance (optional)** + +Expo built apps can now use SQLite with DataStore. SQLite offers considerable performance advantages over the default "AsyncStorage" database. + +To enable SQLite with DataStore for enhanced local database performance, follow the steps below: + +```sh +expo init AmplifyDataStoreExpo +cd AmplifyDataStoreExpo +npx amplify-app@latest +expo install aws-amplify @aws-amplify/datastore-storage-adapter expo-sqlite expo-file-system @react-native-community/netinfo @react-native-async-storage/async-storage +``` + +Then configure the SQLite storage adapter with DataStore in your app: + +```js +import { DataStore } from 'aws-amplify'; +import { ExpoSQLiteAdapter } from '@aws-amplify/datastore-storage-adapter/ExpoSQLiteAdapter'; + +DataStore.configure({ + storageAdapter: ExpoSQLiteAdapter +}); +``` + + + diff --git a/src/fragments/lib-legacy/datastore/js/getting-started/40_codegen.mdx b/src/fragments/lib-legacy/datastore/js/getting-started/40_codegen.mdx new file mode 100644 index 00000000000..b5690df8305 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/getting-started/40_codegen.mdx @@ -0,0 +1,22 @@ +When using `npx amplify-app`, an NPM script named `amplify-modelgen` should be added to your `package.json`. You can **generate model code** with the following command. + +```console +npm run amplify-modelgen +``` + +The following files will be generated. + +``` +src/ +|_ models/ + |_ index.d.ts + |_ index.js + |_ schema.d.ts + |_ schema.js +``` + + + +The `.d.ts` are [TypeScript declaration files](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html). If your project does not use TypeScript, do not worry, those files can still provide most editors a better developer experience, with a more accurate auto-complete and realtime type checking. Worst case scenario they will just be ignored. + + diff --git a/src/fragments/lib-legacy/datastore/js/getting-started/50_initDataStore.mdx b/src/fragments/lib-legacy/datastore/js/getting-started/50_initDataStore.mdx new file mode 100644 index 00000000000..11d6de1b595 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/getting-started/50_initDataStore.mdx @@ -0,0 +1 @@ +When the `@aws-amplify/datastore` dependency is added to the project, the plugin is automatically initialized when you start using it. diff --git a/src/fragments/lib-legacy/datastore/js/getting-started/60_saveSnippet.mdx b/src/fragments/lib-legacy/datastore/js/getting-started/60_saveSnippet.mdx new file mode 100644 index 00000000000..8dc487684d6 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/getting-started/60_saveSnippet.mdx @@ -0,0 +1,12 @@ +```js +try { + await DataStore.save( + new Post({ + title: "My First Post" + }) + ); + console.log("Post saved successfully!"); +} catch (error) { + console.log("Error saving post", error); +} +``` diff --git a/src/fragments/lib-legacy/datastore/js/getting-started/70_querySnippet.mdx b/src/fragments/lib-legacy/datastore/js/getting-started/70_querySnippet.mdx new file mode 100644 index 00000000000..f5912c534b0 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/getting-started/70_querySnippet.mdx @@ -0,0 +1,8 @@ +```js +try { + const posts = await DataStore.query(Post); + console.log("Posts retrieved successfully!", JSON.stringify(posts, null, 2)); +} catch (error) { + console.log("Error retrieving posts", error); +} +``` diff --git a/src/fragments/lib-legacy/datastore/js/other-methods.mdx b/src/fragments/lib-legacy/datastore/js/other-methods.mdx new file mode 100644 index 00000000000..a6c304c1e09 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/other-methods.mdx @@ -0,0 +1,27 @@ +## Clear + +To clear local data from DataStore, use the `clear` method: + +```js +import { DataStore } from 'aws-amplify'; + +await DataStore.clear(); +``` + + + +If your app is has authentication implemented, it is recommended to call `DataStore.clear()` on signin/signout to remove any user-specific data. This method is often important to use for shared device scenarios or where you need to purge the local on-device storage of records for security/privacy concerns. + + + +## Start + +To manually start the sync process, use the `start` method: + +```js +import { DataStore } from 'aws-amplify'; + +await DataStore.start(); +``` + +Synchronization starts automatically whenever you run any of the following methods: `DataStore.query()`, `DataStore.save()`, `DataStore.delete()`, or `DataStore.observe()`. diff --git a/src/fragments/lib-legacy/datastore/js/real-time/observe-query-snippet.mdx b/src/fragments/lib-legacy/datastore/js/real-time/observe-query-snippet.mdx new file mode 100644 index 00000000000..f54f16e13ed --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/real-time/observe-query-snippet.mdx @@ -0,0 +1,18 @@ +```js +const subscription = DataStore.observeQuery( + Post, + p => p.title("beginsWith", "post").rating("gt", 10), + { + sort: s => s.rating(SortDirection.ASCENDING) + } +).subscribe(snapshot => { + const { items, isSynced } = snapshot; + console.log(`[Snapshot] item count: ${items.length}, isSynced: ${isSynced}`); +}); +``` + + + +The default configuration for `observeQuery` has a default `syncPageSize` of 1,000 and a default `maxRecordsToSync` of 10,000. These values can be customized by configuring their values manually in `DataStore.configure`. + + diff --git a/src/fragments/lib-legacy/datastore/js/real-time/observe-snippet.mdx b/src/fragments/lib-legacy/datastore/js/real-time/observe-snippet.mdx new file mode 100644 index 00000000000..307edf64990 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/real-time/observe-snippet.mdx @@ -0,0 +1,38 @@ +```js +const subscription = DataStore.observe(Post).subscribe(msg => { + console.log(msg.model, msg.opType, msg.element); +}); +``` + +Observing changes of a single item by ID. + +```js +const id = '69ddcb63-7e4a-4325-b84d-8592e6dac07b'; + +const subscription = DataStore.observe(Post, id).subscribe(msg => { + console.log(msg.model, msg.opType, msg.element); +}); +``` + +Closing a subscription + +```js +const subscription = DataStore.observe(Post, id).subscribe(msg => { + console.log(msg.model, msg.opType, msg.element); +}); + +// Call unsubscribe to close the subscription +subscription.unsubscribe(); +``` + + + +The `observe` function is asynchronous; however, you should not use `await` like the other DataStore API methods since it is a long running task and you should make it non-blocking (i.e. code after the `DataStore.observe()` call should not wait for its execution to finish). + + + + + +`DataStore.clear()` will remove any active subscriptions. You'll need to re-establish them manually by calling `DataStore.observe()` again after you clear. + + diff --git a/src/fragments/lib-legacy/datastore/js/relational/delete-snippet.mdx b/src/fragments/lib-legacy/datastore/js/relational/delete-snippet.mdx new file mode 100644 index 00000000000..364346aae23 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/relational/delete-snippet.mdx @@ -0,0 +1,4 @@ +```js +const toDelete = await DataStore.query(Post, "123"); +DataStore.delete(toDelete); +``` diff --git a/src/fragments/lib-legacy/datastore/js/relational/query-many-snippet.mdx b/src/fragments/lib-legacy/datastore/js/relational/query-many-snippet.mdx new file mode 100644 index 00000000000..09e432b123b --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/relational/query-many-snippet.mdx @@ -0,0 +1,48 @@ +These many-to-many relationship models can be queried directly to return all models in the relationship: + +```js +// All post editor relationships +const results = await DataStore.query(PostEditor); +``` + +This will return an array of `PostEditor`s with `Post` and `Editor` model instances attached. For example, without metadata, `results` from above would look like this: + +```json +[ + { + "id": "4b66cee3-1436-4d53-910f-7cfe0d955cd8", + "post": { + "id": "d2a96183-938f-4469-9873-944336fb9d9d", + "title": "My first post" + }, + "editor": { + "id": "2cbfdd83-8353-4b0e-ae63-8f7d004c728f", + "username": "Nadia" + } + } +] +``` + +This model instance contains both related models. Use `filter()` or `map()` to retrieve the related model instances: + +```js +// All posts for a given editor +const postsByEditor = (await DataStore.query(PostEditor)).filter( + pe => pe.editor.id === editor.id +).map(pe => pe.post); + +// All editors on a given post +const editorsByPost = (await DataStore.query(PostEditor)).filter( + pe => pe.post.id === post.id +).map(pe => pe.editor); + +// All editors of posts where title contains "first" +const editorsOfFirstPosts = (await DataStore.query(PostEditor)).filter( + pe => pe.post.title.includes("first") +).map(pe => pe.editor); + +// All editors of posts where title is long +const editorsWithLongTitles = (await DataStore.query(PostEditor)).filter( + pd => pe.post.title.length > 20 +).map(pe => pe.editor); +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/js/relational/query-snippet.mdx b/src/fragments/lib-legacy/datastore/js/relational/query-snippet.mdx new file mode 100644 index 00000000000..1be1e00c03d --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/relational/query-snippet.mdx @@ -0,0 +1,10 @@ +```js +const comments = (await DataStore.query(Comment)).filter(c => c.postID === "123"); +``` + +Alternatively: + +```js +const post = await DataStore.query(Post, "123"); +const comments = (await DataStore.query(Comment)).filter(c => c.postID === post.id); +``` diff --git a/src/fragments/lib-legacy/datastore/js/relational/save-many-snippet.mdx b/src/fragments/lib-legacy/datastore/js/relational/save-many-snippet.mdx new file mode 100644 index 00000000000..1a1758f60b8 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/relational/save-many-snippet.mdx @@ -0,0 +1,23 @@ +```js +// first you save the post +const post = await DataStore.save( + new Post({ + title: "My first post", + }) +); + +// secondly, you save the editor/user +const editor = await DataStore.save( + new User({ + username: "Nadia", + }) +); + +// then you save the mode that links a post with an editor +await DataStore.save( + new PostEditor({ + post: post, + editor: editor + }) +); +``` diff --git a/src/fragments/lib-legacy/datastore/js/relational/save-snippet.mdx b/src/fragments/lib-legacy/datastore/js/relational/save-snippet.mdx new file mode 100644 index 00000000000..e1fff0c5b12 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/relational/save-snippet.mdx @@ -0,0 +1,16 @@ +```js +const post = await DataStore.save( + new Post({ + title: "My Post with comments", + rating: 10, + status: PostStatus.ACTIVE + }) +); + +await DataStore.save( + new Comment({ + content: "Loving Amplify DataStore!", + postID: post.id + }) +); +``` diff --git a/src/fragments/lib-legacy/datastore/js/relational/updated-schema.mdx b/src/fragments/lib-legacy/datastore/js/relational/updated-schema.mdx new file mode 100644 index 00000000000..e18a591a09d --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/relational/updated-schema.mdx @@ -0,0 +1,25 @@ +For the examples below with DataStore let's add a new model to the [sample schema](/lib/datastore/getting-started#sample-schema): + +```graphql +enum PostStatus { + ACTIVE + INACTIVE +} + +type Post @model { + id: ID! + title: String! + rating: Int! + status: PostStatus! + # New field with @hasMany + comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) +} + +# New model +type Comment @model { + id: ID! + postID: ID! @index(name: "byPost", sortKeyFields: ["content"]) + post: Post! @belongsTo(fields: ["postID"]) + content: String! +} +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/js/setup-auth-rules/10_multiauth-snippet.mdx b/src/fragments/lib-legacy/datastore/js/setup-auth-rules/10_multiauth-snippet.mdx new file mode 100644 index 00000000000..9d1f2dbc199 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/setup-auth-rules/10_multiauth-snippet.mdx @@ -0,0 +1,11 @@ +```js +import { Amplify, AuthModeStrategyType } from 'aws-amplify'; +import awsconfig from './aws-exports'; + +Amplify.configure({ + ...awsconfig, + DataStore: { + authModeStrategyType: AuthModeStrategyType.MULTI_AUTH + } +}) +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/js/setup-auth-rules/20_function-auth-snippet.mdx b/src/fragments/lib-legacy/datastore/js/setup-auth-rules/20_function-auth-snippet.mdx new file mode 100644 index 00000000000..c4f1d922984 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/setup-auth-rules/20_function-auth-snippet.mdx @@ -0,0 +1,23 @@ +## Configure custom authorization logic with AWS Lambda + +You can implement your own custom API authorization logic using an AWS Lambda function. To add a Lambda function as an authorization mode for your AppSync API, go to the **Settings** section of the **AppSync console**. + +You will need to manage the details of token refreshes in your application code yourself. + +Here's how you can specify a function for handling token refresh when using Lambda as an authorization mode with DataStore: + +```js +import { DataStore } from 'aws-amplify'; + +DataStore.configure({ + authProviders: { + functionAuthProvider: async () => { + const authToken = await refreshAuthToken(); // refreshAuthToken + + return { + token: authToken + } + }, + } +}); +``` diff --git a/src/fragments/lib-legacy/datastore/js/setup-auth-rules/multi-auth.mdx b/src/fragments/lib-legacy/datastore/js/setup-auth-rules/multi-auth.mdx new file mode 100644 index 00000000000..3d30f48de60 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/setup-auth-rules/multi-auth.mdx @@ -0,0 +1,56 @@ +## Configure Multiple Authorization Types + +For some use cases, you will want DataStore to use multiple authorization types. For example, an app might use `API Key` for public content and `Cognito User Pool` for personalized content once the user logs in. + +By default, DataStore uses your API's default authorization type listed in `aws_appsync_authenticationType` in your Amplify configuration. To change the default authorization type, run `amplify update api`. Every network request sent through DataStore uses that authorization type, regardless of the model's `@auth` rule. + +To enable DataStore to use multiple authorization types based on the model's `@auth` rules, add `authModeStrategyType: AuthModeStrategyType.MULTI_AUTH` to your Amplify configuration: + +```js +import { Amplify, AuthModeStrategyType } from 'aws-amplify'; +import awsconfig from './aws-exports'; + +Amplify.configure({ + ...awsconfig, + DataStore: { + authModeStrategyType: AuthModeStrategyType.MULTI_AUTH + } +}) +``` + +This configuration enables DataStore to synchronize data using the model's `@auth` rule provider for each model. + +### Multiple authorization types priority order + +If there are multiple `@auth` rules on a model, the rules will be ranked by priority (see below), and DataStore will attempt the synchronization with each authorization type until one succeeds (or they all fail). + +| Priority | `allow`: AuthStrategy | `provider` | +|:----------|:-----:|:------:| +| 1 | `owner` | `userPools` | +| 2 | `owner` | `oidc` | +| 3 | `group` | `userPools` | +| 4 | `group` | `oidc` | +| 5 | `private` | `userPools` | +| 6 | `private` | `iam` | +| 7 | `public` | `iam` | +| 8 | `public` | `apiKey` | + +If there is **not** an authenticated user session, DataStore will only attempt `public` rules. + +As a fallback, if there are no valid rules for a model, DataStore will attempt synchronization with the default authorization type. + +#### Example with multiple authorization types + +```graphql +type YourModel + @model + @auth( + rules: [ + { allow: owner } + { allow: public, provider: apiKey, operations: [read] } + ] + ) { + ... +} +``` +DataStore will attempt to use owner-based authorization first when synchronizing data if there is an authenticated user. If that request fails for some reason, DataStore will attempt the request again with public authorization. If there is **no** authenticated user, public authorization will be used. diff --git a/src/fragments/lib-legacy/datastore/js/sync/20-savePredicate.mdx b/src/fragments/lib-legacy/datastore/js/sync/20-savePredicate.mdx new file mode 100644 index 00000000000..3cf97effbb6 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/sync/20-savePredicate.mdx @@ -0,0 +1,10 @@ +```js +await DataStore.save( + new Post({ + title: 'My First Post', + rating: 10, + status: PostStatus.INACTIVE + }), + (p) => p.title('beginsWith', '[Amplify]') +); +``` diff --git a/src/fragments/lib-legacy/datastore/js/sync/30-savePredicateComparison.mdx b/src/fragments/lib-legacy/datastore/js/sync/30-savePredicateComparison.mdx new file mode 100644 index 00000000000..ccb945c1c8e --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/sync/30-savePredicateComparison.mdx @@ -0,0 +1,16 @@ +```js +// Tests only against the local state +if (post.title.startsWith('[Amplify]')) { + await DataStore.save(post); +} + +// Only applies the update if the data in the remote backend satisfies the criteria +await DataStore.save( + new Post({ + title: 'My First Post', + rating: 10, + status: PostStatus.INACTIVE + }), + (p) => p.title('beginsWith', '[Amplify]') +); +``` diff --git a/src/fragments/lib-legacy/datastore/js/sync/40-clear.mdx b/src/fragments/lib-legacy/datastore/js/sync/40-clear.mdx new file mode 100644 index 00000000000..e68fa3855b1 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/sync/40-clear.mdx @@ -0,0 +1,7 @@ +```js +Hub.listen('auth', async (data) => { + if (data.payload.event === 'signOut') { + await DataStore.clear(); + } +}); +``` diff --git a/src/fragments/lib-legacy/datastore/js/sync/50-selectiveSync.mdx b/src/fragments/lib-legacy/datastore/js/sync/50-selectiveSync.mdx new file mode 100644 index 00000000000..038bbac3bc5 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/js/sync/50-selectiveSync.mdx @@ -0,0 +1,170 @@ +## Selectively syncing a subset of your data + +By default, DataStore downloads the entire contents of your cloud data source to your local device. +The max number of records that will be stored is configurable [here](https://docs.amplify.aws/lib/datastore/conflict/q/platform/js#example). + +You can utilize selective sync to only persist a subset of your data instead. + +Selective sync works by applying predicates to the base and delta sync queries, as well as to incoming subscriptions. + +```js +import { DataStore, syncExpression } from 'aws-amplify'; +import { Post, Comment } from './models'; + +DataStore.configure({ + syncExpressions: [ + syncExpression(Post, () => { + return post => post.rating('gt', 5); + }), + syncExpression(Comment, () => { + return comment => comment.status('eq', 'active'); + }), + ] +}); +``` + +When DataStore starts syncing, only Posts with `rating > 5` and Comments with `status === 'active'` will be synced down to the user's local store. + + + +Developers should only specify a single `syncExpression` per model. Any subsequent expressions for the same model will be ignored. + + + +### Reevaluate expressions at runtime +Sync expressions get evaluated whenever DataStore starts. +In order to have your expressions reevaluated, you can execute `DataStore.clear()` or `DataStore.stop()` followed by `DataStore.start()`. + +If you have the following expression and you want to change the filter that gets applied at runtime, you can do the following: +```js +let rating = 5; + +DataStore.configure({ + syncExpressions: [ + syncExpression(Post, () => { + return post => post.rating('gt', rating)); + }) + ] +}); + +async function changeSync() { + rating = 1; + await DataStore.stop(); + await DataStore.start(); +} +``` + +Upon calling `DataStore.start()` (or executing a DataStore operation, e.g., `query`, `save`, `delete`, or `observe`), DataStore will reevaluate the `syncExpressions`. + +In the above case, the predicate will contain the value `1`, so all Posts with `rating > 1` will get synced down. + +Keep in mind: `DataStore.stop()` will retain the local store's existing content. Run `DataStore.clear()` to clear the locally-stored contents. + + + +When applying a more restrictive filter, clear the local records first by running `DataStore.clear()` instead: + + + +```js +async function changeSync() { + rating = 8; + await DataStore.clear(); + await DataStore.start(); +} +``` +This will clear the contents of your local store, reevaluate your sync expressions and re-sync the data from the cloud, applying all of the specified predicates to the sync queries. + +You can also have your sync expression return `Predicates.ALL` in order to remove any filtering for that model. This will have the same effect as the default sync behavior. + +```js +let rating = null; + +DataStore.configure({ + syncExpressions: [ + syncExpression(Post, () => { + if (rating) { + return post => post.rating('gt', rating)); + } + + return Predicates.ALL; + }) + ] +}); +``` + + +`DataStore.configure()` should only by called once at the root of your file. + + + +### Async expressions +You can pass a Promise to `syncExpression`: +```js +DataStore.configure({ + syncExpressions: [ + syncExpression(Post, async () => { + const ratingValue = await getRatingValue(); + return post => post.rating('gt', ratingValue); + }) + ] +}); +``` +DataStore will wait for the Promise to resolve before applying the expression to the sync. Async expressions can also be reevaluated at runtime, just like synchronous expressions (see previous section). + +### Shorthand +If you don't need to add any logic to your `syncExpression`, you can use the following shorthand, returning the predicate directly: +```js +DataStore.configure({ + syncExpressions: [ + syncExpression(Post, post => post.rating('gt', 5)) + ] +}); +``` + +### Advanced use case - Query instead of Scan +You can configure selective sync to retrieve items from DynamoDB with a [query operation](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html) against a GSI. By default, the base sync will perform a [scan](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html). Query operations enable a highly efficient and cost-effective data retrieval for customers running DynamoDB at scale. Learn about creating GSIs with the `@index` directive [here](https://docs.amplify.aws/cli/graphql/data-modeling). + +In order to do that, your `syncExpression` should return a predicate that maps to a query expression. + +For example, for the following schema: +```graphql +type User @model { + id: ID! + firstName: String! + lastName: String! @index(name: "byLastName", sortKeyFields: ["createdAt"]) + createdAt: AWSDateTime! +} +``` + +Both of these sync expressions will result in AWS AppSync retrieving records from Amazon DynamoDB via a query operation: + +```js +DataStore.configure({ + syncExpressions: [ + syncExpression(User, () => { + const lastName = await getLastNameForSync(); + return user => user.lastName('eq', lastName) + }) + ] +}); + +// OR + +DataStore.configure({ + syncExpressions: [ + syncExpression(User, () => { + const lastName = await getLastNameForSync(); + return user => user.lastName('eq', lastName).createdAt('gt', '2020-10-10') + }) + ] +}); +``` + +To construct a query expression, return a predicate with the primary key of the GSI. You can only use the `eq` operator with this predicate. + +For the schema defined above `user => user.lastName('eq', 'Bobby')` is a valid query expression. + +Optionally, you can also chain the sort key to this expression, using any of the following operators: `eq | ne | le | lt | ge | gt | beginsWith | between`. + +E.g., `user => user.lastName('eq', 'Bobby').createdAt('gt', '2020-10-10')`. diff --git a/src/fragments/lib-legacy/datastore/native_common/callout/datastore-clear-with-auth.mdx b/src/fragments/lib-legacy/datastore/native_common/callout/datastore-clear-with-auth.mdx new file mode 100644 index 00000000000..2f13336d29d --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/callout/datastore-clear-with-auth.mdx @@ -0,0 +1,5 @@ + + +If your app uses authentication, it is recommended to call `DataStore.clear()` on sign-in or sign-out to remove any user-specific data. In scenarios where a mobile device can be shared by several users, calling `DataStore.clear()` will ensure that data does not leak from one user to another. + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/native_common/codegen.mdx b/src/fragments/lib-legacy/datastore/native_common/codegen.mdx new file mode 100644 index 00000000000..8265d1e8878 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/codegen.mdx @@ -0,0 +1,10 @@ + +Models can also be generated using the Amplify CLI directly. + +In your terminal, make sure you are in your project/root folder and **execute the codegen command**: + +```console +amplify codegen models +``` + +You can **find the generated files** at `amplify/generated/models/`. \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/native_common/conflict.mdx b/src/fragments/lib-legacy/datastore/native_common/conflict.mdx new file mode 100644 index 00000000000..df38998fb2c --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/conflict.mdx @@ -0,0 +1,54 @@ + +If data synchronization is enabled via [AppSync](https://aws.amazon.com/appsync/), there can be different versions of the same object on the client and server. Multiple clients may have updated their respective copies of an object. DataStore will converge different object versions by applying conflict detection and resolution strategies. The default resolution is called `Auto Merge`. This strategy allows collections to grow, and prefers server-side versions of single-field data. Other strategies include `Optimistic Concurrency` control and `Custom Lambda` functions. For more information, see the [AWS AppSync documentation on conflict handling](https://docs.aws.amazon.com/appsync/latest/devguide/conflict-detection-and-sync.html). + +## Custom conflict resolution + +To select a different conflict resolution strategy, navigate into your project from a terminal and run `amplify update api`. Choose *Conflict resolution strategy* to change the conflict detection and resolution strategies. + +```console +? Please select from one of the below mentioned services: + `GraphQL` +... +? Select a setting to edit + `Conflict resolution strategy` +? Select the default resolution strategy + Auto Merge +❯ Optimistic Concurrency + Custom Lambda + Learn More +``` + +### Per model configuration + +Note that this flow will also allow you to change the strategy on each individual GraphQL type, though it is recommended to use the same strategy for your whole schema unless you have an advanced use case: + +``` +? Do you want to override default per model settings? Yes +? Select the models from below: +❯◉ Post + ◯ PostEditor + ◯ User + +? Select the resolution strategy for Post model Custom Lambda +? Select from the options below (Use arrow keys) +❯ Create a new Lambda Function + Existing Lambda Function +``` + +## Custom configuration + +import js0 from "/src/fragments/lib/datastore/js/conflict.mdx"; + + + +import ios1 from "/src/fragments/lib/datastore/ios/conflict.mdx"; + + + +import android2 from "/src/fragments/lib/datastore/android/conflict.mdx"; + + + +import flutter3 from "/src/fragments/lib/datastore/flutter/conflict.mdx"; + + diff --git a/src/fragments/lib-legacy/datastore/native_common/data-access.mdx b/src/fragments/lib-legacy/datastore/native_common/data-access.mdx new file mode 100644 index 00000000000..5871065ae0c --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/data-access.mdx @@ -0,0 +1,220 @@ +import js0 from "/src/fragments/lib/datastore/js/data-access/importing-datastore-snippet.mdx"; + + + +## Create and update + +To write data to the DataStore, pass an instance of a model to `Amplify.DataStore.save()`: + +import js1 from "/src/fragments/lib/datastore/js/data-access/save-snippet.mdx"; + + + +import ios2 from "/src/fragments/lib/datastore/ios/data-access/save-snippet.mdx"; + + + +import android3 from "/src/fragments/lib/datastore/android/data-access/save-snippet.mdx"; + + + +import flutter4 from "/src/fragments/lib/datastore/flutter/data-access/save-snippet.mdx"; + + + +The `save` method creates a new record, or in the event that one already exists in the local store, it updates the record. + +import js5 from "/src/fragments/lib/datastore/js/data-access/update-snippet.mdx"; + + + +import ios6 from "/src/fragments/lib/datastore/ios/data-access/update-snippet.mdx"; + + + +import android7 from "/src/fragments/lib/datastore/android/data-access/update-snippet.mdx"; + + + +import flutter8 from "/src/fragments/lib/datastore/flutter/data-access/update-snippet.mdx"; + + + + + + **Avoid working with stale data!** + + Model instances which store values, such as those from the results of a `DataStore.Query` operation, can become stale and outdated when their properties are updated. This can be the result of a manual change or even a side effect of [real time data](/lib/datastore/sync#distributed-data) being received by the application. In order to ensure you are performing mutations on the latest committed state to the system, either perform a query directly before the `DataStore.save()` operation or observe the model to keep the state updated at all times and perform mutations on that latest state referencing the model instance. The preceding example demonstrates one approach. The following example demonstrates the `observeQuery` approach. + + + +import js_observe_update from "/src/fragments/lib/datastore/js/data-access/observe-update-snippet.mdx"; + + + +import ios_observe_update from "/src/fragments/lib/datastore/ios/data-access/observe-update-snippet.mdx"; + + + +import android_observe_update from "/src/fragments/lib/datastore/android/data-access/observe-update-snippet.mdx"; + + + +import flutter_observe_update from "/src/fragments/lib/datastore/flutter/data-access/observe-update-snippet.mdx"; + + + +## Delete + +To delete an item, simply pass in an instance. + +import js9 from "/src/fragments/lib/datastore/js/data-access/delete-snippet.mdx"; + + + +import ios10 from "/src/fragments/lib/datastore/ios/data-access/delete-snippet.mdx"; + + + +import android11 from "/src/fragments/lib/datastore/android/data-access/delete-snippet.mdx"; + + + +import flutter12 from "/src/fragments/lib/datastore/flutter/data-access/delete-snippet.mdx"; + + + +## Query Data + +Queries are performed against the _local store_. When cloud synchronization is enabled, the local store is updated in the background by the DataStore Sync Engine. + +For more advanced filtering, such as matching arbitrary field values on an object, you can supply a query predicate. + +import js13 from "/src/fragments/lib/datastore/js/data-access/query-basic-snippet.mdx"; + + + +import ios14 from "/src/fragments/lib/datastore/ios/data-access/query-basic-snippet.mdx"; + + + +import android15 from "/src/fragments/lib/datastore/android/data-access/query-basic-snippet.mdx"; + + + +import flutter16 from "/src/fragments/lib/datastore/flutter/data-access/query-basic-snippet.mdx"; + + + +import js17 from "/src/fragments/lib/datastore/js/data-access/query-single-item-snippet.mdx"; + + + +### Predicates + +Predicates are filters that can be used to match items in the DataStore. When applied to a query(), they constrain the returned results. When applied to a save(), they act as a pre-requisite for updating the data. You can match against fields in your schema by using the following predicates: + +**Strings:** `eq | ne | le | lt | ge | gt | contains | notContains | beginsWith | between` + +**Numbers:** `eq | ne | le | lt | ge | gt | between` + +**Lists:** `contains | notContains` + +For example if you wanted a list of all `Post` Models that have a `rating` greater than 4: + +import js18 from "/src/fragments/lib/datastore/js/data-access/query-predicate-snippet.mdx"; + + + +import ios19 from "/src/fragments/lib/datastore/ios/data-access/query-predicate-snippet.mdx"; + + + +import android20 from "/src/fragments/lib/datastore/android/data-access/query-predicate-snippet.mdx"; + + + +import flutter21 from "/src/fragments/lib/datastore/flutter/data-access/query-predicate-snippet.mdx"; + + + +Multiple conditions can also be used, like the ones defined in [GraphQL Transform condition statements](/cli/graphql/data-modeling). For example, fetch all posts that have a rating greater than `4` and are `ACTIVE`: + +import js22 from "/src/fragments/lib/datastore/js/data-access/query-predicate-multiple-snippet.mdx"; + + + +import ios23 from "/src/fragments/lib/datastore/ios/data-access/query-predicate-multiple-snippet.mdx"; + + + +import android24 from "/src/fragments/lib/datastore/android/data-access/query-predicate-multiple-snippet.mdx"; + + + +import flutter25 from "/src/fragments/lib/datastore/flutter/data-access/query-predicate-multiple-snippet.mdx"; + + + +Alternatively, the `or` logical operator can also be used: + +import js26 from "/src/fragments/lib/datastore/js/data-access/query-predicate-or-snippet.mdx"; + + + +import ios27 from "/src/fragments/lib/datastore/ios/data-access/query-predicate-or-snippet.mdx"; + + + +import android28 from "/src/fragments/lib/datastore/android/data-access/query-predicate-or-snippet.mdx"; + + + +import flutter29 from "/src/fragments/lib/datastore/flutter/data-access/query-predicate-or-snippet.mdx"; + + + +import js30 from "/src/fragments/lib/datastore/native_common/sort.mdx"; + + + +import ios31 from "/src/fragments/lib/datastore/native_common/sort.mdx"; + + + +import android32 from "/src/fragments/lib/datastore/native_common/sort.mdx"; + + + +import flutter33 from "/src/fragments/lib/datastore/native_common/sort.mdx"; + + + +### Pagination + +Query results can also be paginated by passing in a `page` number (starting at 0) and an optional `limit` (defaults to 100). This will return a list of the first 100 items: + +import js34 from "/src/fragments/lib/datastore/js/data-access/query-pagination-snippet.mdx"; + + + +import ios35 from "/src/fragments/lib/datastore/ios/data-access/query-pagination-snippet.mdx"; + + + +import android36 from "/src/fragments/lib/datastore/android/data-access/query-pagination-snippet.mdx"; + + + +import flutter37 from "/src/fragments/lib/datastore/flutter/data-access/query-pagination-snippet.mdx"; + + + +### Query, then observe changes + + + + To both query _and_ observe subsequent changes to a Model, consider using + [`observeQuery`](/lib/datastore/real-time#displaying-data-during-the-sync-process-with-observequery). + + diff --git a/src/fragments/lib-legacy/datastore/native_common/datastore-events/model-synced.mdx b/src/fragments/lib-legacy/datastore/native_common/datastore-events/model-synced.mdx new file mode 100644 index 00000000000..2f94e32ed62 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/datastore-events/model-synced.mdx @@ -0,0 +1,11 @@ +## modelSynced + +Dispatched once for each model after the model instances have been synced from the cloud + +HubPayload `modelSyncedEvent` contains: +- `modelName` (String): the name of the model that was synced +- `isFullSync` (Bool): `true` if the model was synced with a "full" query to retrieve all models +- `isDeltaSync` (Bool): `true` if the model was synced with a "delta" query to retrieve only changes since the last sync +- `added` (Int): the number of new model instances added to the local store +- `updated` (Int): the number of existing model instances updated in the local store +- `deleted` (Int): the number of model instances deleted from the local store \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/native_common/datastore-events/outbox-mutation-enqueued.mdx b/src/fragments/lib-legacy/datastore/native_common/datastore-events/outbox-mutation-enqueued.mdx new file mode 100644 index 00000000000..89eb0932d3e --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/datastore-events/outbox-mutation-enqueued.mdx @@ -0,0 +1,8 @@ +## outboxMutationEnqueued + +Dispatched when a local change has been newly staged for synchronization with the Cloud + +HubPayload `outboxMutationEvent` contains: +- `modelName` (String): the name of the model that is awaiting publication to the Cloud +- `element`: + - `model` (Model): the model instance that will be published \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/native_common/datastore-events/outbox-mutation-processed.mdx b/src/fragments/lib-legacy/datastore/native_common/datastore-events/outbox-mutation-processed.mdx new file mode 100644 index 00000000000..b11b946d8b4 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/datastore-events/outbox-mutation-processed.mdx @@ -0,0 +1,11 @@ +## outboxMutationProcessed + +Dispatched when a local change has finished synchronization with the Cloud and is updated locally + +HubPayload `outboxMutationEvent` contains: +- `modelName` (String): the name of the model that has finished processing +- `element`: + - `model` (Model): the model instance that is processed + - `_version` (Int): version of the model instance + - `_lastChangedAt` (Int): last change time of model instance (unix time) + - `_deleted` (Bool): true if the model instance has been deleted in Cloud \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/native_common/getting-started.mdx b/src/fragments/lib-legacy/datastore/native_common/getting-started.mdx new file mode 100644 index 00000000000..0b18443817b --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/getting-started.mdx @@ -0,0 +1,204 @@ + +## DataStore with Amplify + +Amplify DataStore provides a programming model for leveraging shared and distributed data without writing additional code for offline and online scenarios, which makes working with distributed, cross-user data just as simple as working with local-only data. + + + +**Note:** this allows you to start persisting data locally to your device with DataStore, even without an AWS account. + + + +## Goal +To setup and configure your application with Amplify DataStore and use it to persist data locally on a device. + +## Prerequisites + +import js0 from "/src/fragments/lib/datastore/js/getting-started/10_preReq.mdx"; + + + +import ios1 from "/src/fragments/lib/datastore/ios/getting-started/10_preReq.mdx"; + + + +import android2 from "/src/fragments/lib/datastore/android/getting-started/10_preReq.mdx"; + + + +import flutter3 from "/src/fragments/lib/datastore/flutter/getting-started/10_preReq.mdx"; + + + +import ios4 from "/src/fragments/lib/datastore/ios/getting-started/20_installLib.mdx"; + + + +import android5 from "/src/fragments/lib/datastore/android/getting-started/20_installLib.mdx"; + + + +import flutter6 from "/src/fragments/lib/datastore/flutter/getting-started/20_installLib.mdx"; + + + +## Setup local development environment + +import js7 from "/src/fragments/lib/datastore/native_common/setup-env.mdx"; + + + +import ios8 from "/src/fragments/lib/datastore/ios/getting-started/30_setupEnv.mdx"; + + + +import android9 from "/src/fragments/lib/datastore/native_common/setup-env-cli.mdx"; + + + +import flutter10 from "/src/fragments/lib/datastore/native_common/setup-env.mdx"; + + + +## Idiomatic persistence + +DataStore relies on platform standard data structures to represent the data schema in an idiomatic way. The persistence language is composed by data types that satisfies the `Model` interface and operations defined by common verbs such as `save`, `query` and `delete`. + +### Data schema + +The first step to create an app backed by a persistent datastore is to **define a schema**. DataStore uses GraphQL schema files as the definition of the application data model. The schema contains data types and relationships that represent the app's functionality. + +### Sample schema + +For the next steps, let's start with a schema for a small blog application. Currently, it has only a single model. New types and constructs will be added to this base schema as more concepts are presented. + +Open the `schema.graphql` file located by default at `amplify/backend/{api_name}/` and **define a model** `Post` as follows. + +```graphql +type Post @model { + id: ID! + title: String! + status: PostStatus! + rating: Int + content: String +} + +enum PostStatus { + ACTIVE + INACTIVE +} +``` + +Now you will to convert the platform-agnostic `schema.graphql` into platform-specific data structures. DataStore relies on code generation to guarantee schemas are correctly converted to platform code. + +Like the initial setup, models can be generated either using the IDE integration or Amplify CLI directly. + +### Code generation: Platform integration + +import js11 from "/src/fragments/lib/datastore/js/getting-started/40_codegen.mdx"; + + + +import ios12 from "/src/fragments/lib/datastore/ios/getting-started/40_codegen.mdx"; + + + +import android13 from "/src/fragments/lib/datastore/android/getting-started/40_codegen.mdx"; + + + +import flutter14 from "/src/fragments/lib/datastore/flutter/getting-started/40_codegen.mdx"; + + + +### Code generation: Amplify CLI + +import js15 from "/src/fragments/lib/datastore/native_common/codegen.mdx"; + + + +import ios16 from "/src/fragments/lib/datastore/native_common/codegen.mdx"; + + + +import android17 from "/src/fragments/lib/datastore/android/getting-started/50_codegenCli.mdx"; + + + +import flutter18 from "/src/fragments/lib/datastore/flutter/getting-started/50_codegenCli.mdx"; + + + +## Initialize Amplify DataStore + +import js19 from "/src/fragments/lib/datastore/js/getting-started/50_initDataStore.mdx"; + + + +import ios20 from "/src/fragments/lib/datastore/ios/getting-started/50_initDataStore.mdx"; + + + +import android21 from "/src/fragments/lib/datastore/android/getting-started/60_initDataStore.mdx"; + + + +import flutter22 from "/src/fragments/lib/datastore/flutter/getting-started/60_initDataStore.mdx"; + + + +## Persistence operations + +Now the application is ready to execute persistence operations. The data will be persisted to a local database, enabling offline-first use cases by default. + +Even though a GraphQL API is already added to your project, the cloud synchronization will only be enabled when the API plugin is initialized and the backend provisioned. See the [Next steps](#next-steps) for more info. + +### Writing to the database + +To write to the database, create an instance of the `Post` model and save it. + +import js23 from "/src/fragments/lib/datastore/js/getting-started/60_saveSnippet.mdx"; + + + +import ios24 from "/src/fragments/lib/datastore/ios/getting-started/60_saveSnippet.mdx"; + + + +import android25 from "/src/fragments/lib/datastore/android/getting-started/70_saveSnippet.mdx"; + + + +import flutter26 from "/src/fragments/lib/datastore/flutter/getting-started/80_saveSnippet.mdx"; + + + +### Reading from the database + +To read from the database, the simplest approach is to query for all records of a given model type. + +import js27 from "/src/fragments/lib/datastore/js/getting-started/70_querySnippet.mdx"; + + + +import ios28 from "/src/fragments/lib/datastore/ios/getting-started/70_querySnippet.mdx"; + + + +import android29 from "/src/fragments/lib/datastore/android/getting-started/80_querySnippet.mdx"; + + + +import flutter30 from "/src/fragments/lib/datastore/flutter/getting-started/70_querySnippet.mdx"; + + + +## Next steps + +Congratulations! You’ve created and retrieved data from the local database. Check out the following links to see other Amplify DataStore use cases and advanced concepts: + +- [Write data](/lib/datastore/data-access#create-and-update) +- [Query data](/lib/datastore/data-access#query-data) +- [Model associations](/lib/datastore/relational) +- [Cloud synchronization](/lib/datastore/sync) +- [Clear local data](/lib/datastore/sync#clear-local-data) diff --git a/src/fragments/lib-legacy/datastore/native_common/how-it-works.mdx b/src/fragments/lib-legacy/datastore/native_common/how-it-works.mdx new file mode 100644 index 00000000000..8e46f4f6f56 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/how-it-works.mdx @@ -0,0 +1,66 @@ + +Amplify DataStore provides a persistent on-device storage repository for you to write, read, and observe changes to data if you are online or offline, and seamlessly sync to the cloud as well as across devices. Data modeling for your application is using GraphQL and converted to Models that are used in JavaScript, iOS, or Android applications. You can use DataStore for your offline use cases in a “local only” mode without an AWS account or provision an entire backend using AWS AppSync and Amazon DynamoDB. DataStore includes Delta Sync using your GraphQL backend and several conflict resolution strategies. + +## How it Works + + + +Amplify DataStore is an on device persistent repository for interacting with your local data while it synchronizes with the cloud. The core idea is to focus on your data modeling in your application with GraphQL, adding any authorization rules or business logic into your application when needed. This can be done using Amplify CLI project functionality (`amplify add auth` or `amplify add function`) as well as the [GraphQL Transformer](/cli/graphql/overview). + +## Model data locally + +Starting with GraphQL schema (with or without an AWS account) a code generation process creates *Models* which are domain native constructs for a programming platform (TypeScript, Java, Swift classes). This "modelgen" process happens using the Amplify CLI which is either done manually in your terminal or using build tools that will invoke the CLI process (NPX scripts, Gradle, Xcode build phase). + +Once Models have been generated, you can operate on these instances with the DataStore API to save, query, update, delete, or observe changes. At runtime models are passed into a *Storage Engine* that has a *Storage Adapter*. The Storage Engine manages a "Model Repository" of Models which were defined by the developer's GraphQL schema as well as "System Models" which are used for both metadata (such as settings) and queueing updates over the network when syncing to the cloud. Amplify ships with default Storage Adapter implementations, such as SQLite and IndexedDB, however the pattern allows for more in the future for community contributions and is not specific to one technology (e.g. SQL vs NoSQL). + +![Image](/images/storage.png) + +When developer application code interacts with the DataStore API, it is the responsibility of the Storage Engine to store the specific Model for a GraphQL type in the Model Repository as well as serialize & deserialize as appropriate for persistence in the specific Storage Adapter representation. This includes conversion from a GraphQL specific type the appropriate structure in that database engine (e.g. `Int` to `Int64`). + +## Sync data to cloud + +If a developer chooses to sync with the cloud, the Amplify CLI will use the GraphQL schema to deploy an AWS AppSync backend with DynamoDB tables for each type and an additional table used for *Delta Sync*. Other AWS services such as Amazon Cognito or AWS Lambda will also be deployed if added to the project. Once this completes the local configuration for the platform (`aws-exports.js` or `amplifyconfiguration.json`) will be generated inside the project and updated with settings and endpoint information. + +> An application should never write to or modify the Delta Sync table. It is internal to the DataStore implementation. + +If the DataStore starts up and sees API information to sync with an AppSync endpoint, it will start an instance of its *Sync Engine*. This component interfaces with the Storage Engine to get updates from the Model Repository. These components use an *Observer* pattern where the Sync Engine publishes events whenever updates happen in it (such as data being added, updated, or deleted) and both the DataStore API and Sync Engine subscribe to this publication stream. This is how the developer knows when updates have happened from the cloud by interacting with the DataStore API, and conversely how the Sync Engine knows when to communicate with the cloud when applications have made updates to data. + +![Image](/images/sync.png) + +As notifications come into the Sync Engine from the Storage Engine it converts information from the Model Repository into GraphQL statements at runtime. This includes subscribing to all create/update/delete operations for each type, as well as running queries or mutations. + +The Sync Engine will run a GraphQL query on first start that hydrates the Storage Engine from the network using a *Base Query*. This defaults to a limit of 100 items at a time and will paginate through up to 1000 items. It will then store a *Last Sync Time* and each time the device goes from an offline to online state, it will use this as an argument in a *Delta Query*. When AppSync receives this Last Sync Time in its argument list, it will only return the changes that have been missed by pulling items in a Delta Table. + +All items (or "objects") are versioned by *Sync Enabled Resolvers* in AppSync using monotonically increasing counters. Clients never update versions, only the service controls versions. The Sync Engine receives new items or updates from GraphQL operations and applies them with their versions to the Storage Engine. When items are updated by application code they are always written to a queue and the Sync Engine sends them to AppSync using the currently known version as an argument (`_version`) in the mutation. + +## Conflict resolution + +When multiple clients send concurrent updates using the same version and conflict resolution is configured, a strategy for conflict resolution will be entered. The default strategy for clients is Automerge where the GraphQL type information is used to inspect the update and compare it to the current item that has been written to your table. Any non-conflicting fields are merged with the item and any lists will have values appended, with the service updating the item version as appropriate. You can change this default to apply version checks to the entire object with *Optimistic Concurrency* where the latest written item to your database will be used with a version check against the incoming record, or alternatively you can use a Lambda function and apply any custom business logic you wish to the process when merging or rejecting updates. In all cases the service controls the versions. For more information on how these conflict resolution rules work please [see the AWS AppSync documentation](https://docs.aws.amazon.com/appsync/latest/devguide/conflict-detection-and-sync.html). + +## Writing data from the AppSync console + +DataStore is designed primarily for developers to not have to focus on the backend and let your application code and workflow create everything. However, there will be some use cases where you will use the AppSync console, a Lambda function, or other out of band processes to write data (such as batch actions or data migrations) and you might send GraphQL operations without the DataStore client. + +In these cases it's important that the selection set of your GraphQL mutation includes all the required fields of the model, including: `_lastChangedAt`, `_version`, and `_deleted` so that the DataStore clients can react to these updates. You will also need to send the **current** object version in the mutation input argument as `_version` so that the service can act accordingly. If you do not send this information the clients will still eventually catch up during the global sync process, but you will not see realtime updates to the client DataStore repositories. An example mutation: + +```graphql +mutation UpdatePost { + updatePost(input: { + id: "12345" + title: "updated title 19:40" + status: ACTIVE + rating: 5 + _version: 7 + }) { + id + title + status + rating + createdAt + updatedAt + _lastChangedAt + _version + _deleted + } +} +``` diff --git a/src/fragments/lib-legacy/datastore/native_common/other-methods.mdx b/src/fragments/lib-legacy/datastore/native_common/other-methods.mdx new file mode 100644 index 00000000000..318b21db199 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/other-methods.mdx @@ -0,0 +1,62 @@ +## Clear + +To stop DataStore sync process and to clear local data from DataStore, use the `clear` method: + +import ios0 from "/src/fragments/lib/datastore/ios/other-methods/10_clear.mdx"; + + + +import android1 from "/src/fragments/lib/datastore/android/other-methods/10_clear.mdx"; + + + +import flutter1 from "/src/fragments/lib/datastore/flutter/other-methods/10_clear.mdx"; + + + +import datastoreClearCallout from '/src/fragments/lib/datastore/native_common/callout/datastore-clear-with-auth.mdx'; + + + +## Start + +import ios2 from "/src/fragments/lib/datastore/ios/other-methods/15_start.mdx"; + + + +import android3 from "/src/fragments/lib/datastore/android/other-methods/15_start.mdx"; + + + +import flutter2 from "/src/fragments/lib/datastore/flutter/other-methods/15_start.mdx"; + + + + +import ios4 from "/src/fragments/lib/datastore/ios/other-methods/20_start.mdx"; + + + +import android5 from "/src/fragments/lib/datastore/android/other-methods/20_start.mdx"; + + + +import flutter3 from "/src/fragments/lib/datastore/flutter/other-methods/20_start.mdx"; + + + +## Stop + +To stop the DataStore sync process, you can use `DataStore.stop()`. This will close the real time subscription connection when your app is no longer interested in updates. You will typically call `DataStore.stop()` just before your application is closed. You can also force your [DataStore sync expressions](/lib/datastore/sync) to be re-evaluated at runtime by calling `stop()` followed by `start()`. + +import ios6 from "/src/fragments/lib/datastore/ios/other-methods/30_stop.mdx"; + + + +import android7 from "/src/fragments/lib/datastore/android/other-methods/30_stop.mdx"; + + + +import flutter4 from "/src/fragments/lib/datastore/flutter/other-methods/30_stop.mdx"; + + diff --git a/src/fragments/lib-legacy/datastore/native_common/real-time.mdx b/src/fragments/lib-legacy/datastore/native_common/real-time.mdx new file mode 100644 index 00000000000..f4d4ad9c010 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/real-time.mdx @@ -0,0 +1,47 @@ +## Observe model mutations in real-time + +You can subscribe to changes on your Models. This reacts dynamically to updates of data to the underlying Storage Engine, which could be the result of GraphQL Subscriptions as well as Queries or Mutations that run against the backing AppSync API if you are synchronizing with the cloud. + +import js0 from "/src/fragments/lib/datastore/js/real-time/observe-snippet.mdx"; + + + +import ios1 from "/src/fragments/lib/datastore/ios/real-time/observe-snippet.mdx"; + + + +import android2 from "/src/fragments/lib/datastore/android/real-time/observe-snippet.mdx"; + + + +import flutter3 from "/src/fragments/lib/datastore/flutter/real-time/observe-snippet.mdx"; + + + +## Observe query results in real-time + +`observeQuery(...)` returns an initial data set, similar to `query(...)`, and also automatically subscribes to subsequent changes to the query. + +The first snapshot returned from `observeQuery` will contain the same results as calling `query` directly on your Model - a collection of all the locally-available records. Subsequent snapshots will be emitted as updates to the dataset are received in the local store. + +While data is syncing from the cloud, snapshots will contain all of the items synced so far and an `isSynced` status of `false`. When the sync process is complete, a snapshot will be emitted with all the records in the local store and an `isSynced` status of `true`. + +In addition to typical real-time use cases, `observeQuery` can be used on app launch to show your customers an initial data set from the local store while new data is being synced from cloud. + +`observeQuery` also accepts the same [predicates](/lib/datastore/data-access#predicates) and [sorting](/lib/datastore/data-access#sort) options as `query`. + +import js1 from "/src/fragments/lib/datastore/js/real-time/observe-query-snippet.mdx"; + + + +import ios2 from "/src/fragments/lib/datastore/ios/real-time/observe-query-snippet.mdx"; + + + +import android3 from "/src/fragments/lib/datastore/android/real-time/observe-query-snippet.mdx"; + + + +import flutter4 from "/src/fragments/lib/datastore/flutter/real-time/observe-query-snippet.mdx"; + + diff --git a/src/fragments/lib-legacy/datastore/native_common/relational.mdx b/src/fragments/lib-legacy/datastore/native_common/relational.mdx new file mode 100644 index 00000000000..1624983c219 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/relational.mdx @@ -0,0 +1,131 @@ + +DataStore has the capability to handle relationships between Models, such as *has one*, *has many*, *belongs to*. In GraphQL this is done with the `@hasOne`, `@hasMany` and `@index` directives as defined in the [GraphQL Transformer documentation](/cli/graphql/data-modeling). + + + +When using `@primaryKey` with DataStore, you must assign it to the `id: ID!` field. + + + +## Updated schema + +import js0 from "/src/fragments/lib/datastore/js/relational/updated-schema.mdx"; + + + +import ios1 from "/src/fragments/lib/datastore/ios/relational/updated-schema.mdx"; + + + +import android2 from "/src/fragments/lib/datastore/android/relational/updated-schema.mdx"; + + + +import flutter3 from "/src/fragments/lib/datastore/flutter/relational/updated-schema.mdx"; + + + +## Saving relations + +In order to save connected models, you will create an instance of the model you wish to connect and pass its ID to `DataStore.save`: + +import js4 from "/src/fragments/lib/datastore/js/relational/save-snippet.mdx"; + + + +import ios5 from "/src/fragments/lib/datastore/ios/relational/save-snippet.mdx"; + + + +import android6 from "/src/fragments/lib/datastore/android/relational/save-snippet.mdx"; + + + +import flutter7 from "/src/fragments/lib/datastore/flutter/relational/save-snippet.mdx"; + + + +## Querying relations + +import js8 from "/src/fragments/lib/datastore/js/relational/query-snippet.mdx"; + + + +import ios9 from "/src/fragments/lib/datastore/ios/relational/query-snippet.mdx"; + + + +import android10 from "/src/fragments/lib/datastore/android/relational/query-snippet.mdx"; + + + +import flutter11 from "/src/fragments/lib/datastore/flutter/relational/query-snippet.mdx"; + + + +## Deleting relations + +When you delete a parent object in a one-to-many relationship, the children will also be removed from the DataStore and mutations for this deletion will be sent over the network. For example, the following operation would remove the Post with id *123* as well as any related comments: + +import js12 from "/src/fragments/lib/datastore/js/relational/delete-snippet.mdx"; + + + +import ios13 from "/src/fragments/lib/datastore/ios/relational/delete-snippet.mdx"; + + + +import android14 from "/src/fragments/lib/datastore/android/relational/delete-snippet.mdx"; + + + +import flutter15 from "/src/fragments/lib/datastore/flutter/relational/delete-snippet.mdx"; + + + +However, in a many-to-many relationship the children are not removed and you must explicitly delete them. + +### Many-to-many + +For many-to-many relationships, you can use the `@manyToMany` directive and specify a `relationName`. Under the hood, Amplify creates a join table and a one-to-many relationship from both models. + +```graphql +enum PostStatus { + ACTIVE + INACTIVE +} + +type Post @model { + id: ID! + title: String! + rating: Int + status: PostStatus + editors: [User] @manyToMany(relationName: "PostEditor") +} + +type User @model { + id: ID! + username: String! + posts: [Post] @manyToMany(relationName: "PostEditor") +} +``` + +import js16 from "/src/fragments/lib/datastore/js/relational/save-many-snippet.mdx"; + + + +import ios17 from "/src/fragments/lib/datastore/ios/relational/save-many-snippet.mdx"; + + + +import android18 from "/src/fragments/lib/datastore/android/relational/save-many-snippet.mdx"; + + + +import flutter19 from "/src/fragments/lib/datastore/flutter/relational/save-many-snippet.mdx"; + + + +import js20 from "/src/fragments/lib/datastore/js/relational/query-many-snippet.mdx"; + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/native_common/schema-updates.mdx b/src/fragments/lib-legacy/datastore/native_common/schema-updates.mdx new file mode 100644 index 00000000000..f633722519f --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/schema-updates.mdx @@ -0,0 +1,27 @@ + +## Update the schema + +Edit the schema and re-run `amplify codegen models`. + +```graphql +enum PostStatus { + ACTIVE + INACTIVE + STAGED # new enum value +} + +type Post @model { + id: ID! + title: String! + rating: Int! + status: PostStatus! +} +``` + +This will evaluate the changes and create a versioned hash if any changes are detected which impact the underlying on-device storage structure. For example, types being added/deleted or fields becoming required/optional. DataStore evaluates this version on startup and if there are changes the **local items on device will be removed and a full sync from AppSync will take place** if you are syncing with the cloud. + +## Local migrations + +Local migrations (i.e. migrations controlled by the developer) on device are not currently supported. Therefore, your local data will be lost when the schema changes. + +If you are syncing with the cloud the structure and items of that **data in your AppSync backend will not be touched** as part of this process. \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/native_common/setup-auth-rules.mdx b/src/fragments/lib-legacy/datastore/native_common/setup-auth-rules.mdx new file mode 100644 index 00000000000..ed22fc33d54 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/setup-auth-rules.mdx @@ -0,0 +1,222 @@ +Amplify gives you the ability to limit which individuals or groups should have access to create, read, update, or delete data on your types by specifying an `@auth` directive. + +Here's a high-level overview of the authorization scenarios we support in the Amplify libraries. Each scenario has options you can tune to fit the needs of your application. + +* [**Owner Based Authorization**](#owner-based-authorization): Limit a model instance's access to an "owner" and defines authorization rules for those owners. Backed by Cognito User Pool. +* [**Static Group Authorization**](#static-group-authorization): Limit a model instance's access to a specific group of users and define authorization rules for that group. Backend by Cognito User Pool. +* [**Owner and Static Group Combined**](#owner-and-static-group-combined): Uses a combination of both *Owner Based Authorization* and *Static Group Authorization* to control ownership and access. +* [**Public Authorization**](#public-authorization): Allow public access to your model instances. Backed by an API Key or IAM. +* [**Private Authorization**](#private-authorization): Allow any signed-in user to access your model instances. Backed by IAM or Cognito User Pool. +* [**Owner based Authorization with OIDC provider**](#owner-based-authorization-with-oidc-provider): Use a 3rd party OIDC Provider to achieve *Owner based authorization*. +* [**Static Group Authorization with OIDC provider**](#static-group-authorization-with-oidc-provider): Use a 3rd party OIDC Provider to achieve *Static group authorization* using a custom `groupClaim`. + +import datastoreClearCallout from '/src/fragments/lib/datastore/native_common/callout/datastore-clear-with-auth.mdx'; + + + +## Commonly used `@auth` rule patterns + +### Per User / Owner Based Data Access + +The following are commonly used patterns for owner based authorization. For more information on how to tune these examples, please see the [CLI documentation on owner based authorization](/cli/graphql/authorization-rules/#per-user--owner-based-data-access). + +* Create/Read/Update/Delete mutations are private to the owner. +```graphql +type YourModel @model @auth(rules: [{ allow: owner }]) { + ... +} +``` + +* Owners can create and delete; other signed-in users can read and update. +```graphql +type YourModel + @model + @auth( + rules: [ + { allow: owner, operations: [create, delete] } + { allow: private, operations: [read, update] } + ] + ) { + ... +} +``` + +### Static Group Authorization +The following are commonly used patterns for static group authorization. For more information on how to tune these examples, please see the [CLI documentation on static group authorization](/cli/graphql/authorization-rules/#user-group-based-data-access). + +* Users belonging to the "Admin" group can CRUD (create, read, update, and delete), others cannot access anything. +```graphql +type YourModel @model @auth(rules: [{ allow: groups, + groups: ["Admin"] }]) { + ... +} +``` + +* Users belonging to the "Admin" group can create and delete, other signed users can read and update. +```graphql +type YourModel + @model + @auth( + rules: [ + { allow: groups, groups: ["Admin"], operations: [create, delete] } + { allow: private, operations: [read, update] } + ] + ) { + ... +} +``` + +### Owner and Static Group Combined +The following are commonly used patterns for combining owner and static group authorization. For more information on how to tune these examples, please see the [CLI documentation on static group authorization](/cli/graphql/authorization-rules#static-group-authorization). + +* Users have their own data, but users who belong to the `Admin` group have access to their data and anyone else in that group. Users in the `Admin` group have the ability to make mutation on behalf of users not in the `Admin` group +```graphql +type YourModel + @model + @auth(rules: [{ allow: owner }, { allow: groups, groups: ["Admin"] }]) { + ... +} +``` + +### Public Data Access +The following are commonly used patterns to grant everyone access. For more information on how to tune these examples, please see the [CLI documentation on public data access](/cli/graphql/authorization-rules/#public-data-access). + +* Auth provider is API Key +```graphql +type YourModel @model @auth(rules: [{ allow: public }]) { + ... +} +``` + +* Auth provider is IAM +```graphql +type YourModel @model @auth(rules: [{ allow: public, provider: iam }]) { + ... +} +``` + +### Signed-in User Data Access +The following are commonly used patterns for private authorization. For more information on how to tune these examples, please see the [CLI documentation on signed-in user data access](https://docs.amplify.aws/cli/graphql/authorization-rules/#signed-in-user-data-access). + +* Cognito user pool authenticated users can CRUD all posts, regardless of who created it. Guest users do not have access. +```graphql +type YourModel @model @auth(rules: [{ allow: private }]) { + ... +} +``` +* IAM authenticated users can CRUD all posts, regardless of who created it. Guest users do not have access: +```graphql +type YourModel @model @auth(rules: [{ allow: private, provider: iam }]) { + ... +} +``` + +### Owner based Authorization with OIDC provider +The following are commonly used patterns for owner based authorization using a 3rd party OIDC provider (e.g. Facebook, Google, etc...). For more information on how to tune these examples, please see the [CLI documentation on using an oidc authorization provider](/cli/graphql/authorization-rules/#using-oidc-authorization-provider). + +* Using a 3rd party OIDC provider to achieve owner based authorization. +```graphql +type YourModel + @model + @auth(rules: [{ allow: owner, provider: oidc, identityClaim: "sub" }]) { + ... +} +``` + +import android0 from "/src/fragments/lib/datastore/android/setup-auth-rules/owner_based_auth_oidc.mdx"; + + + +import ios1 from "/src/fragments/lib/datastore/ios/setup-auth-rules/owner_based_auth_oidc.mdx"; + + + +import flutterOidc from "/src/fragments/lib/datastore/flutter/setup-auth-rules/owner_based_auth_oidc.mdx"; + + + +### Static Group Authorization with OIDC provider +The following are commonly used patterns for using `groupClaims` to achieve group based authorization using a 3rd party OIDC provider. For more information on how to tune these examples, please see the [CLI documentation on static group authorization](/cli/graphql/authorization-rules#custom-claims). + +* Using a custom value for `groupClaim` to achieve static group authorization with a 3rd party OIDC provider. +```graphql +type YourModel + @model + @auth( + rules: [ + { + allow: groups + provider: oidc + groups: ["Admin"] + groupClaim: "https://myapp.com/claims/groups" + } + ] + ) { + ... +} +``` + +## Configure Multiple Authorization Types + +For some use cases, you will want DataStore to use multiple authorization types. For example, an app might use `API Key` for public content and `Cognito User Pool` for personalized content once the user logs in. + +By default, DataStore uses your API's default authorization type specified in the `amplifyconfiguration.json`/`.dart`/`aws-exports.js` file. Every network request sent through DataStore uses that authorization type, regardless of the model's `@auth` rule. To change the default authorization type, run `amplify update api`. + +To enable DataStore to use multiple authorization types based on the model's `@auth` rules, run `amplify update api` to configure additional auth types and deploy by running `amplify push`. Then, configure the "auth mode strategy" when initializing DataStore: + +import ios2 from "/src/fragments/lib/datastore/ios/setup-auth-rules/10_multiauth-snippet.mdx"; + + + +import android3 from "/src/fragments/lib/datastore/android/setup-auth-rules/10_multiauth-snippet.mdx"; + + + +import js4 from "/src/fragments/lib/datastore/js/setup-auth-rules/10_multiauth-snippet.mdx"; + + + +import flutterMultiAuth from "/src/fragments/lib/datastore/flutter/setup-auth-rules/10_multiauth-snippet.mdx"; + + + +This configuration enables DataStore to synchronize data using the model's `@auth` rule provider for each model. + +### Multiple authorization types priority order + +If there are multiple `@auth` rules on a model, the rules will be ranked by priority (see below), and DataStore will attempt the synchronization with each authorization type until one succeeds (or they all fail). + +| Priority | `allow`: AuthStrategy | `provider` | +|:----------|:-----:|:------:| +| 1 | `owner` | `userPools` | +| 2 | `owner` | `oidc` | +| 3 | `group` | `userPools` | +| 4 | `group` | `oidc` | +| 5 | `private` | `userPools` | +| 6 | `private` | `iam` | +| 7 | `public` | `iam` | +| 8 | `public` | `apiKey` | + +If there is **not** an authenticated user session, DataStore will only attempt `public` rules. + +If a model has no auth rules defined, DataStore will continue to use the default authorization type from `amplifyconfiguration.json`/`.dart`. + +#### Example with multiple authorization types + +```graphql +type YourModel + @model + @auth( + rules: [ + { allow: owner } + { allow: public, provider: apiKey, operations: [read] } + ] + ) { + ... +} +``` +DataStore will attempt to use owner-based authorization first when synchronizing data if there is an authenticated user. If that request fails for some reason, DataStore will attempt the request again with public authorization. If there is **no** authenticated user, public authorization will be used. + +import js5 from "/src/fragments/lib/datastore/js/setup-auth-rules/20_function-auth-snippet.mdx"; + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/native_common/setup-env-cli.mdx b/src/fragments/lib-legacy/datastore/native_common/setup-env-cli.mdx new file mode 100644 index 00000000000..6017fd86c84 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/setup-env-cli.mdx @@ -0,0 +1,51 @@ +To use Amplify, you must first initialize it for use in your project. If you haven't already done so, run this command: +```bash +amplify init +``` + +The base structure for a DataStore app is created by adding a new GraphQL API to your app. + +```console +# For new APIs +amplify add api + +# For existing APIs +amplify update api +``` + +The CLI will prompt you to configure your API. Select **GraphQL** as the API type and reply to the questions as shown below. Conflict detection is **required** when using the DataStore to sync data with the cloud. + +```console +? Please select from one of the below mentioned services: + `GraphQL` +? Here is the GraphQL API that we will create. Select a setting to edit or continue: + `Name` +? Provide API name: + `BlogAppApi` +? Here is the GraphQL API that we will create. Select a setting to edit or continue: + `Authorization modes: API key (default, expiration time: 7 days from now)` +? Choose the default authorization type for the API + `API key` +? Enter a description for the API key: + `BlogAPIKey` +? After how many days from now the API key should expire (1-365): + `365` +? Configure additional auth types? + `No` +? Here is the GraphQL API that we will create. Select a setting to edit or continue: + `Conflict detection (required for DataStore): Disabled` +? Enable conflict detection? + `Yes` +? Select the default resolution strategy + `Auto Merge` +? Here is the GraphQL API that we will create. Select a setting to edit or continue: + `Continue` +? Choose a schema template + `Single object with fields (e.g., “Todo” with ID, name, description)` +``` + + + +**Troubleshooting:** Cloud sync will fail without the **conflict detection** configuration. To enable it for an existing project, run `amplify update api` and choose **Enable DataStore for entire API**. + + diff --git a/src/fragments/lib-legacy/datastore/native_common/setup-env.mdx b/src/fragments/lib-legacy/datastore/native_common/setup-env.mdx new file mode 100644 index 00000000000..95a2106eab2 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/setup-env.mdx @@ -0,0 +1,19 @@ +In order to setup your local development environment, you have two options. + +### Option 1: Platform integration + +import js1 from "/src/fragments/lib/datastore/js/getting-started/30_platformIntegration.mdx"; + + + +import flutter3 from "/src/fragments/lib/datastore/flutter/getting-started/30_platformIntegration.mdx"; + + + +### Option 2: Use Amplify CLI + +Instead of using the platform integration, you can alternatively use the Amplify CLI on its own. This option is particularly **useful for existing projects** where Amplify is already configured and you want to add DataStore to it. + +import all0 from "/src/fragments/lib/datastore/native_common/setup-env-cli.mdx"; + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/native_common/sort.mdx b/src/fragments/lib-legacy/datastore/native_common/sort.mdx new file mode 100644 index 00000000000..1a0ea51f958 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/sort.mdx @@ -0,0 +1,39 @@ +### Sort + +Query results can also be sorted by one or more fields. + +For example, to sort all `Post` objects by `rating` in ascending order: + +import js0 from "/src/fragments/lib/datastore/js/data-access/query-sort-snippet.mdx"; + + + +import ios1 from "/src/fragments/lib/datastore/ios/data-access/query-sort-snippet.mdx"; + + + +import android2 from "/src/fragments/lib/datastore/android/data-access/query-sort-snippet.mdx"; + + + +import flutter3 from "/src/fragments/lib/datastore/flutter/data-access/query-sort-snippet.mdx"; + + + +To get all `Post` objects sorted first by `rating` in ascending order, and then by `title` in descending order: + +import js4 from "/src/fragments/lib/datastore/js/data-access/query-sort-multiple-snippet.mdx"; + + + +import ios5 from "/src/fragments/lib/datastore/ios/data-access/query-sort-multiple-snippet.mdx"; + + + +import android6 from "/src/fragments/lib/datastore/android/data-access/query-sort-multiple-snippet.mdx"; + + + +import flutter7 from "/src/fragments/lib/datastore/flutter/data-access/query-sort-multiple-snippet.mdx"; + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/datastore/native_common/sync-distributed-data.mdx b/src/fragments/lib-legacy/datastore/native_common/sync-distributed-data.mdx new file mode 100644 index 00000000000..0af11192435 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/sync-distributed-data.mdx @@ -0,0 +1,47 @@ +## Distributed data + +When working with distributed data, it is important to be mindful about the state of the local and the remote systems. DataStore tries to make that as simple as possible for you; however, some scenarios might require some consideration. + +For instance, when updating or deleting data, one has to consider that the state of the local data might be out-of-sync with the backend. This scenario can affect how conditions should be implemented. + +### Update and delete with predicate + +For such scenarios both the `save()` and the `delete()` APIs support an optional predicate which will be sent to the backend and executed against the remote state. + +import js0 from "/src/fragments/lib/datastore/js/sync/20-savePredicate.mdx"; + + + +import ios1 from "/src/fragments/lib/datastore/ios/sync/20-savePredicate.mdx"; + + + +import android2 from "/src/fragments/lib/datastore/android/sync/20-savePredicate.mdx"; + + + +import flutterSavePredicateExample from "/src/fragments/lib/datastore/flutter/sync/20-savePredicate.mdx"; + + + +There's a difference between the traditional local condition check using `if/else` constructs and the predicate in the `save()` and `delete()` APIs as you can see in the example below. + +import js3 from "/src/fragments/lib/datastore/js/sync/30-savePredicateComparison.mdx"; + + + +import ios4 from "/src/fragments/lib/datastore/ios/sync/30-savePredicateComparison.mdx"; + + + +import android5 from "/src/fragments/lib/datastore/android/sync/30-savePredicateComparison.mdx"; + + + +import flutterSavePredicateComparisonExample from "/src/fragments/lib/datastore/flutter/sync/30-savePredicateComparison.mdx"; + + + +### Conflict detection and resolution + +When concurrently updating the data in multiple places, it is likely that some conflict might happen. For most of the cases the default *Auto-merge* algorithm should be able to resolve conflicts. However, there are scenarios where the algorithm won't be able to be resolved, and in these cases, a more advanced option is available and will be described in detail in the [conflict resolution](https://docs.amplify.aws/lib/datastore/conflict) section. diff --git a/src/fragments/lib-legacy/datastore/native_common/sync.mdx b/src/fragments/lib-legacy/datastore/native_common/sync.mdx new file mode 100644 index 00000000000..a2c38a49cc7 --- /dev/null +++ b/src/fragments/lib-legacy/datastore/native_common/sync.mdx @@ -0,0 +1,124 @@ + +Once you're happy with your application, you can start syncing with the cloud by provisioning a backend from your project. DataStore can connect to remote backend and automatically sync all locally saved data using GraphQL as a data protocol. + + + +**Best practice:** it is recommended to develop without cloud synchronization enabled initially so you can change the schema as your application takes shape without the impact of having to update the provisioned backend. Once you are satisfied with the stability of your data schema, setup cloud synchronization as described below and the data saved locally will be synchronized to the cloud automatically. + + + +## Setup cloud sync + +Synchronization between offline and online data can be tricky. DataStore goal is to remove that burden from the application code and handle all data consistency and reconciliation between local and remote behind the scenes, while developers focus on their application logic. Up to this point the focus was to setup a local datastore that works offline and has all the capabilities you would expect from a data persistence framework. + +The next step is to make sure the local saved data is synchronized with a cloud backend powered by [AWS AppSync](https://aws.amazon.com/appsync/). + + + +**Note:** Syncing data between the cloud and the local device starts automatically whenever you run any DataStore operation after your app is setup. + + + +import ios0 from "/src/fragments/lib/datastore/ios/sync/10-installPlugin.mdx"; + + + +import android1 from "/src/fragments/lib/datastore/android/sync/10-installPlugin.mdx"; + + + +import flutter2 from "/src/fragments/lib/datastore/flutter/sync/10-installPlugin.mdx"; + + + +### Push the backend to the cloud + +By now you should have a backend created with conflict detection enabled, as described in the [Getting started](/lib/datastore/getting-started) guide. + +**Check the status of the backend** to verify if it is already provisioned in the cloud. + +```console +amplify status +``` + +You should see a table similar to this one. + +``` +| Category | Resource name | Operation | Provider plugin | +| -------- | ----------------- | --------- | ----------------- | +| Api | amplifyDatasource | No Change | awscloudformation | +``` + + + +**Troubleshooting:** if `amplify status` gives you an error saying *"You are not working inside a valid Amplify project"*, make sure you run `amplify init` before the next step. + + + +In case `Operation` says `Create` or `Update` you need to **push the backend to the cloud**. + +```console +amplify push +``` + + + +**AWS credentials needed.** At this point an AWS account is required. If you have never run `amplify configure` before, do it so and follow the steps to configure Amplify with your AWS account. Details can be found in the [Configure the Amplify CLI](/cli/start/install#configure-the-amplify-cli) guide. + + + +## Existing backend + +DataStore can connect to an existing AWS AppSync backend that has been deployed from another project, no matter the platform it was originally created in. In these workflows it is best to work with the CLI directly by running an `amplify pull` command from your terminal and then generating models afterwards, using the process described in the [Getting started](/lib/datastore/getting-started#idiomatic-persistence-models) guide. + +For more information on this workflow please see the [Multiple Frontends documentation](/cli/teams/multi-frontend). + +import distributedDataFragment from "/src/fragments/lib/datastore/native_common/sync-distributed-data.mdx"; + + + +## Clear local data + +`Amplify.DataStore.clear()` provides a way for you to clear all local data if needed. This is a destructive operation but the **remote data will remain intact**. When the next sync happens, data will be pulled into the local storage again and reconstruct the local data. + +One common use for `clear()` is to manage different users sharing the same device or even as a development-time utility. + + + +**Note:** In case multiple users share the same device and your schema defines user-specific data, make sure you call `Amplify.DataStore.clear()` when switching users. Visit [Auth events](/lib/auth/auth-events) for all authentication related events. + + + +import js6 from "/src/fragments/lib/datastore/js/sync/40-clear.mdx"; + + + +import ios7 from "/src/fragments/lib/datastore/ios/sync/40-clear.mdx"; + + + +import android8 from "/src/fragments/lib/datastore/android/sync/40-clear.mdx"; + + + +import flutter9 from "/src/fragments/lib/datastore/flutter/sync/40-clear.mdx"; + + + +This is a simple yet effective example. However, in a real scenario you might want to only call `clear()` when a different user is `signedIn` in order to avoid clearing the database for a repeated sign-in of the same user. + +import js10 from "/src/fragments/lib/datastore/js/sync/50-selectiveSync.mdx"; + + + +import android11 from "/src/fragments/lib/datastore/android/sync/50-selectiveSync.mdx"; + + + +import ios12 from "/src/fragments/lib/datastore/ios/sync/50-selectiveSync.mdx"; + + + +import flutter13 from "/src/fragments/lib/datastore/flutter/sync/50-selectiveSync.mdx"; + + diff --git a/src/fragments/lib-legacy/debugging/android/dev-menu/setup.mdx b/src/fragments/lib-legacy/debugging/android/dev-menu/setup.mdx new file mode 100644 index 00000000000..e231310bebf --- /dev/null +++ b/src/fragments/lib-legacy/debugging/android/dev-menu/setup.mdx @@ -0,0 +1,37 @@ +To enable the developer menu, pass a custom configuration when configuring Amplify: + + + + +```java +AmplifyConfiguration config = AmplifyConfiguration.builder(getApplicationContext()) + .devMenuEnabled(true) + .build(); +Amplify.configure(config, getApplicationContext()); +``` + + + + +```kotlin +val config = AmplifyConfiguration.builder(applicationContext) + .devMenuEnabled(true) + .build() +Amplify.configure(config, applicationContext) +``` + + + + +Next, run the following commands from the Android project directory to add a copy of the file `./amplify/.config/local-env-info.json` to the `./app/src/main/res/raw/` directory in your Android project and rename the copy to `localenvinfo.json`: + +```bash +cp amplify/.config/local-env-info.json app/src/main/res/raw +mv app/src/main/res/raw/local-env-info.json app/src/main/res/raw/localenvinfo.json +``` + + + +If the `amplify` directory is not in your Android project folder, please follow the [project setup instructions](https://docs.amplify.aws/lib/project-setup/prereq/q/platform/android). The file `local-env-info.json` contains environment information that will be displayed on the developer menu. + + diff --git a/src/fragments/lib-legacy/debugging/android/dev-menu/usage.mdx b/src/fragments/lib-legacy/debugging/android/dev-menu/usage.mdx new file mode 100644 index 00000000000..f0e35624b33 --- /dev/null +++ b/src/fragments/lib-legacy/debugging/android/dev-menu/usage.mdx @@ -0,0 +1,3 @@ +Shake the device to access the developer menu during a debug build of your app. An emulator can be shaken by selecting the more option (denoted by three dots) next to the emulator, navigating to the “Virtual Sensors” tab, clicking the “Move” button, and then moving the emulator shown within the “Virtual Sensors” tab back and forth a few times. + +![Shake Android Emulator](/images/debugging/shakeAndroidEmulator.gif) \ No newline at end of file diff --git a/src/fragments/lib-legacy/debugging/ios/dev-menu/setup.mdx b/src/fragments/lib-legacy/debugging/ios/dev-menu/setup.mdx new file mode 100644 index 00000000000..73e4e74dd33 --- /dev/null +++ b/src/fragments/lib-legacy/debugging/ios/dev-menu/setup.mdx @@ -0,0 +1,15 @@ +In your project, drag and drop the file `./amplify/.config/local-env-info.json` to xcode. This file contains environment information that will be displayed on the developer menu. + + + +If you do not see the `amplify` directory, please follow the [project setup instructions](https://docs.amplify.aws/lib/project-setup/prereq/q/platform/ios). + + + +To be able to activate the developer menu when running a debug build of your app, add the following line of code before calling `Amplify.configure()`: + +``` +Amplify.enableDevMenu(contextProvider: obj1) +``` + +where `obj1` is an object that implements `DevMenuPresentationContextProvider`. \ No newline at end of file diff --git a/src/fragments/lib-legacy/debugging/ios/dev-menu/usage.mdx b/src/fragments/lib-legacy/debugging/ios/dev-menu/usage.mdx new file mode 100644 index 00000000000..ad91cd9fc5b --- /dev/null +++ b/src/fragments/lib-legacy/debugging/ios/dev-menu/usage.mdx @@ -0,0 +1 @@ +Long press the screen to access the developer menu during a debug build of your app. \ No newline at end of file diff --git a/src/fragments/lib-legacy/debugging/native_common/dev-menu/common.mdx b/src/fragments/lib-legacy/debugging/native_common/dev-menu/common.mdx new file mode 100644 index 00000000000..57eb6b6e0eb --- /dev/null +++ b/src/fragments/lib-legacy/debugging/native_common/dev-menu/common.mdx @@ -0,0 +1,46 @@ +When running a **debug** build of your app, you can access information, view logs, and file GitHub issues for Amplify directly from your application through the developer menu. By filing GitHub issues through the developer menu, critical information about the issue (device and environment information) is automatically added to the issue description, allowing Amplify team members to better assist you in resolving the issue. + + + +![Amplify iOS Developer Menu](/images/debugging/iosDevMenu.png) + + + + + +![Amplify Android Developer Menu](/images/debugging/androidDevMenu.png) + + + + + +The developer menu is disabled in **production** app builds. + + + +## Setup + +import ios0 from "/src/fragments/lib/debugging/ios/dev-menu/setup.mdx"; + + + +import android1 from "/src/fragments/lib/debugging/android/dev-menu/setup.mdx"; + + + +## Access and Usage + +import ios2 from "/src/fragments/lib/debugging/ios/dev-menu/usage.mdx"; + + + +import android3 from "/src/fragments/lib/debugging/android/dev-menu/usage.mdx"; + + + +The developer menu contains the following: + +* **Environment Information** - View versions of Amplify plugins used in your app and developer environment information. +* **Device Information** - View information about the device you are using to run your application. +* **Logs** - View and search the logs generated by Amplify loggers. +* **File a GitHub Issue** - After providing a description of your issue, an issue report is generated. The issue report contains your issue description, environment information, and device information. Within the developer menu, you can copy this issue report or directly file an issue on GitHub. The issue body will be pre-populated with the aforementioned information. When using the developer menu to file a GitHub issue, you will have an opportunity to review the issue information before filing the issue. \ No newline at end of file diff --git a/src/fragments/lib-legacy/flutter.mdx b/src/fragments/lib-legacy/flutter.mdx new file mode 100644 index 00000000000..2ec157b420f --- /dev/null +++ b/src/fragments/lib-legacy/flutter.mdx @@ -0,0 +1,15 @@ +## Amplify Flutter + +Welcome to the Amplify Flutter documentation. To stay up to date with the latest changes and provide feedback, please take a look at our [Github repo](https://github.com/aws-amplify/amplify-flutter) or join us on [Discord](https://discord.gg/jWVbPfC). + +The stable release of Amplify Flutter currently supports iOS/Android as target platforms. + +import flutter0 from "/src/fragments/lib/auth/flutter/getting_started/60_developerPreview.mdx"; + + + +This guide shows how to build an app using our Amplify Libraries for Flutter and the Amplify CLI toolchain. + + + Get Started 🚀 + diff --git a/src/fragments/lib-legacy/geo/android/dev-preview-callout.mdx b/src/fragments/lib-legacy/geo/android/dev-preview-callout.mdx new file mode 100644 index 00000000000..88650e2028d --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/dev-preview-callout.mdx @@ -0,0 +1,5 @@ + + +**Note:** Amplify Geo is in developer preview and is not intended to be used in production environments. Please reach out to us for any feedback and/or issues [here](https://github.com/aws-amplify/amplify-android/issues) + + diff --git a/src/fragments/lib-legacy/geo/android/escapehatch.mdx b/src/fragments/lib-legacy/geo/android/escapehatch.mdx new file mode 100644 index 00000000000..7bb398856de --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/escapehatch.mdx @@ -0,0 +1,68 @@ +If you need functionality in the AWS services used by the Amplify Geo category that isn't available, we provide an escape hatch so you can get a reference to that service. + +Note: If you provisioned your Geo resources via Amplify CLI, then the IAM policy will be specifically scoped to only allow actions required by the library. +Please [adjust your authorization permissions](/lib/geo/existing-resources) accordingly for your escape hatch use-cases. + + + + +```java +import android.util.Log; +import com.amazonaws.services.geo.AmazonLocationClient; +import com.amazonaws.services.geo.model.ListMapsRequest; +import com.amazonaws.services.geo.model.ListMapsResult; +import com.amplifyframework.core.Amplify; +import com.amplifyframework.geo.location.AWSLocationGeoPlugin; +``` + +```java +// Obtain reference to the plugin +AWSLocationGeoPlugin geoPlugin = (AWSLocationGeoPlugin) + Amplify.Geo.getPlugin("awsLocationGeoPlugin"); +AmazonLocationClient locationClient = geoPlugin.getEscapeHatch(); + +// Send a new request to the Location Maps endpoint directly using the client +ListMapsRequest request = new ListMapsRequest(); +ListMapsResult response = locationClient.listMaps(request); +Log.i("MyAmplifyApp", response.getEntries().toString()); +``` + + + + +```kotlin +import android.util.Log +import com.amazonaws.services.geo.AmazonLocationClient +import com.amazonaws.services.geo.model.ListMapsRequest +import com.amplifyframework.core.Amplify +``` + +```kotlin +// Obtain reference to the Amazon Location Service client +val geoPlugin = Amplify.Geo.getPlugin("awsLocationGeoPlugin") +val locationClient = geoPlugin.escapeHatch as AmazonLocationClient + +// Send a new request to the Location Maps endpoint directly using the client +val request = ListMapsRequest() +val response = locationClient.listMaps(request) +Log.i("MyAmplifyApp", response.entries.toString()) +``` + + + + +### Documentation Resources + +* [How to manage Amazon Location Service resources through console](https://docs.aws.amazon.com/location/latest/developerguide/welcome.html) + +**Maps** +* [Using Amazon Location Maps in your application](https://docs.aws.amazon.com/location/latest/developerguide/using-maps.html) +* [Amazon Location Maps API Reference](https://docs.aws.amazon.com/location-maps/latest/APIReference/API_Operations.html) + +**Places** +* [Searching place and geolocation data using Amazon Location](https://docs.aws.amazon.com/location/latest/developerguide/searching-for-places.html) +* [Amazon Location Places API Reference](https://docs.aws.amazon.com/location-places/latest/APIReference/API_Operations.html) + +**Device Tracking** +* [Managing your tracker resources](https://docs.aws.amazon.com/location/latest/developerguide/managing-trackers.html) +* [Amazon Location Trackers API Reference](https://docs.aws.amazon.com/location-trackers/latest/APIReference/API_Operations.html) diff --git a/src/fragments/lib-legacy/geo/android/existing-resources.mdx b/src/fragments/lib-legacy/geo/android/existing-resources.mdx new file mode 100644 index 00000000000..52d44b90f01 --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/existing-resources.mdx @@ -0,0 +1,53 @@ +## In your app configuration + +Amplify Geo is dependent on your Amplify Auth category. If it is not already configured, then you will have to manually configure it as well. +In order to manually configure Amplify Geo category, you must edit `amplifyconfiguration.json` in your project's `src/main/res/raw` directory with your information from existing Amazon Cognito and Amazon Location Service resources. + +```json +{ + "UserAgent": "aws-amplify-cli/2.0", + "Version": "1.0", + "geo": { + "plugins": { + "awsLocationGeoPlugin": { + "region": , + "maps": { + "items": { + : { + "style": + } + }, + "default": + }, + "searchIndices": { + "items": [ + + ], + "default": + } + } + } + }, + "auth": { + "plugins": { + "awsCognitoAuthPlugin": { + "UserAgent": "aws-amplify-cli/0.1.0", + "Version": "0.1.0", + "IdentityManager": { + "Default": {} + }, + "CredentialsProvider": { + "CognitoIdentity": { + "Default": { + "PoolId": , + "Region": + } + } + } + } + } + } +} +``` + +Now you can proceed to [displaying a map](/lib/geo/maps) or [adding location search](/lib/geo/search) to your app. diff --git a/src/fragments/lib-legacy/geo/android/getting_started/10_pre_req.mdx b/src/fragments/lib-legacy/geo/android/getting_started/10_pre_req.mdx new file mode 100644 index 00000000000..37ee5e48e37 --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/getting_started/10_pre_req.mdx @@ -0,0 +1,2 @@ +* An Android application targeting at least Android SDK API level 16 with Amplify libraries integrated + * For a full example of creating Android project, please follow the [project setup walkthrough](/lib/project-setup/create-application) diff --git a/src/fragments/lib-legacy/geo/android/getting_started/20_cli_resources.mdx b/src/fragments/lib-legacy/geo/android/getting_started/20_cli_resources.mdx new file mode 100644 index 00000000000..0ca31e5de19 --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/getting_started/20_cli_resources.mdx @@ -0,0 +1,89 @@ +> Prerequisite: [Install and configure the Amplify CLI](/cli/start/install) + +To start provisioning Geo resources in the backend, go to your project directory and execute the command: + +```sh +amplify init +``` + +The above command will guide you through setting up your project name and preferred authentication profile. +Refer to ["Create your application"](/lib/project-setup/create-application) section for detailed project setup guide. + +Now you are able to add a `geo` resource, such as map resources or a search index: + +```sh +amplify add geo +``` + +The CLI will let you configure the Geo category based on the capabilities you want to add (maps and/or search). +You can either choose to stick with the defaults or configure advanced settings. +Please refer to [Amplify CLI Geo docs](/cli/geo/maps/) for more details on these configurations. + + + + +```console +? Select which capability you want to add: +❯ Map (visualize the geospatial data) + +? geo category resources require auth (Amazon Cognito). Do you want to add auth now? (Y/n) +❯ yes + +? Do you want to use the default authentication and security configuration? +❯ Default configuration + +? How do you want users to be able to sign in? +❯ Username + +? Do you want to configure advanced settings? +❯ No, I am done. + +? Provide a name for the Map: +❯ mapResourceName + +? Who can access this Map? +❯ Authorized and Guest users + +? Do you want to configure advanced settings? (y/N) +❯ no +``` + + + + +```console +? Select which capability you want to add: +❯ Location search (search by places, addresses, coordinates) + +? geo category resources require auth (Amazon Cognito). Do you want to add auth now? (Y/n) +❯ yes + +? Do you want to use the default authentication and security configuration? +❯ Default configuration + +? How do you want users to be able to sign in? +❯ Username + +? Do you want to configure advanced settings? +❯ No, I am done. + +? Provide a name for the location search index (place index): +❯ placeIndexResourceName + +? Who can access this search index? +❯ Authorized and Guest users + +? Do you want to configure advanced settings? (y/N) +❯ no +``` + + + + +The `add` command automatically creates the backend configuration. Once all your configuration is complete, run the following (this might take a few minutes): + +```sh +amplify push +``` + +A file named `amplifyconfiguration.json` that contains all geo-related configuration information will be created in your app's `src/main/res/raw` directory. diff --git a/src/fragments/lib-legacy/geo/android/getting_started/30_install_lib.mdx b/src/fragments/lib-legacy/geo/android/getting_started/30_install_lib.mdx new file mode 100644 index 00000000000..b6805ffaa5a --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/getting_started/30_install_lib.mdx @@ -0,0 +1,14 @@ +Add the following dependencies to your **app**'s build.gradle file and click "Sync Now" when prompted: + +```groovy +dependencies { + implementation 'com.amplifyframework:aws-auth-cognito:ANDROID_VERSION' + implementation 'com.amplifyframework:aws-geo-location:ANDROID_GEO_VERSION' +} +``` + + + +**Note:** The Geo plugin has a dependency on Cognito Auth. + + diff --git a/src/fragments/lib-legacy/geo/android/getting_started/40_init_geo.mdx b/src/fragments/lib-legacy/geo/android/getting_started/40_init_geo.mdx new file mode 100644 index 00000000000..f118b3587de --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/getting_started/40_init_geo.mdx @@ -0,0 +1,96 @@ +To initialize Amplify Geo, use the `Amplify.addPlugin()` method to add the AWS Location Geo plugin. Next, finish configuring the Amplify framework by calling `Amplify.configure()`. + +Add the following import statements at the top of your application class that you created during the project setup: + + + + +```java +import android.util.Log; +import com.amplifyframework.AmplifyException; +import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin; +import com.amplifyframework.core.Amplify; +import com.amplifyframework.geo.location.AWSLocationGeoPlugin; +``` + + + + +```kotlin +import android.util.Log +import com.amplifyframework.AmplifyException +import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin +import com.amplifyframework.core.Amplify +import com.amplifyframework.geo.location.AWSLocationGeoPlugin +``` + + + + +Add the following code to your `onCreate()` method in your application class: + + + + +```java +Amplify.addPlugin(new AWSCognitoAuthPlugin()); +Amplify.addPlugin(new AWSLocationGeoPlugin()); +Amplify.configure(getApplicationContext()); +``` + +Your class will look like this: + +```java +public class MyAmplifyApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + try { + Amplify.addPlugin(new AWSCognitoAuthPlugin()); + Amplify.addPlugin(new AWSLocationGeoPlugin()); + Amplify.configure(getApplicationContext()); + Log.i("MyAmplifyApp", "Initialized Amplify"); + } catch (AmplifyException error) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error); + } + } +} +``` + + + + +```kotlin +Amplify.addPlugin(AWSCognitoAuthPlugin()) +Amplify.addPlugin(AWSLocationGeoPlugin()) +Amplify.configure(applicationContext) +``` + +Your class will look like this: + +```kotlin +class MyAmplifyApp : Application() { + override fun onCreate() { + super.onCreate() + + try { + Amplify.addPlugin(AWSCognitoAuthPlugin()) + Amplify.addPlugin(AWSLocationGeoPlugin()) + Amplify.configure(applicationContext) + Log.i("MyAmplifyApp", "Initialized Amplify") + } catch (error: AmplifyException) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error) + } + } +} +``` + + + + +Upon building and running this application you should see the following in your console window: + +```console +Initialized Amplify +``` diff --git a/src/fragments/lib-legacy/geo/android/maps/10_install_adapter.mdx b/src/fragments/lib-legacy/geo/android/maps/10_install_adapter.mdx new file mode 100644 index 00000000000..2fb71fad73e --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/maps/10_install_adapter.mdx @@ -0,0 +1,17 @@ +Add the following dependency to your **app**'s build.gradle file and click "Sync Now" when prompted: + +```groovy +dependencies { + implementation 'com.amplifyframework:aws-auth-cognito:ANDROID_VERSION' + implementation 'com.amplifyframework:aws-geo-location:ANDROID_GEO_VERSION' + + // Add this dependency to integrate MapLibre into your app + implementation 'com.amplifyframework:maplibre-adapter:ANDROID_GEO_VERSION' +} +``` + + + +**Note:** the minimum API level required for the UI components is 21. + + diff --git a/src/fragments/lib-legacy/geo/android/maps/20_display_map.mdx b/src/fragments/lib-legacy/geo/android/maps/20_display_map.mdx new file mode 100644 index 00000000000..1f101f924c6 --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/maps/20_display_map.mdx @@ -0,0 +1,436 @@ +### Select your user interface + +There are two UI components available to render maps on an Android app, the `MapLibreView` and the `AmplifyMapView`. The `MapLibreView` is an extension of the standard [Android MapLibre MapView](https://docs.maptiler.com/maplibre-gl-native-android/android-basic-get-started/) that is integrated with the `Amplify.Geo` APIs, while the `AmplifyMapView` is a wrapper with built-in location search, map controls, markers and a few standard UX interactions. + +#### `MapLibreView` vs `AmplifyMapView` + +If the goal is to customize the UI, the `MapLibreView` provides a plain map that allows you to build and integrate your own controls and flow. If the goal is to use the map to search and select places, with a standard UI, then `AmplifyMapView` is a good choice. + +Note that even though the extensibility of `AmplifyMapView` is limited, you have access to the wrapped `MapLibreView` through the `mapView` property (`getMapView()` on Java). So any API available to `MapLibreView` is also available on `AmplifyMapView`. + +## MapLibreView + +The `MapLibreView` is an extension of the standard `MapView` provided by the MapLibre library. The implementation adds the `Amplify.Geo` integration behind the scenes to enable developers to focus on their UI instead of the library integration. That also means all MapLibre APIs are available and will work as expected. Check the [official MapLibre SDK for Android documentation](https://docs.maptiler.com/maplibre-gl-native-android/android-basic-get-started/) for the API reference and guides. + +### Add a map to your app + +1. Navigate to your app's `src/main/res/layout` directory in Android Studio + +2. Create a new layout named `activity_main.xml`, or use an existing layout of your choice, and add the following: + +```xml + + + + + + +``` + +### Initialize the MapLibreView + +1. Navigate to your app's activity (e.g. `MainActivity`) + +2. Add an import statement for MapLibreView at the top of your app's activity: + + + + +```java +import com.amplifyframework.geo.maplibre.view.MapLibreView; +``` + + + + +```kotlin +import com.amplifyframework.geo.maplibre.view.MapLibreView +``` + + + + +3. Declare the view instance variable at top-level of the activity: + + + + +```java +private MapLibreView mapView; +``` + + + + +```kotlin +private val mapView by lazy { + findViewById(R.id.mapView) +} +``` + + + + +4. Interact with the map in the Activity's `onCreate`: + + + + + +```java +@Override +protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Note: make sure you configure Amplify before calling setContentView + // See the Getting Started instructions + + setContentView(R.layout.activity_main); + mapView = findViewById(R.id.mapView); + + // now you can interact with the mapView, see examples below +} +``` + + + + + +```kotlin +override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Note: make sure you configure Amplify before calling setContentView + // See the Getting Started instructions + + setContentView(R.layout.activity_main) + + // now you can interact with the mapView, see examples below +} +``` + + + + + +### Interact with the map + +The map needs to be loaded in order to interact with it. You can use either `getMapAsync` that is called when the map is ready or `getStyle` that is called when both the map and its style are ready. Some APIs, like the `SymbolManager`, require the style to also be loaded so you can draw markers and other symbols on the map, that's when `getStyle` comes in handy. + +### Update the map center + + + + + +```java +import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.geometry.LatLng; +``` + +```java +mapView.getMapAsync(map -> { + LatLng seattle = new LatLng(47.6160281982247, -122.32642111977668); + map.setCameraPosition( + new CameraPosition.Builder() + .target(seattle) + .zoom(13.0) + .build() + ); +}); +``` + + + + + +```kotlin +import com.mapbox.mapboxsdk.camera.CameraPosition +import com.mapbox.mapboxsdk.geometry.LatLng +``` + +```kotlin +mapView.getMapAsync { map -> + val seattle = LatLng(47.6160281982247, -122.32642111977668) + map.cameraPosition = CameraPosition.Builder() + .target(seattle) + .zoom(13.0) + .build() +} +``` + + + + + +Updating `cameraPosition` moves the camera to the passed coordinates without any animation. If animation is needed, use `map.animateCamera()` instead. See the [official reference](https://docs.maptiler.com/maplibre-gl-native-android/com.mapbox.mapboxsdk.camera/) for more details. + +### Add markers to your map + +The MapLibre SDK for Android relies on the [MapLibre Annotation Plugin](https://docs.maptiler.com/maplibre-gl-native-android/android-annotation/) in order to display markers on a map. + + + + + +```java +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; +import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions; +``` + +```java +mapView.getStyle((map, style) -> { + LatLng spaceNeedle = new LatLng(47.6205063, -122.3514661); + mapView.symbolManager.create( + new SymbolOptions() + .withIconImage("place") + .withLatLng(spaceNeedle) + ); + map.animateCamera(CameraUpdateFactory.newLatLngZoom(spaceNeedle, 16.0)); +}); +``` + + + + + +```kotlin +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory +import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions +``` + +```kotlin +mapView.getStyle { map, style -> + val spaceNeedle = LatLng(47.6205063, -122.3514661) + mapView.symbolManager.create( + SymbolOptions() + .withIconImage("place") + .withLatLng(spaceNeedle) + ) + map.animateCamera(CameraUpdateFactory.newLatLngZoom(spaceNeedle, 16.0)) +} +``` + + + + + +**Notes:** + +- The `mapView.symbolManager` is a built-in reference of `SymbolManager` from the [MapLibre Annotation Plugin](https://docs.maptiler.com/maplibre-gl-native-android/android-annotation/) with some standard configuration. +- If customized icons or render other types of shapes and layers are needed, an instance of `SymbolManager` can be created and used to manage the different types of custom use-cases. + +### MapLibreView configuration parameters + +The `MapLibreView` has several configuration parameters that are not present in the official guides yet. For a complete list, refer to the [source xml file](https://github.com/maplibre/maplibre-gl-native/blob/main/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml). + +Also, check the [official MapView API reference](https://docs.maptiler.com/maplibre-gl-native-android/com.mapbox.mapboxsdk.maps/#mapview) for the available public API documentation. + + +## AmplifyMapView + +The `AmplifyMapView` provides a default search field, place markers, visualization modes (map or list) and map controls. It can be used to easily embed a place picker into any app. To use the search functionality of `AmplifyMapView`, provision a search index resource using the instructions in either [Amplify CLI - Geo - Location Search](/cli/geo/search) or [Use existing Amazon Location Service resources](/lib/geo/existing-resources). + +### Add a map to your app + +1. Navigate to your app's `src/main/res/layout` directory in Android Studio + +2. Create a new layout named `activity_main.xml`, or use an existing layout of your choice, and add the following: + +```xml + + + + + + +``` + +### Initialize the AmplifyMapView + +1. Navigate to your app's activity (e.g. `MainActivity`) + +2. Add an import statement for AmplifyMapView at the top of your app's activity: + + + + +```java +import com.amplifyframework.geo.maplibre.view.AmplifyMapView; +``` + + + + +```kotlin +import com.amplifyframework.geo.maplibre.view.AmplifyMapView +``` + + + + +3. Declare the view instance variable at top-level of the activity: + + + + +```java +private AmplifyMapView amplifyMapView; +``` + + + + +```kotlin +private val amplifyMapView by lazy { + findViewById(R.id.mapView) +} +``` + + + + +4. Interact with the map in the Activity's `onCreate`: + + + + + +```java +@Override +protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Note: make sure you configure Amplify before calling setContentView + // See the Getting Started instructions + + setContentView(R.layout.activity_main); + amplifyMapView = findViewById(R.id.mapView); + + // now you can interact with the mapView, see examples below +} +``` + + + + + +```kotlin +override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Note: make sure you configure Amplify before calling setContentView + // See the Getting Started instructions + + setContentView(R.layout.activity_main) + + // now you can interact with the mapView, see examples below +} +``` + + + + + +### Place select + +The main API provided by `AmplifyMapView` is an event listener that is called when a place is selected on the map, either though clicking on the mark or the item on the list. + + + + + +```java +import android.util.Log; +``` + +```java +amplifyMapView.setOnPlaceSelectListener((place, symbol) -> { + // place is an instance of AmazonLocationPlace + // symbol is an instance of Symbol from MapLibre + Log.i("MyAmplifyApp", "The selected place is " + place.getLabel()); + Log.i("MyAmplifyApp", "It is located at " + place.getCoordinates()); +}); +``` + + + + + +```kotlin +import android.util.Log +``` + +```kotlin +amplifyMapView.onPlaceSelect { place, symbol -> + // place is an instance of AmazonLocationPlace + // symbol is an instance of Symbol from MapLibre + Log.i("MyAmplifyApp", "The selected place is ${place.label}") + Log.i("MyAmplifyApp", "It is located at ${place.coordinates}") +} +``` + + + + + +### AmplifyMapView configuration parameters + +The view can be initialized with the following configuration parameters: + +| Property | Type | Description | Default | +|--------------------------------|---------|------------------------------------------------------|---------| +| `map:map_centerLatitude` | Float | The initial center latitude | `0.0` | +| `map:map_centerLongitude` | Float | The initial center longitude | `0.0` | +| `map:map_minZoomLevel` | Integer | The minimum zoom level (min is 0) | `3` | +| `map:map_maxZoomLevel` | Integer | The maximum zoom level (max is 22) | `18` | +| `map:map_showCompassIndicator` | Boolean | Whether the compass should be displayed or not | `true` | +| `map:map_showZoomControls` | Boolean | Whether the zoom controls should be displayed or not | `false` | +| `map:map_zoomLevel` | Integer | The initial zoom level (between 0 and 22) | `14` | + + +Example: + +```xml + + + + + + +``` diff --git a/src/fragments/lib-legacy/geo/android/maps/30_styles.mdx b/src/fragments/lib-legacy/geo/android/maps/30_styles.mdx new file mode 100644 index 00000000000..1b543329953 --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/maps/30_styles.mdx @@ -0,0 +1,90 @@ +The `getAvailableMaps` API fetches information for all maps that are available to be displayed. + +This is useful if you would like to give your users a variety of map styles to choose from. + + + + +```java +import com.amplifyframework.core.Amplify; +import com.amplifyframework.geo.models.MapStyle; +``` + +```java +Amplify.Geo.getAvailableMaps( + result -> { + for (final MapStyle style : result) { + Log.i("MyAmplifyApp", style.toString()); + } + }, + error -> Log.e("MyAmplifyApp", "Failed to get available maps.", error) +); +``` + + + + +```kotlin +import com.amplifyframework.core.Amplify +``` + +```kotlin +Amplify.Geo.getAvailableMaps( + { + for (mapStyle in it) { + Log.i("MyAmplifyApp", mapStyle.toString()) + } + }, + { Log.e("MyAmplifyApp", "Failed to get available maps.", it) } +) +``` + + + + +You can set a different style to your map using `setStyle` method from the adapter: + + + + +With `MapLibreView`: + +```java +// where mapStyle is a reference to the selected style from Amplify.Geo.getAvailableMaps +mapView.setStyle(mapStyle, style -> { + Log.i("MyAmplifyApp", "Finished loading " + mapStyle.getStyle()); +}); +``` + +With `AmplifyMapView`: +```java +// where mapStyle is a reference to the selected style from Amplify.Geo.getAvailableMaps +amplifyMapView.getMapView().setStyle(mapStyle, style -> { + Log.i("MyAmplifyApp", "Finished loading " + mapStyle.getStyle()); +}); +``` + + + + + +With `MapLibreView`: + +```kotlin +// where mapStyle is a reference to the selected style from Amplify.Geo.getAvailableMaps +mapView.setStyle(mapStyle) { style -> + Log.i("MyAmplifyApp", "Finished loading ${mapStyle.style}.") +} +``` + +With `AmplifyMapView`: + +```kotlin +// where mapStyle is a reference to the selected style from Amplify.Geo.getAvailableMaps +amplifyMapView.mapView.setStyle(mapStyle) { style -> + Log.i("MyAmplifyApp", "Finished loading ${mapStyle.style}.") +} +``` + + + diff --git a/src/fragments/lib-legacy/geo/android/search/10_search_by_text.mdx b/src/fragments/lib-legacy/geo/android/search/10_search_by_text.mdx new file mode 100644 index 00000000000..7dc6f4779cf --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/search/10_search_by_text.mdx @@ -0,0 +1,96 @@ +### Search by text + +The `Amplify.Geo.searchByText()` API enables you to search for places or points of interest by free-form text, such as an address, name, city, or region. + + + + +```java +import android.util.Log; +import com.amplifyframework.core.Amplify; +import com.amplifyframework.geo.models.Place; +``` + +```java +String searchQuery = "Amazon Go"; +Amplify.Geo.searchByText(searchQuery, + result -> { + for (final Place place : result.getPlaces()) { + Log.i("MyAmplifyApp", place.toString()); + } + }, + error -> Log.e("MyAmplifyApp", "Failed to search for " + searchQuery, error) +); +``` + + + + +```kotlin +import android.util.Log +import com.amplifyframework.core.Amplify +``` + +```kotlin +val searchQuery = "Amazon Go" +Amplify.Geo.searchByText(searchQuery, + { + for (place in it.places) { + Log.i("MyAmplifyApp", place.toString()) + } + }, + { Log.e("MyAmplifyApp", "Failed to search for $searchQuery", it) } +) +``` + + + + +Restrict your search results by specifying following parameters inside `GeoSearchByTextOptions`: +- `countries` - to limit the search results to given countries. Follows [ISO Alpha-3 country codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3). (defaults to "USA") +- `maxResults` - to limit the maximum result set (defaults to 50) +- `searchArea` + - `near` - to act as the search origination location + - `within` - to limit the area to search inside of + + + + +```java +import com.amplifyframework.geo.models.Coordinates; +import com.amplifyframework.geo.models.CountryCode; +import com.amplifyframework.geo.models.SearchArea; +import com.amplifyframework.geo.options.GeoSearchByTextOptions; +import java.util.Collections; +``` + +```java +Coordinates position = new Coordinates(47.6153, -122.3384); +GeoSearchByTextOptions options = GeoSearchByTextOptions.builder() + .maxResults(10) + .searchArea(SearchArea.near(position)) + .countries(Collections.singletonList(CountryCode.USA)) + .build(); +``` + + + + +```kotlin +import com.amplifyframework.geo.models.Coordinates +import com.amplifyframework.geo.models.CountryCode +import com.amplifyframework.geo.models.SearchArea +import com.amplifyframework.geo.options.GeoSearchByTextOptions +``` + +```kotlin +val position = Coordinates(47.6153, -122.3384) +val options = GeoSearchByTextOptions.builder() + .maxResults(10) + .searchArea(SearchArea.near(position)) + .countries(listOf(CountryCode.USA)) + .build() +``` + + + diff --git a/src/fragments/lib-legacy/geo/android/search/20_search_by_coordinates.mdx b/src/fragments/lib-legacy/geo/android/search/20_search_by_coordinates.mdx new file mode 100644 index 00000000000..0896679a918 --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/search/20_search_by_coordinates.mdx @@ -0,0 +1,82 @@ +### Search by coordinates + +The `Amplify.Geo.searchByCoordinates()` API is a reverse Geocoder that takes a coordinate point and returns information about what it finds at that point on the map. +The returned object is the same shape as `Amplify.Geo.searchByText()` API. + + + + +```java +import android.util.Log; +import com.amplifyframework.core.Amplify; +import com.amplifyframework.geo.models.Coordinates; +import com.amplifyframework.geo.models.Place; +``` + +```java +Coordinates position = new Coordinates(47.6153, -122.3384); +Amplify.Geo.searchByCoordinates(position, + result -> { + for (final Place place : result.getPlaces()) { + Log.i("MyAmplifyApp", place.toString()); + } + }, + error -> Log.e("MyAmplifyApp", "Failed to reverse geocode " + position, error) +); +``` + + + + +```kotlin +import android.util.Log +import com.amplifyframework.core.Amplify +import com.amplifyframework.geo.models.Coordinates +``` + +```kotlin +val position = Coordinates(47.6153, -122.3384) +Amplify.Geo.searchByCoordinates(position, + { + for (place in it.places) { + Log.i("MyAmplifyApp", place.toString()) + } + }, + { Log.e("MyAmplifyApp", "Failed to reverse geocode $position", it) } +) +``` + + + + +Restrict your search results by specifying following parameters inside `GeoSearchByCoordinatesOptions`: +- `maxResults` - to limit the maximum result set (defaults to 50) + + + + +```java +import com.amplifyframework.geo.options.GeoSearchByCoordinatesOptions; +``` + +```java +GeoSearchByCoordinatesOptions options = GeoSearchByCoordinatesOptions.builder() + .maxResults(1) + .build(); +``` + + + + +```kotlin +import com.amplifyframework.geo.options.GeoSearchByCoordinatesOptions +``` + +```kotlin +val options = GeoSearchByCoordinatesOptions.builder() + .maxResults(1) + .build() +``` + + + diff --git a/src/fragments/lib-legacy/geo/android/search/30_location_search_map.mdx b/src/fragments/lib-legacy/geo/android/search/30_location_search_map.mdx new file mode 100644 index 00000000000..d247e5e96f0 --- /dev/null +++ b/src/fragments/lib-legacy/geo/android/search/30_location_search_map.mdx @@ -0,0 +1 @@ +`AmplifyMapView` provides built-in location search, search field, and place markers. Follow the [AmplifyMapView section](/lib/geo/maps#amplifymapview) on the Maps page to setup the `AmplifyMapView`. diff --git a/src/fragments/lib-legacy/geo/existing-resources.mdx b/src/fragments/lib-legacy/geo/existing-resources.mdx new file mode 100644 index 00000000000..8e9a7a95d3a --- /dev/null +++ b/src/fragments/lib-legacy/geo/existing-resources.mdx @@ -0,0 +1,49 @@ +You can also use Amplify Geo with your existing Amazon Location Service resources if you'd like, there's just some manual setup that you need to do. + +## Authorization permissions + +To use your existing Amazon Location Service resources, i.e. maps and place indices, with Amplify Geo, you need to ensure your role has the right authorization permissions through Cognito. If you set up your Cognito resources in any way other than the Amplify CLI or Amplify Studio, the roles will need to be given permission to access the map and place indices. + +**Note:** Here is a guide on [Creating an Amazon Cognito identity pool for use with Amazon Location Service](https://docs.aws.amazon.com/location/latest/developerguide/authenticating-using-cognito.html) + +There are two roles created by Cognito: an `Auth_Role` that grants signed-in-user-level access and an `Unauth_Role` that allows unauthenticated access to resources. Attach the following policies for the appropriate resources and roles (Auth and/or Unauth). Replace ```{region}```, ```{account-id}```, and ```{enter Map/PlaceIndex name}``` with the correct items. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "GetTiles", + "Effect": "Allow", + "Action": [ + "geo:GetMapTile", + "geo:GetMapSprites", + "geo:GetMapGlyphs", + "geo:GetMapStyleDescriptor" + ], + "Resource": "arn:aws:geo:{region}:{account-id}:map/{enter Map name}" + }, + { + "Sid": "Search", + "Effect": "Allow", + "Action": [ + "geo:SearchPlaceIndexForPosition", + "geo:SearchPlaceIndexForText" + ], + "Resource": "arn:aws:geo:{region}:{account-id}:place-index/{enter PlaceIndex name}" + } + ] +} +``` + +import js0 from "/src/fragments/lib/geo/js/existing-resources.mdx"; + + + +import android1 from "/src/fragments/lib/geo/android/existing-resources.mdx"; + + + +import ios2 from "/src/fragments/lib/geo/ios/existing-resources.mdx"; + + diff --git a/src/fragments/lib-legacy/geo/ios/dev-preview-callout.mdx b/src/fragments/lib-legacy/geo/ios/dev-preview-callout.mdx new file mode 100644 index 00000000000..d16fc814194 --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/dev-preview-callout.mdx @@ -0,0 +1,5 @@ + + +**Note:** Amplify Geo is in developer preview and is not intended to be used in production environments. Please reach out to us for any feedback and/or issues [here](https://github.com/aws-amplify/amplify-ios/issues) + + diff --git a/src/fragments/lib-legacy/geo/ios/escapehatch.mdx b/src/fragments/lib-legacy/geo/ios/escapehatch.mdx new file mode 100644 index 00000000000..a4a3f42985e --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/escapehatch.mdx @@ -0,0 +1,111 @@ +If you need functionality in the AWSLocation framework used by the Amplify Geo category that isn't available, we provide an escape hatch so you can reference it directly. + +Note: If you provisioned your Geo resources via Amplify CLI, then the IAM policy will be specifically scoped to only allow actions required by the library. +Please [adjust your authorization permissions](/lib/geo/existing-resources) accordingly for your escape hatch use-cases. + + + + + + + + + +```swift +import AWSLocationXCF +``` + + + + + +```swift +import AWSLocation +``` + + + + + + + + +```swift +import AWSLocation +``` + + + + + +Then retrieve the escape hatch and call methods on `AWSLocation` directly: + + + + + +```swift +do { + // Retrieve AWSLocationGeoPlugin + let plugin = try Amplify.Geo.getPlugin(for: "awsLocationGeoPlugin") + guard let locationPlugin = plugin as? AWSLocationGeoPlugin else { + return + } + + // Retrieve reference to AWSLocation + let awsLocation = locationPlugin.getEscapeHatch() + + // Make Request + let request = AWSLocationListMapsRequest()! + request.maxResults = 5 + awsLocation.listMaps(request) { response, error in + // handle response ... + } +} catch { + print("Error occurred while fetching the escape hatch \(error)") +} +``` + + + + + +```swift +do { + // Retrieve AWSLocationGeoPlugin + let plugin = try Amplify.Geo.getPlugin(for: "awsLocationGeoPlugin") + guard let locationPlugin = plugin as? AWSLocationGeoPlugin else { + return + } + + // Retrieve reference to AWSLocation + let awsLocation = locationPlugin.getEscapeHatch() + + // Make Request + var request = ListMapsInput() + request.maxResults = 5 + let response = try await awsLocation.listMaps(input: request) + // handle response ... +} catch { + print("Error occurred while fetching the escape hatch \(error)") +} +``` + + + + +### Documentation Resources + +* [How to manage Amazon Location Service resources through console](https://docs.aws.amazon.com/location/latest/developerguide/welcome.html) + +**Maps** +* [Using Amazon Location Maps in your application](https://docs.aws.amazon.com/location/latest/developerguide/using-maps.html) +* [Amazon Location Maps API Reference](https://docs.aws.amazon.com/location-maps/latest/APIReference/API_Operations.html) + +**Places** +* [Searching place and geolocation data using Amazon Location](https://docs.aws.amazon.com/location/latest/developerguide/searching-for-places.html) +* [Amazon Location Places API Reference](https://docs.aws.amazon.com/location-places/latest/APIReference/API_Operations.html) + +**Device Tracking** +* [Managing your tracker resources](https://docs.aws.amazon.com/location/latest/developerguide/managing-trackers.html) +* [Amazon Location Trackers API Reference](https://docs.aws.amazon.com/location-trackers/latest/APIReference/API_Operations.html) \ No newline at end of file diff --git a/src/fragments/lib-legacy/geo/ios/existing-resources.mdx b/src/fragments/lib-legacy/geo/ios/existing-resources.mdx new file mode 100644 index 00000000000..8607b9fb621 --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/existing-resources.mdx @@ -0,0 +1,31 @@ +## In your app configuration + +Amplify Geo is dependent on your Amplify Auth category. If it is not already configured, then you will need to [configure it](/lib/auth/existing-resources) as well. + +Existing Amazon Location Service resources can be used with the Amplify Libraries by adding information about the resources to your `amplifyconfiguration.json` file. + +```json +{ + "geo": { + "plugins": { + "awsLocationGeoPlugin": { + "region": , + "maps": { + "items": { + : { + "style": + } + }, + "default": + }, + "searchIndices": { + "items": [ + + ], + "default": + } + } + } + } +} +``` diff --git a/src/fragments/lib-legacy/geo/ios/getting_started/10_pre_req.mdx b/src/fragments/lib-legacy/geo/ios/getting_started/10_pre_req.mdx new file mode 100644 index 00000000000..685f10b718c --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/getting_started/10_pre_req.mdx @@ -0,0 +1,2 @@ +* An iOS application targeting at least iOS 13.0, using Xcode 12.0 or higher, with Amplify libraries integrated + * For a full example of creating iOS project, please follow the [project setup walkthrough](/lib/project-setup/create-application) diff --git a/src/fragments/lib-legacy/geo/ios/getting_started/20_cli_resources.mdx b/src/fragments/lib-legacy/geo/ios/getting_started/20_cli_resources.mdx new file mode 100644 index 00000000000..8a628e1ad48 --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/getting_started/20_cli_resources.mdx @@ -0,0 +1,15 @@ +> Prerequisite: [Install and configure the Amplify CLI](/cli/start/install) + +To start provisioning Geo resources in the backend, go to your project directory and execute the command: + +```sh +amplify add geo +``` + +The CLI will prompt configuration options for the Geo category for what type of capability you want to add and default or advanced settings. The `add` command automatically creates the backend configuration. Once all your configuration is complete run the following: + +```sh +amplify push +``` + +Upon completion, `amplifyconfiguration.json` should be updated to reference provisioned backend geo resources. Note that this file should already be a part of your project if you followed the Project setup walkthrough. diff --git a/src/fragments/lib-legacy/geo/ios/getting_started/30_install_lib.mdx b/src/fragments/lib-legacy/geo/ios/getting_started/30_install_lib.mdx new file mode 100644 index 00000000000..61427132252 --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/getting_started/30_install_lib.mdx @@ -0,0 +1,50 @@ +The Geo plugin is dependent on Cognito Auth. + + + + + +1. To install Amplify Geo and Authentication to your application, open your project in Xcode and select **File > Add Packages...**. + +1. Enter the Amplify iOS GitHub repo URL (`https://github.com/aws-amplify/amplify-ios`) into the search bar and and hit **Enter** Wait for the result to load. You'll see the repository rules for which version of amplify-ios-mapLibre you want Swift Package Manager to install. + +1. Choose the dependency rule **Up to Next Major Version**, as it will use the latest compatible version of the dependency, then click **Add Package**. + +1. Lastly, choose **AWSLocationGeoPlugin**, **AWSCognitoAuthPlugin**, and **Amplify**. Then click Finish. + + + + + + + +**Troubleshooting:** If you intend on using the Amplify-MapLibre adapter to render maps, you **must** use Swift Package Manager to add Amplify. The adapter is incompatible with existing Amplify dependencies added via CocoaPods. + + + +To install the Amplify Geo and Authentication to your application, **add both "AmplifyPlugins/AWSLocationGeoPlugin" and "AmplifyPlugins/AWSCognitoAuthPlugin" to your `Podfile`** (Because IAM credential is required to access Amazon Location Service, `"AWSCognitoAuthPlugin"` also needs to be installed). Your `Podfile` should look similar to: + +```bash +target 'MyAmplifyApp' do + use_frameworks! + pod 'Amplify' + pod 'AmplifyPlugins/AWSLocationGeoPlugin' + pod 'AmplifyPlugins/AWSCognitoAuthPlugin' +end +``` + +To install, download and resolve these pods, **execute the command**: + +```bash +pod install --repo-update +``` + +Now you can **open your project** by opening the `.xcworkspace` file using the following command: + +```bash +xed . +``` + + + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/geo/ios/getting_started/40_init_geo.mdx b/src/fragments/lib-legacy/geo/ios/getting_started/40_init_geo.mdx new file mode 100644 index 00000000000..d1504d9f42a --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/getting_started/40_init_geo.mdx @@ -0,0 +1,77 @@ +To initialize the Amplify Geo, use the `Amplify.addPlugin()` method to add the AWSLocationGeoPlugin. Next, finish configuring the Amplify framework by calling `Amplify.configure()`. + +Open the main file of the application - `AppDelegate.swift` or `App.swift` depending on the app's life cycle - and **add the following** import statements at the top of the file: + + + + +```swift +import Amplify +import AWSLocationGeoPlugin +``` + + + + + +```swift +import Amplify +import AmplifyPlugins +``` + + + + + +In the same file, **create a function** to configure Amplify: +```swift +func configureAmplify() { + do { + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.add(plugin: AWSLocationGeoPlugin()) + try Amplify.configure() + print("Initialized Amplify"); + } catch { + print("Could not initialize Amplify: \(error)") + } +} +``` + +Now **call the `configureAmplify()` function** in the starting point of your application. + + + +```swift +@main +struct App: App { + // add a default initializer and configure Amplify + public init() { + configureAmplify() + } +} +``` + + + + +```swift +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + func application(_: UIApplication, + didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + configureAmplify() + return true + } + // ... +} +``` + + + + +Upon building and running this application you should see the following in your console window: + +```console +Initialized Amplify +``` diff --git a/src/fragments/lib-legacy/geo/ios/maps/10_install_adapter.mdx b/src/fragments/lib-legacy/geo/ios/maps/10_install_adapter.mdx new file mode 100644 index 00000000000..adf88d959a3 --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/maps/10_install_adapter.mdx @@ -0,0 +1,23 @@ +First, ensure you've provisioned an Amazon Location Service Map resource and configured your app using the instructions in either [Amplify CLI - Geo - Maps](/cli/geo/maps) or [Use existing resources](/lib/geo/existing-resources) guide. + + + +**Troubleshooting:** If your project already imports Amplify through CocoaPods, you may encounter build errors after adding the Amplify-MapLibre adapter. If this occurs, switching to SPM will resolve the issue. + + + +Amplify-MapLibre is an open source adapter that enables the popular MapLibre SDK to work seamlessly with Amplify Geo. + +1. To install the Amplify-MapLibre adapter to your application, open your project in Xcode and select **File > Add Packages...** + +1. Enter the amplify-ios-maplibre GitHub repo URL (`https://github.com/aws-amplify/amplify-ios-maplibre`) into the search bar and hit **Enter**. Wait for the result to load. You'll see the repository rules for which version of amplify-ios-mapLibre you want Swift Package Manager to install. + +1. Choose the dependency rule **Up to Next Major Version**, as it will use the latest compatible version of the dependency, then click **Add Package**. + +1. Lastly, choose which of the libraries you want added to your project. If you want to use the SwiftUI user interface components provided by the adapter, select both **AmplifyMapLibreAdapter** and **AmplifyMapLibreUI**. If you only want to enable Amplify Geo to work directly with MapLibre and do not wish use use the provided SwiftUI views, you can just select **AmplifyMapLibreAdapter**. After you select the libraries, click **Add Package**. + + + +You can always go back and modify which SPM packages are included in your project by opening the Swift Packages tab for your project (`Project file > Project > Package Dependencies`) + + diff --git a/src/fragments/lib-legacy/geo/ios/maps/20_display_map.mdx b/src/fragments/lib-legacy/geo/ios/maps/20_display_map.mdx new file mode 100644 index 00000000000..be965850a8b --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/maps/20_display_map.mdx @@ -0,0 +1,119 @@ +### Select your user interface + +There are two ways to display a map. The easiest way to get started is to use the `AmplifyMaplibreUI` library to create an instance of `AMLMapView`. This is the recommended approach for new projects or anyone who wants to use SwiftUI. + +Alternatively, if you are using UIKit or have existing code using the MapLibre/MapBox SDK, you can simply call `AmplifyMapLibre.createMap(completionHandler:)` to create an instance of `MGLMapView` that is pre-wired for use with Amazon Location Service and Amplify. For more information on using `MGLMapView` directly, please see the [MapLibre Documentation](https://docs.maptiler.com/maplibre-gl-native-ios/) + + + + +```swift +import SwiftUI +import AmplifyMapLibreUI + +struct ContentView: View { + var body: some View { + AMLMapView() + .edgesIgnoringSafeArea(.all) + } +} +``` + + + + + + + + +```swift +import AmplifyMapLibreAdapter +import Mapbox +import Amplify + +var mapView: MGLMapView? + +AmplifyMapLibre.createMap { result in + switch result { + case .failure(let error): + print(error) + case .success(let map): + mapView = map + } +} +``` + + + + + +```swift +import AmplifyMapLibreAdapter +import Mapbox +import Amplify + +var mapView: MGLMapView? +do { + mapView = try await AmplifyMapLibre.createMap() +} catch { + print(error) +} +``` + + + + + + +## Customize the map and access state + +The `AMLMapView` can be customized through a number of view modifiers. The map state information can be set and observed through an instance of `AMLMapViewState`, which can optionally be passed into `AMLMapView` to set initial values. The following example sets an initial zoom level and center location for the map and configures the map to show the user's location. + +```swift +@StateObject private var mapState = AMLMapViewState( + zoomLevel: 8, + center: CLLocationCoordinate2D(latitude: 39.7392, longitude: -104.9903) +) + +var body: some View { + AMLMapView(mapState: mapState) + .showUserLocation(true) + .edgesIgnoringSafeArea(.all) +} +``` + +## Inject custom behavior + +The `AMLMapView` also allows for custom behavior triggered by user interaction to be injected. The following example sets a custom feature image and defines the maps behavior when that feature is tapped - zooming in two levels above the current level. + +```swift +var body: some View { + AMLMapView() + .featureImage { MyCustomImage() } + .featureTapped { mapView, pointFeature in + mapView.setCenter( + pointFeature.coordinate, + zoomLevel: mapView.zoomLevel + 2, + direction: mapView.camera.heading, + animated: true + ) + } + .edgesIgnoringSafeArea(.all) +} +``` + +## AMLMapCompositeView + +The `AMLMapCompositeView` combines `AMLMapView`, `AMLSearchBar`, `AMLMapControlView`, and `AMLPlaceCellView` to create a full user experience. This includes accessible map control buttons, a search bar that automatically searches for points of interest based on user input, and a list representation of points. +In its simplest form, which still leverages all of the above mentioned functionality, the `AMLMapCompositeView` can be instantiated without any arguments. All of the view modifiers and state tracking capabilities of `AMLMapView` are also available on `AMLMapCompositeView`. + +```swift +var body: some View { + AMLMapCompositeView() +} +``` + + +For full details on `AMLMapView` usage and customization, see the [AmplifyMapLibre documentation](https://aws-amplify.github.io/amplify-ios-maplibre/docs/). + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/geo/ios/maps/30_styles.mdx b/src/fragments/lib-legacy/geo/ios/maps/30_styles.mdx new file mode 100644 index 00000000000..c53f140e292 --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/maps/30_styles.mdx @@ -0,0 +1,47 @@ +The `availableMaps` API fetches information for all maps that are available to be displayed. + +This is useful if you would like to give your users a variety of maps styles to choose from. + + + + + +```swift +var maps = [Geo.MapStyle]() + +Amplify.Geo.availableMaps { result in + switch result { + case .failure(let error): + print(error) + case .success(let availableMaps): + maps = availableMaps + } +} +``` + + + + + +```swift +var maps = [Geo.MapStyle]() +do { + maps = try await Amplify.Geo.availableMaps() +} catch { + print(error) +} +``` + + + + +You can load a different style map by passing it to the createMap function. + +```swift +guard let mapStyle = maps.first else { + print("No maps available") + return +} + +let mapView = AmplifyMapLibre.createMap(mapStyle) +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/geo/ios/search/10_search_by_text.mdx b/src/fragments/lib-legacy/geo/ios/search/10_search_by_text.mdx new file mode 100644 index 00000000000..65d315668dc --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/search/10_search_by_text.mdx @@ -0,0 +1,76 @@ +### Search for text + +The `Amplify.Geo.search(for text:)` API enables you to search for places or points of interest by free-form text, such as an address, name, city, or region. + + + + +```swift +Amplify.Geo.search(for: "coffee shops") { result in + switch result { + case .failure(let error): + print(error) + case .success(let places): + dump(places) + } +} +``` + + + + +```swift +do { + let places = try await Amplify.Geo.search(for: "coffee shops") + dump(places) +} catch { + print(error) +} +``` + + + + +You can refine your search by specifying following parameters inside `Geo.SearchForTextOptions` +- `area` + - `.near` - to act as the search origination location. + - `.within` - to limit the area to search within. +- `countries` - to limit the search results to given countries. +- `maxResults` - to limit the maximum result set (defaults to 50). + + + + +```swift +let coordinates = Geo.Coordinates(latitude: 47.62246, longitude: -122.336775) +let options = Geo.SearchForTextOptions(area: .near(coordinates), + countries: [.usa, .can], + maxResults: 25) + +Amplify.Geo.search(for: "coffee shops", options: options) { result in + switch result { + case .failure(let error): + print(error) + case .success(let places): + dump(places) + } +} +``` + + + + +```swift +let coordinates = Geo.Coordinates(latitude: 47.62246, longitude: -122.336775) +let options = Geo.SearchForTextOptions(area: .near(coordinates), + countries: [.usa, .can], + maxResults: 25) +do { + let places = try await Amplify.Geo.search(for: "coffee shops", options: options) + dump(places) +} catch { + print(error) +} +``` + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/geo/ios/search/20_search_by_coordinates.mdx b/src/fragments/lib-legacy/geo/ios/search/20_search_by_coordinates.mdx new file mode 100644 index 00000000000..d87e9198cc6 --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/search/20_search_by_coordinates.mdx @@ -0,0 +1,41 @@ +### Search for coordinates + +The `Amplify.Geo.search(for coordinates:)` API is a reverse Geocoder that takes a coordinate point and returns information about what it finds at that point on the map. + + + + +```swift +Amplify.Geo.search(for: coordinates) { result in + switch result { + case .failure(let error): + print(error) + case .success(let places): + dump(places) + } +} +``` + + + + + +```swift +do { + let places = try await Amplify.Geo.search(for: coordinates) + dump(places) +} catch { + print(error) +} +``` + + + + + +You can refine your search by specifying following parameters inside `Geo.SearchForCoordinatesOptions` +- `maxResults` - to limit the maximum result set (defaults to 50) + +```swift +let options = Geo.SearchForCoordinatesOptions(maxResults: 25) +``` diff --git a/src/fragments/lib-legacy/geo/ios/search/30_location_search_map.mdx b/src/fragments/lib-legacy/geo/ios/search/30_location_search_map.mdx new file mode 100644 index 00000000000..101b7f2e866 --- /dev/null +++ b/src/fragments/lib-legacy/geo/ios/search/30_location_search_map.mdx @@ -0,0 +1,124 @@ +To add a location search UI component to your `AMLMapView`, you will add an `AMLSearchBar` to your View in your desired layout. Upon searching, `Geo.Place`s are converted to `MGLPointFeature`s using `AmplifyMapLibre.createFeatures(places)`. Lastly assign those converted `MGLPointFeature`s to `mapState.features`. +Alternatively, you can leverage the `AMLMapCompositeView` directly, which includes an `AMLSearchBar` among other pre-configured UI components. + + + + +```swift +import SwiftUI +import AmplifyMapLibreUI +import AmplifyMapLibreAdapter +import Amplify + +struct MyMapView: View { + + @StateObject private var mapState = AMLMapViewState() + @State private var searchText = "" + @State private var displayState: AMLSearchBar.DisplayState = .map + + var body: some View { + ZStack(alignment: .top) { + AMLMapView(mapState: mapState) + .edgesIgnoringSafeArea(.all) + + AMLSearchBar( + text: $searchText, + displayState: $displayState, + onCommit: search, + onCancel: { mapState.features = [] } + ) + .padding() + } + } + + private func search() { + let searchArea = Geo.SearchArea.near(mapState.center) + let searchOptions = Geo.SearchForTextOptions(area: searchArea) + Amplify.Geo.search(for: searchText, options: searchOptions) { result in + switch result { + case .success(let places): + DispatchQueue.main.async { + mapState.features = AmplifyMapLibre.createFeatures(places) + } + case .failure(let geoError): + print(geoError) + } + } + } +} +``` + + + + + +```swift +import SwiftUI +import AmplifyMapLibreUI +import AmplifyMapLibreAdapter +import Amplify + +struct MyMapView: View { + + @StateObject private var mapState = AMLMapViewState() + @State private var searchText = "" + @State private var displayState: AMLSearchBar.DisplayState = .map + + var body: some View { + ZStack(alignment: .top) { + AMLMapView(mapState: mapState) + .edgesIgnoringSafeArea(.all) + + AMLSearchBar( + text: $searchText, + displayState: $displayState, + onEditing: { }, + onCommit: search, + onCancel: { mapState.features = [] } + ) + .padding() + } + } + + private func search() { + let searchArea = Geo.SearchArea.near(mapState.center) + let searchOptions = Geo.SearchForTextOptions(area: searchArea) + Task { + do { + let places = try await Amplify.Geo.search(for: searchText, options: searchOptions) + await MainActor.run { + self.mapState.features = AmplifyMapLibre.createFeatures(places) + } + } catch { + print(error) + } + } + } +} +``` + + + +![AMLMapView with AMLSearchBar](/images/ios-geocoder-search-box-map.png) + +### Customize Feature Icons + +You can customize the feature images displayed on the `AMLMapView` or `AMLMapCompositeView`, you can leverage the `featureImage()` view modifier. + +```swift + var body: some View { + AMLMapView(mapState: mapState) + .featureImage { + let image = UIImage( + systemName: "paperplane.circle.fill", + withConfiguration: UIImage.SymbolConfiguration( + font: .systemFont(ofSize: 22, weight: .medium) + ) + )! + return image + } + .edgesIgnoringSafeArea(.all) + } +``` + +![AMLMapView featureImage view modifier](/images/ios-geocoder-custom-images.png) diff --git a/src/fragments/lib-legacy/geo/js/escapehatch.mdx b/src/fragments/lib-legacy/geo/js/escapehatch.mdx new file mode 100644 index 00000000000..6870c3e3008 --- /dev/null +++ b/src/fragments/lib-legacy/geo/js/escapehatch.mdx @@ -0,0 +1,91 @@ +Amplify Geo provides solutions for common use cases with [Amazon Location Service](https://aws.amazon.com/location/) but for any functionality that is not currently supported by Amplify Geo you can access the [Amazon Location Service SDK](https://github.com/aws/aws-sdk-js-v3/tree/main/clients/client-location) directly. + +Follow this guide to get started with the `aws-sdk` for Amazon Location Service using Amplify Auth credentials. + +## Overview + +In this tutorial, we’ll go over the following: + +- Setting up the AWS SDK JavaScript v3 package for the Amazon Location Service SDK calls with Amplify auth. +- Code examples using the Amazon Location Service SDK. + +## Installing SDK dependencies + +The first step to using the SDKs in the client is to install the necessary dependencies with the following command: + +```bash +npm install @aws-sdk/client-location +``` + +## Connecting your app to Amazon Location Service + +In the following procedure, you’ll connect your app to the Amazon Location Service APIs. + +**To connect your app to the Amazon Location Service** + +In your React App, open `src/App.js` file, and call the following function to initialize the Amazon Location Service client: + +```javascript +import { Amplify, Auth } from 'aws-amplify'; +import { + LocationClient, + AssociateTrackerConsumerCommand +} from '@aws-sdk/client-location'; +import awsconfig from './aws-exports'; + +Amplify.configure(awsconfig); + +const createClient = async () => { + const credentials = await Auth.currentCredentials(); + const client = new LocationClient({ + credentials, + region: awsconfig.aws_project_region + }); + return client; +}; +``` + +You’ve now successfully connected your app to the Amazon Location Service. + +## Using the Amazon Location Service APIs + +In order to access Amazon Location Service APIs, ensure you've provisioned resources and configured your app using the instructions in either [Amplify CLI Geo Maps docs](/cli/geo/maps) or the [Amazon Location Service console](https://console.aws.amazon.com/location/home#/create). + +You can check out the [Amazon Location API Reference documentation](https://docs.aws.amazon.com/location/index.html) for a complete list of supported features. + +### Example: Getting Device Position + +This example requires you to have first provisioned a Tracker resource using the [Amazon Location Service console](https://console.aws.amazon.com/location/tracking/home#/create). + +The following code details how to use the Amazon Location Service APIs to update a device position and get a device position using the tracker you just created: + +```javascript +// UpdateDevicePosition API +const params = { + TrackerName: 'trackerId', + Updates: [ + { + DeviceId: 'deviceId', + Position: [-122.431297, 37.773972], + SampleTime: new Date() + } + ] +}; +const command = new BatchUpdateDevicePositionCommand(params); +client.send(command, (err, data) => { + if (err) console.error(err); + if (data) console.log(data); +}); + +// GetDevicePosition API +const client = await createClient(); +const params = { + TrackerName: 'trackerId', + DeviceId: 'deviceId' +}; +const command = new GetDevicePositionCommand(params); +client.send(command, (err, data) => { + if (err) console.error(err); + if (data) console.log(data); +}); +``` diff --git a/src/fragments/lib-legacy/geo/js/existing-resources.mdx b/src/fragments/lib-legacy/geo/js/existing-resources.mdx new file mode 100644 index 00000000000..c384885fdc7 --- /dev/null +++ b/src/fragments/lib-legacy/geo/js/existing-resources.mdx @@ -0,0 +1,39 @@ +## In your application + +To configure Amplify Geo manually, you will have to configure Amplify Auth category as well. Instead of configuring Amplify by importing the CLI generated `aws-exports.js`, you will be calling `Amplify.configure` with your existing Amazon Cognito and Amazon Location Service information in your app like this: + +```js +import { Amplify, Auth, Geo } from 'aws-amplify'; + +Amplify.configure({ + Auth: { + identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab', // REQUIRED - Amazon Cognito Identity Pool ID + region: 'XX-XXXX-X', // REQUIRED - Amazon Cognito Region + userPoolId: 'XX-XXXX-X_abcd1234', // OPTIONAL - Amazon Cognito User Pool ID for authenticated user access + userPoolWebClientId: 'XX-XXXX-X_abcd1234', // OPTIONAL - Amazon Cognito Web Client ID for authenticated user access + }, + geo: { + AmazonLocationService: { + maps: { + items: { + "XXXXXXXXXXX": { // REQUIRED - Amazon Location Service Map resource name + style: "VectorEsriStreets", // REQUIRED - String representing the style of map resource + }, + }, + default: "XXXXXXXXXXX", // REQUIRED - Amazon Location Service Map resource name to set as default + }, + search_indices: { + items: ["XXXXXXXXX", "XXXXXXXXX"], // REQUIRED - Amazon Location Service Place Index name + default: "XXXXXXXXX", // REQUIRED - Amazon Location Service Place Index name to set as default + }, + geofenceCollections: { + items: ["XXXXXXXXX", "XXXXXXXXX"], // REQUIRED - Amazon Location Service Geofence Collection name + default: "XXXXXXXXX", // REQUIRED - Amazon Location Service Geofence Collection name to set as default + } + region: 'XX-XXXX-X', // REQUIRED - Amazon Location Service Region + }, + }, +}) +``` + +Now you can proceed to [displaying a map](/lib/geo/maps) or [adding location search](/lib/geo/search) to your app. diff --git a/src/fragments/lib-legacy/geo/js/geofences.mdx b/src/fragments/lib-legacy/geo/js/geofences.mdx new file mode 100644 index 00000000000..fd2e9c6a607 --- /dev/null +++ b/src/fragments/lib-legacy/geo/js/geofences.mdx @@ -0,0 +1,344 @@ + + +**Note:** Please reach out to us for any feedback and/or issues [here](https://github.com/aws-amplify/amplify-js/issues) + + + +## Provisioning geofence resources +First, make sure you've provisioned a geofence collection resource and configured your app using the instructions in either [Amplify CLI - Geo - Geofence Collection](/cli/geo/geofencing) or [Use existing Amazon Location Service resources](/lib/geo/existing-resources) and you have already setup [displaying a map](/lib/geo/maps) in your application. + +## Manage Geofences in Your Application + +To add a geofence management component to your map, you can use the [amplify-geofence-control](https://github.com/aws-amplify/maplibre-gl-js-amplify/blob/geo/API.md#amplifygeofencecontrol). + +Install the necessary dependencies with the following command: + +```bash +npm install maplibre-gl maplibre-gl-js-amplify aws-amplify @aws-amplify/ui-react +``` + +> **Note:** Make sure that `maplibre-gl-js-amplify` version `2.0.0` or above and `@aws-amplify/geo` version `1.3.0` or above are installed. + +First, create a map onto which you want to add the geofence management component. See the guide on [creating and displaying maps](https://docs.amplify.aws/lib/geo/maps/q/platform/js/). + +Then, import [AmplifyGeofenceControl](https://github.com/aws-amplify/maplibre-gl-js-amplify/blob/geo/API.md#amplifygeofencecontrol) from "maplibre-gl-js-amplify", create a new instance of this control and add it to your MapLibre map instance. + + + + + + **Note:** When using the existing [maps implementation](/lib/geo/maps) you can add the Geofence control to an existing map + + ```diff + import { useEffect, useRef } from "react"; +- import { createMap } from "maplibre-gl-js-amplify"; ++ import { createMap, AmplifyGeofenceControl } from "maplibre-gl-js-amplify"; ++ import { withAuthenticator } from "@aws-amplify/ui-react"; ++ import "@aws-amplify/ui-react/styles.css"; ++ import "maplibre-gl-js-amplify/dist/public/amplify-ctrl-geofence.css"; +import "maplibre-gl/dist/maplibre-gl.css"; + + function Map() { + const mapRef = useRef(null); // Reference to the map DOM element + + // Wrapping our code in a useEffect allows us to run initializeMap after the div has been rendered into the DOM + useEffect(() => { + let map; + async function initializeMap() { + // We only want to initialize the underlying maplibre map after the div has been rendered + if (mapRef.current != null) { + map = await createMap({ + container: mapRef.current, + center: [-122.431297, 37.773972], + zoom: 11, + }); + } + ++ const control = new AmplifyGeofenceControl() ++ map.addControl(control); + } + initializeMap(); + + // Cleans up and maplibre DOM elements and other resources - https://maplibre.org/maplibre-gl-js-docs/api/map/#map#remove + return function cleanup() { + if (map != null) map.remove(); + }; + }, []); + + return ( +
+
+
+ ); + } + + export default withAuthenticator(Map); + ``` + + + + + + **Note:** When using the [Amplify React MapView component](https://ui.docs.amplify.aws/react/components/geo) you can use the [`useControl` hook from react-map-gl](https://visgl.github.io/react-map-gl/docs/api-reference/use-control) to render the Geofence control component. + + ```javascript + import React from "react"; + import Amplify from "aws-amplify"; + import { MapView, withAuthenticator } from "@aws-amplify/ui-react"; + import { useControl } from "react-map-gl"; + import { AmplifyGeofenceControl } from "maplibre-gl-js-amplify"; + import "@aws-amplify/ui-react/styles.css"; + import "maplibre-gl/dist/maplibre-gl.css"; + import "maplibre-gl-js-amplify/dist/public/amplify-ctrl-geofence.css"; + import awsconfig from "../src/aws-exports"; + Amplify.configure(awsconfig); + + function Geofence() { + useControl(() => new AmplifyGeofenceControl()); + + return null; + } + + function App({ signOut }) { + return ( +
+ + + +
+ ); + } + + export default withAuthenticator(App); + ``` + +
+ + + +> **Note:** Ensure that your package bundler (webpack, rollup, etc) is configured to handle css files. Check out the webpack documentation [here](https://webpack.js.org/loaders/css-loader/). + +> **Notes:** To use Geofence Controls the user will need to be authenticated with the administrative Cognito user associated with the Geofence Collection you created above. Below is an example using React and the [Amplify Authenticator](https://ui.docs.amplify.aws/react/components/authenticator) but for other use cases check the [Authentication documentation](/lib/auth/overview). + +![Geofence Create](/images/geofence-create.png) + +![Geofence Edit](/images/geofence-edit.png) + +## Geofence API + +If you are using a different mapping library or need a programmatic approach to managing geofences, the `@aws-amplify/geo` package provides methods for managing geofences, but not geofence collections. + +First, you need to import Geo from either the `@aws-amplify/geo` or `aws-amplify` packages. + +```javascript +import { Geo } from "@aws-amplify/geo"; +// Or +import { Geo } from "aws-amplify"; +``` + +### saveGeofences + +`saveGeofences` is used to save geofences to your collection. It can take a single geofence or an array of geofences. + +#### API + +```javascript +Geo.saveGeofences(geofences, options) => Promise; +``` + +#### Parameters + +- `geofences` - can be a single geofence object, or an array of geofence objects to save to a collection. +- `options` - optional options object for saving geofences + - `collectionName` - the name of the collection to save geofences to. + - Defaults to the default collection listed in your `aws-exports.js` file after provisioning a geofence collection resource. + +Geofence objects must have the following properties: + +- `geofenceId` - a opaque and unique identifier for the geofence. +- `geometry` - a geometry object that defines the geofence. + - `polygon` - an array of arrays with [Longitude, Latitude] coordinates. + + + +NOTE: Polygon arrays have a few requirements: + +- must have at least 4 vertices (i.e. 4 coordinate points) +- the first and last point must be the same in order to complete the polygonal loop +- vertices must be in counter-clockwise order + + + +#### Return + +The return from `saveGeofences` is a Promise that resolves to `SaveGeofenceResults` which contains both successes and errors for geofences that were successfully created or failed. + +Each success object has the following properties: + +- `geofenceId` - the geofenceId of the geofence that was saved. +- `createTime` - the time the geofence was created. +- `updateTime` - the time the geofence was last updated. + +Each error object has the following properties: + +- `geofenceId` - the geofenceId of the geofence that failed to be saved. +- `error` - an error object + - `code` - the [error code](https://docs.aws.amazon.com/location-geofences/latest/APIReference/API_BatchItemError.html) + - `message` - the error message + +#### Example + +```js +let saveGeofenceResults; +try { + saveGeofenceResults = await Geo.saveGeofences({ + geofenceId: "my-geofence", + geometry: { + polygon: [ + [-123.14695358276366, 49.290090146520434], + [-123.1358814239502, 49.294960279811974], + [-123.15021514892577, 49.29300108863353], + [-123.14909934997559, 49.29132171993048], + [-123.14695358276366, 49.290090146520434], + ], + }, + }); +} catch (error) { + // errors thrown by input validations of `saveGeofences` + throw error; +} + +if (saveGeofenceResults.errors.length > 0) { + // error handling that are from the underlying API calls + console.log(`Success count: ${saveGeofenceResults.successes.length}`); + console.log(`Error count: ${saveGeofenceResults.errors.length}`); +} +``` + +### getGeofence + +`geoGeofence` is used to get a single geofence from a collection. + +#### API + +```javascript +Geo.getGeofence(geofenceId, options) => Promise; +``` + +#### Parameters + +- `geofenceId` - the `id` of the geofence to get. +- `options` - optional options object for getting a geofence + - `collectionName` - the name of the collection to get geofence from. + - Defaults to the default collection listed in your `aws-exports.js` file after provisioning a geofence collection resource. + + +#### Return + +The return from `getGeofence` is a Promise that resolves to a geofence object. + +#### Example + +```javascript +let responses; +try { + response = await Geo.getGeofence("geofenceId"); +} catch (error) { + throw error; +} +``` + +### listGeofences + +`listGeofences` is used to get a list of geofences from a collection. It has pagination built in and will return 100 geofences per page. + +#### API + +```javascript +Geo.listGeofences(options) => Promise; +``` + +#### Parameters + +- `options` - optional options object for saving geofences + - `nextToken` - the pagination token for the next page of geofences. + - if no token is given, it will return the first page of geofences. + - `collectionName` - the name of the collection to save geofences to. + - Defaults to the default collection listed in your `aws-exports.js` file after provisioning a geofence collection resource. + +#### Return + +Returns a Promise that resolves to an object with the following properties: + +- `entries` - an array of geofences +- `nextToken` - the pagination token for the next page of geofences + +#### Example + +```javascript +let response; +try { + response = await Geo.listGeofences(); + response.entries.forEach(geofence => console.log(geofence.geofenceId)) +} catch (error) { + throw error; +} +``` + +### deleteGeofences + +`deleteGeofences` is used to delete a geofences from a collection. It can delete a single or multiple geofences at once. + +#### API + +```js +Geo.deleteGeofences(geofenceIds, options) => Promise; +``` + +#### Parameters + +- `geofenceIds` - a single geofenceId or array of geofenceIds to delete +- `options` - optional options object for saving geofences + - `collectionName` - the name of the collection to save geofences to. + - Defaults to the default collection listed in your `aws-exports.js` file after provisioning a geofence collection resource. + +#### Return + +The return from `deleteGeofences` is a Promise that resolves to an object with both successes and errors for geofences that were successfully deleted or not. + +- The success object is an array of geofenceIds that were successfully deleted. +- The error object is an array of error objects that include the following properties: + - `geofenceId` - the geofenceId of the geofence that failed to be deleted. + - `error` - an error object + - `code` - the [error code](https://docs.aws.amazon.com/location-geofences/latest/APIReference/API_BatchItemError.html) + - `message` - the error + +#### Example + +```js +let response; +try { + response = await Geo.deleteGeofences( + [ + "geofence1", + "geofence2", + "geofence3", + ] + ) +catch (error) { + // error handling from logic and validation issues within `deleteGeofences` + throw error; +} + +if(response.errors.length > 0){ + // error handling that are from the underlying API calls + console.log(`Success count: ${response.successes.length}`); + console.log(`Error count: ${response.errors.length}`); +} +``` diff --git a/src/fragments/lib-legacy/geo/js/getting-started.mdx b/src/fragments/lib-legacy/geo/js/getting-started.mdx new file mode 100644 index 00000000000..76837107484 --- /dev/null +++ b/src/fragments/lib-legacy/geo/js/getting-started.mdx @@ -0,0 +1,74 @@ + + +**Note:** Please reach out to us for any feedback and/or issues [here](https://github.com/aws-amplify/amplify-js/issues) + + +Amplify Geo provides APIs and map UI components for maps and location search for JavaScript-based web apps. You can add maps and location search functionality to your app in just a few lines of code. Amplify Geo APIs are powered by [Amazon Location Service](https://aws.amazon.com/location/) and the map UI components from MapLibre are already integrated with the Geo APIs. You can quickly get started using [Amplify CLI](/cli/geo/maps) to provision your map and location search resources. + +Follow this guide to get started with Amplify Geo through the Amplify CLI. + +**Notes:** +- If you want to use existing Amazon Location Service resources [follow this guide](/lib/geo/existing-resources) instead. +- If you want to use Amazon Location Service APIs not directly supported by Geo, use the [escape hatch](/lib/geo/escapehatch) to access the Amazon Location Service SDK. + +## Provisioning resources through CLI + +> Prerequisite: [Install and configure the Amplify CLI](/cli/start/install) + +The primary way to provision Geo resources is through the Amplify CLI. You can use the following command to install this globally. + +```sh +npm i -g @aws-amplify/cli +``` + +Now, let's make sure that the right version was installed: + +```sh +amplify --version +``` + +> **Note:** Make sure that version `6.1.0` or above is installed. + +Once that is complete, you can run the following command from your project's root folder to initialize Amplify in your repo: + +```sh +amplify init +``` + +The above command will guide you through setting up your project name and preferred authentication profile. + +Now you are able to add a `geo` resource, such as map resources or a search index: + +```sh +amplify add geo +``` + +The CLI will let you configure the Geo category based on the capabilities you want to add (maps and/or search). You can either choose to stick with the defaults or configure advanced settings. Please refer to [Amplify CLI Geo docs](/cli/geo/maps/) for more details on these configurations. + +The add command automatically creates the backend configuration. Once all your configuration is complete, run the following (this might take a few minutes): + +```sh +amplify push +``` + +A file called `aws-exports.js` that contains all geo-related configuration information is placed in your app's source directory. + +For more information, you can visit the full [Amplify CLI Geo Maps docs](/cli/geo/maps). + +## Configure your application + +Install the necessary dependencies by running the following command: + +```sh +npm install aws-amplify +``` + +> **Note:** Make sure that version `4.3.0` or above is installed. + +Import and load the configuration file generated in previous step using Amplify CLI in your app. It’s recommended you add the Amplify configuration step to your app’s root entry point. For example `src/index.js` in a `create-react-app` generated React application or `main.ts` in Angular or Ionic. + +```javascript +import { Amplify } from 'aws-amplify'; +import awsconfig from './aws-exports'; +Amplify.configure(awsconfig); +``` diff --git a/src/fragments/lib-legacy/geo/js/google-migration.mdx b/src/fragments/lib-legacy/geo/js/google-migration.mdx new file mode 100644 index 00000000000..7e775b6a397 --- /dev/null +++ b/src/fragments/lib-legacy/geo/js/google-migration.mdx @@ -0,0 +1,230 @@ +Are you using Google Maps or another similar Map Provider and would like to switch over to using Amplify Geo or Amazon Location Service? This tutorial will show you how to take your existing Google Maps APIs and switch over to using Amplify Geo. + +## Getting Started + +Amplify Geo provides APIs for using location based functionality. Under the hood Amplify uses [Amazon Location Service](https://aws.amazon.com/location/) and is designed to work with open source mapping library [MapLibre](https://maplibre.org/). + +This guide assumes that you are already familiar with the Google Maps JavaScript API and with front-end web development concepts including HTML, CSS, and JavaScript. + +To complete this tutorial, you will need: + +- **Amplify Geo** +- **A text editor** + +## Key differences between Amplify Geo and Google Maps + +### Coordinates Conventions + +A key difference to notice between using Amplify Geo and Google Maps is with Google Maps Platform their convention for specifying coordinates is `[lat, lng]`. When migrating over to Amplify Geo the order is swapped to be `[lng, lat]`. This was done to match the [geojson spec](https://geojson.org/) which is also used by MapLibre. + +### Authorization + +When using Google Maps Platform or other similar services like Mapbox you will first be prompted to go to the Google Cloud Console to set up APIs and create an API key where you will then use the API key when requesting the Google Maps JS API. With Amplify Geo you will instead setup Amplify Auth using Amplify CLI and the `MapView` component will read the auth configuration from the `aws-exports.js` file. Behind the scenes Amplify Auth uses Amazon Cognito to set up client credentials with access to Location Service and Geo will use those credentials when making any location related API calls. More information on setting Amplify Auth and Geo can be found below in the `Setting Up Amplify` section. + +## Create a webpage + +1. Open your text editor and create a new file called `index.html`. +1. Paste the following code into the file to set up the framework for a webpage with a map. + +```html + + + + + + Display a map on a webpage + + + + + + + + + + + + + + + + +
+ + + + +``` + +This code imports the MapLibre GL JS library and CSS, one of the popular options for map rendering we recommend for use with Amplify Geo. In the HTML body we create a `
` element with an id of 'map' that will be the map's container. Finally in the script section we setup some Amplify configuration that is required for Amplify Geo to understand what Amplify AWS resources have been created. + +### Setting up Amplify + +1. You will need to use the [Amplify CLI to setup Amplify Geo Map resources](/cli/geo/maps). Follow instructions for creating a map. +1. Once the CLI workflow has completed you should have an `aws-exports.js` file in the same directory as your `index.html` file. +1. Save your `index.html` file. + +## Display a map + +In this step we will show you how to add code to display a map in your application. + + + + + +With Amplify Geo and MapLibre you can add the following code to your index.html file inside the ` + + + + +``` + +Next, add a div element with id `map` anywhere in your webpage where you want to render the map. Include the following code snippet to configure Amplify (update the `aws_exports.js` file path accordingly) and instantiate the map. + +```html + +``` + + +### Sample application +```html + + + + + Display a map on a webpage + + + + + + + + + + +
+ + + +``` + +## Map API's + +If you want more information about the maps you currently have configured or want a way to switch between maps programmatically, the `@aws-amplify/geo` package provides API's that return more information about your currently provisioned maps. + +First, you need to import Geo from either the `@aws-amplify/geo` or `aws-amplify` packages. + +```javascript +import { Geo } from "@aws-amplify/geo"; +// Or +import { Geo } from "aws-amplify"; +``` + +### getAvailableMaps + +`getAvailableMaps` will return the map resources you currently have provisioned in your Amplify application. You can switch between any of these different maps and display their different map styles. + +#### API + +```javascript +Geo.getAvailableMaps() => Promise; +``` + +#### Parameters + +- N/A + +#### Return + +The return from `getAvailableMaps` is a Promise that resolves to `AmazonLocationServiceMapStyle[]` which is an array of `mapName`, `style`, and `region`. + +Each object has the following properties: + +- `mapName` - name of the map you created. +- `style` - the Amazon Location Service style used to create the map. +- `region` - the AWS region the map is hosted in. + + + +**Note:** When changing a map with Amplify and MapLibre the [setStyle](https://maplibre.org/maplibre-gl-js-docs/api/map/#map#setstyle) function should be called with the name of the Location Service map NOT the style. This is because the `transformRequest` function uses the Location Service map name to make a new request for map tile data. + + + +#### Example + +```js +const availableMaps = await Geo.getAvailableMaps(); + +map.setStyle(availableMaps[0].mapName); +``` + +### getDefaultMap + +`getDefaultMap` is used to get a the default map object. + +#### API + +```javascript +Geo.getDefaultMap() => Promise; +``` + +#### Parameters + +- N/A + +#### Return + +The return from `getDefaultMap` is a Promise that resolves to a AmazonLocationServiceMapStyle object. + +The object has the following properties: + +- `mapName` - name of the map you created. +- `style` - the Amazon Location Service style used to create the map. +- `region` - the AWS region the map is hosted in. + +#### Example + +```javascript +const defaultMap = await Geo.getDefaultMap(); +``` diff --git a/src/fragments/lib-legacy/geo/js/search.mdx b/src/fragments/lib-legacy/geo/js/search.mdx new file mode 100644 index 00000000000..ebb9b001069 --- /dev/null +++ b/src/fragments/lib-legacy/geo/js/search.mdx @@ -0,0 +1,179 @@ + + +**Note:** Please reach out to us for any feedback and/or issues [here](https://github.com/aws-amplify/amplify-js/issues) + + + +## Add location search functionality on a map + +First, make sure you've provisioned a search index resource and configured your app using the instructions in either [Amplify CLI - Geo - Location Search](/cli/geo/search) or [Use existing Amazon Location Service resources](/lib/geo/existing-resources) and you have already setup [displaying a map](/lib/geo/maps) in your application. + + + +**Note:** For React, you can use the [Amplify UI Geocoder component](https://ui.docs.amplify.aws/react/components/geo#geocoder) to display the search results on a map. + + + +To add a location search UI component to your map, you can use the [maplibre-gl-geocoder](https://github.com/maplibre/maplibre-gl-geocoder) library. `maplibre-gl-js-amplify` package makes it easy to integrate `maplibre-gl-geocoder` with Amplify Geo by exporting a utility function `createAmplifyGeocoder()` that returns an instance of `maplibre-gl-geocoder` with some pre-defined settings and supports all the [options](https://github.com/maplibre/maplibre-gl-geocoder/blob/main/API.md#parameters) for customizing the UI component + +Install the necessary dependencies with the following command: + +```bash +npm install @maplibre/maplibre-gl-geocoder maplibre-gl@1 maplibre-gl-js-amplify +``` + +> **Note:** Make sure that `maplibre-gl-js-amplify` version `1.1.0` or above is installed. + +First, create a map onto which you want to add the location search UI component. See the guide on [creating and displaying maps](https://docs.amplify.aws/lib/geo/maps/q/platform/js/). + +Then, use `createAmplifyGeocoder()` to get a new instance of `MaplibreGeocoder` and add the location search UI component to the map. + +> **Note:** Ensure that your package bundler (webpack, rollup, etc) is configured to handle css files. Check out the webpack documentation [here](https://webpack.js.org/loaders/css-loader/). + +```javascript +import { createMap, createAmplifyGeocoder } from "maplibre-gl-js-amplify"; +import maplibregl from "maplibre-gl"; +import "maplibre-gl/dist/maplibre-gl.css"; +import "@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css"; +import "maplibre-gl-js-amplify/dist/public/amplify-geocoder.css"; // Optional CSS for Amplify recommended styling + +async function initializeMap() { + const el = document.createElement("div"); + el.setAttribute("id", "map"); + document.body.appendChild(el); + + const map = await createMap({ + container: "map", + center: [-123.1187, 49.2819], // [Longitude, Latitude] + zoom: 11, + }) + + map.addControl(createAmplifyGeocoder()); +} + +initializeMap(); +``` + +![A search box on the top right corner of a map](/images/geocoder-search-box-map.png) + +### Display the location search box outside the map + +You can also use [maplibre-gl-geocoder](https://github.com/maplibre/maplibre-gl-geocoder) to display the location search UI component anywhere in your application, even outside the map. + +To do so, extract the html element using function `onAdd()` and attach it anywhere in your DOM instead of adding it via the map's `addControl()` function. + +```javascript +const geocoder = createAmplifyGeocoder(); +document.getElementById("search").appendChild(geocoder.onAdd()); +``` + +![A search box](/images/geocoder-search-box.png) + +### Customize Search Icons + +You can customize the search icons used by the [maplibre-gl-geocoder](https://github.com/maplibre/maplibre-gl-geocoder) to use any image of your choosing. [MapLibre markers](https://maplibre.org/maplibre-gl-js-docs/api/markers/) require an [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) when passing in custom images. + +The following example puts an existing SVG icon into an HTMLElement before being passed to `createAmplifyGeocoder` which creates a [maplibre-gl-geocoder](https://github.com/maplibre/maplibre-gl-geocoder). + +```javascript +import myIcon from "./myIcon.svg" // relative path to your custom icon + +const icon = new Image(100, 100); +icon.src = myIcon; + +const geocoder = createAmplifyGeocoder({ showResultMarkers: { element: icon } }); +map.addControl(geocoder); +``` + +![A search box](/images/geocoder-custom-images.png) + +## Location-based search capabilities + +Amplify Geo enables you to search for locations by text, addresses, or geo-coordinates. + +### Search by text, address, business name, city, and more + +The `Geo.searchByText()` API enables you to search for places or points of interest by free-form text, such as an address, name, city, or region. + +```javascript +import { Geo } from "aws-amplify" + +Geo.searchByText("Amazon Go Store") +``` + +Customize your search results further by providing: +- `countries` - to limit the search results to given countries (specified in [ISO Alpha-3 country codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3)) +- `maxResults` - to limit the maximum result set +- `biasPosition` - to act as the search origination location +- `searchAreaConstraints` - to limit the area to search inside of +- `searchIndexName` - to use a different Location Service search index resource than the default + + **Note:** Providing both `biasPosition` and `searchAreaConstraints` parameters simultaneously returns an error. + +```javascript +const searchOptionsWithBiasPosition = { + countries: string[], // Alpha-3 country codes + maxResults: number, // 50 is the max and the default + biasPosition: [ + longitude // number + latitude // number, + ], // Coordinates point to act as the center of the search + searchIndexName: string, // the string name of the search index +} + +const searchOptionsWithSearchAreaConstraints = { + countries: ["USA"], // Alpha-3 country codes + maxResults: 25, // 50 is the max and the default + searchAreaConstraints: [SWLongitude, SWLatitude, NELongitude, NELatitude], // Bounding box to search inside of + searchIndexName: string, // the string name of the search index +} + +Geo.searchByText('Amazon Go Stores', searchOptionsWithBiasPosition) +``` + +This returns places and their coordinates that match the search constraints. A place can also have additional metadata as shown in the example below. + +```javascript +// returns +[ + { + geometry: { + point: + [ + -122.34014899999994, // Longitude point + 47.61609000000004 // Latitude point + ], + }, + addressNumber: "2131" // optional string for the address number alone + country: "USA" // optional Alpha-3 country code + label: "Amazon Go, 2131 7th Ave, Seattle, WA, 98121, USA" // Optional string + municipality: "Seattle" // Optional string + neighborhood: undefined // Optional string + postalCode: "98121" // Optional string + region: "Washington" // Optional string + street: "7th Ave" // Optional string + subRegion: "King County" // Optional string + } +] +``` + +### Search by coordinates + +The `Geo.searchByCoordinates()` API is a reverse Geocoder that takes a coordinate point and returns information about what it finds at that point on the map. The returned object is the same shape as `searchByText()` API above. + +```javascript +import { Geo } from "aws-amplify"; + +Geo.searchByCoordinates([longitudePoint, latitudePoint]) +``` + +You can optionally limit your result set with the `maxResults` parameter or override the default search index with the `searchIndexName` parameter. + +```javascript +const searchOptionsWithBiasPosition = { + maxResults: number, // 50 is the max and the default + searchIndexName: string, // the string name of the search index +} + +Geo.searchByCoordinates([-122.3399573, 47.616179], searchOptionsWithBiasPosition) +``` diff --git a/src/fragments/lib-legacy/geo/native_common/getting_started/common.mdx b/src/fragments/lib-legacy/geo/native_common/getting_started/common.mdx new file mode 100644 index 00000000000..7f474162c8f --- /dev/null +++ b/src/fragments/lib-legacy/geo/native_common/getting_started/common.mdx @@ -0,0 +1,48 @@ +Amplify Geo provides APIs and map UI components for mobile app development such that you can add maps to your app in just a few lines of code. +Amplify Geo APIs are powered by [Amazon Location Service](https://aws.amazon.com/location/) and the map UI components from [MapLibre](https://maplibre.org/) are already integrated with the Geo APIs. You can quickly get started using [Amplify CLI](/cli/geo/maps) to provision your map resources. + +Follow this guide to get started with Amplify Geo through the Amplify CLI. + +**Note:** +- If you want to use existing Amazon Location Service resources [follow this guide](/lib/geo/existing-resources) instead. +- If you want to use Amazon Location Service APIs not directly supported by Geo, use the [escape hatch](/lib/geo/escapehatch) to access the Amazon Location Service SDK. + +## Prerequisites + +import android3 from "/src/fragments/lib/geo/android/getting_started/10_pre_req.mdx"; + + + +import ios4 from "/src/fragments/lib/geo/ios/getting_started/10_pre_req.mdx"; + + + +## Provisioning resources through CLI + +import android6 from "/src/fragments/lib/geo/android/getting_started/20_cli_resources.mdx"; + + + +import ios7 from "/src/fragments/lib/geo/ios/getting_started/20_cli_resources.mdx"; + + + +## Install Amplify Libraries + +import android9 from "/src/fragments/lib/geo/android/getting_started/30_install_lib.mdx"; + + + +import ios10 from "/src/fragments/lib/geo/ios/getting_started/30_install_lib.mdx"; + + + +## Initialize Amplify Geo + +import android12 from "/src/fragments/lib/geo/android/getting_started/40_init_geo.mdx"; + + + +import ios13 from "/src/fragments/lib/geo/ios/getting_started/40_init_geo.mdx"; + + diff --git a/src/fragments/lib-legacy/geo/native_common/maps/common.mdx b/src/fragments/lib-legacy/geo/native_common/maps/common.mdx new file mode 100644 index 00000000000..ec71fea11cc --- /dev/null +++ b/src/fragments/lib-legacy/geo/native_common/maps/common.mdx @@ -0,0 +1,31 @@ +## Install Amplify-MapLibre adapter + +import android3 from "/src/fragments/lib/geo/android/maps/10_install_adapter.mdx"; + + + +import ios4 from "/src/fragments/lib/geo/ios/maps/10_install_adapter.mdx"; + + + +## Display a map + +First, ensure you've provisioned an Amazon Location Service Map resource and configured your app using the instructions in either [Getting started](/lib/geo/getting-started) or [Use existing resources](/lib/geo/existing-resources) guide. + +import android6 from "/src/fragments/lib/geo/android/maps/20_display_map.mdx"; + + + +import ios7 from "/src/fragments/lib/geo/ios/maps/20_display_map.mdx"; + + + +## Display different map styles + +import android9 from "/src/fragments/lib/geo/android/maps/30_styles.mdx"; + + + +import ios10 from "/src/fragments/lib/geo/ios/maps/30_styles.mdx"; + + diff --git a/src/fragments/lib-legacy/geo/native_common/search/common.mdx b/src/fragments/lib-legacy/geo/native_common/search/common.mdx new file mode 100644 index 00000000000..4ce46a85e8b --- /dev/null +++ b/src/fragments/lib-legacy/geo/native_common/search/common.mdx @@ -0,0 +1,31 @@ +## Add location search functionality on a map + +First, make sure you've provisioned a search index resource and configured your app using the instructions in either [Amplify CLI - Geo - Location Search](/cli/geo/search) or [Use existing Amazon Location Service resources](/lib/geo/existing-resources) and you have already setup [displaying a map](/lib/geo/maps) in your application. + +import android1 from "/src/fragments/lib/geo/android/search/30_location_search_map.mdx"; + + + +import ios2 from "/src/fragments/lib/geo/ios/search/30_location_search_map.mdx"; + + + +## Location-based search capabilities + +Amplify Geo enables you to search for locations by text, addresses, or geo-coordinates. + +import android3 from "/src/fragments/lib/geo/android/search/10_search_by_text.mdx"; + + + +import ios4 from "/src/fragments/lib/geo/ios/search/10_search_by_text.mdx"; + + + +import android6 from "/src/fragments/lib/geo/android/search/20_search_by_coordinates.mdx"; + + + +import ios7 from "/src/fragments/lib/geo/ios/search/20_search_by_coordinates.mdx"; + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/10_example.mdx b/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/10_example.mdx new file mode 100644 index 00000000000..7108accf84d --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/10_example.mdx @@ -0,0 +1,65 @@ + + + +```java +Todo todo = Todo.builder() + .name("My first todo") + .description("todo description") + .build(); + +Amplify.API.mutate(ModelMutation.create(todo), + result -> Log.i("ApiDemo", "Mutation succeeded."), + failure -> Log.e("ApiDemo", "Mutation failed.", failure) +); +``` + + + + +```kotlin +val todo = Todo.builder() + .name("My first todo") + .description("todo description") + .build() + +Amplify.API.mutate(ModelMutation.create(todo), + { Log.i("ApiDemo", "Mutation succeeded") }, + { Log.e("ApiDemo", "Mutation failed", it) } +) +``` + + + + +```kotlin +val todo = Todo.builder() + .name("My first todo") + .description("todo description") + .build() + +try { + val result = Amplify.API.mutate(ModelMutation.create(todo)) + Log.i("ApiDemo", "Mutation succeeded") +} catch (error: ApiException) { + Log.e("ApiDemo", "Mutation failed", error) +} +``` + + + + +```java +Todo todo = Todo.builder() + .name("My first todo") + .description("todo description") + .build(); + +Amplify.API.mutate(ModelMutation.create(todo)) + .subscribe( + result -> Log.i("ApiDemo", "Mutation succeeded"), + failure -> Log.e("ApiDemo", "Mutation failed", failure) + ); +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/20_custom.mdx b/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/20_custom.mdx new file mode 100644 index 00000000000..38b1f27a55d --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/20_custom.mdx @@ -0,0 +1,77 @@ + + + +```java +private GraphQLRequest getTodoRequest(String id) { + String document = "query getTodo($id: ID!) { " + + "getTodo(id: $id) { " + + "id " + + "name " + + "}" + + "}"; + return new SimpleGraphQLRequest<>( + document, + Collections.singletonMap("id", id), + Todo.class, + new GsonVariablesSerializer()); +} +``` + + + + +```kotlin +private fun getTodoRequest(id: String): GraphQLRequest { + val document = ("query getTodo(\$id: ID!) { " + + "getTodo(id: \$id) { " + + "id " + + "name " + + "}" + + "}") + return SimpleGraphQLRequest( + document, + mapOf("id" to id), + Todo::class.java, + GsonVariablesSerializer()) +} +``` + + + + +Then, query for the Todo by a todo id + + + + +```java +Amplify.API.query(getTodoRequest("[TODO_ID]"), + response -> Log.d("MyAmplifyApp", "response" + response), + error -> Log.e("MyAmplifyApp", "error" + error) +); +``` + + + + +```kotlin +Amplify.API.query(getTodoRequest("[TODO_ID]"), + { Log.d("MyAmplifyApp", "Response = $it") }, + { Log.e("MyAmplifyApp", "Error!", it) } +) +``` + + + + +```kotlin +try { + val response = Amplify.API.query(getTodoRequest("[TODO_ID]")) + Log.d("MyAmplifyApp", "Query response = $response") +} catch (error: ApiException) { + Log.e("MyAmplifyApp", "Query failed", error) +} +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/30_nested.mdx b/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/30_nested.mdx new file mode 100644 index 00000000000..70e0c7838bb --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/30_nested.mdx @@ -0,0 +1,95 @@ + + + +```java +private GraphQLRequest getPostWithCommentsRequest(String id) { + String document = "query getPost($id: ID!) { " + + "getPost(id: $id) { " + + "id " + + "title " + + "rating " + + "status " + + "comments { " + + "items { " + + "id " + + "postID " + + "content " + + "} " + + "} " + + "} " + + "}"; + return new SimpleGraphQLRequest<>( + document, + Collections.singletonMap("id", id), + Post.class, + new GsonVariablesSerializer()); +} +``` + + + + +```kotlin +private fun getPostWithCommentsRequest(id: String): GraphQLRequest { + val document = ("query getPost(\$id: ID!) { " + + "getPost(id: \$id) { " + + "id " + + "title " + + "rating " + + "status " + + "comments { " + + "items { " + + "id " + + "postID " + + "content " + + "} " + + "} " + + "} " + + "}") + return SimpleGraphQLRequest( + document, + mapOf("id" to id), + Post::class.java, + GsonVariablesSerializer()) +} +``` + + + + +Then query for the Post: + + + + +```java +Amplify.API.query(getPostWithCommentsRequest("[POST_ID]"), + response -> Log.d("MyAmplifyApp", "response" + response), + error -> Log.e("MyAmplifyApp", "error" + error) +); +``` + + + + +```kotlin +Amplify.API.query(getPostWithCommentsRequest("[POST_ID]"), + { Log.d("MyAmplifyApp", "Response = $it") }, + { Log.e("MyAmplifyApp", "Error!", it) } +) +``` + + + + +```kotlin +try { + val response = Amplify.API.query(getPostWithCommentsRequest("[POST_ID]")) + Log.d("MyAmplifyApp", "Query response = $response") +} catch (error: ApiException) { + Log.e("MyAmplifyApp", "Query error", error) +} +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/40_multiple.mdx b/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/40_multiple.mdx new file mode 100644 index 00000000000..bb19fd996a8 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/40_multiple.mdx @@ -0,0 +1,54 @@ + + + + +```java +private GraphQLRequest getPostAndTodo(String postId, String todoId) { + String document = "query get($postId: ID!, $todoId: ID!) { " + + "getPost(id: $postId) { " + + "id " + + "title " + + "rating " + + "} " + + "getTodo(id: $todoId) { " + + "id " + + "name " + + "} " + + "}"; + Map variables = new HashMap<>(); + variables.put("postId", postId); + variables.put("todoId", todoId); + return new SimpleGraphQLRequest<>( + document, + variables, + String.class, + new GsonVariablesSerializer()); +} +``` + + + + +```kotlin +private fun getPostAndTodo(postId: String, todoId: String): GraphQLRequest { + val document = ("query get(\$postId: ID!, \$todoId: ID!) { " + + "getPost(id: \$postId) { " + + "id " + + "title " + + "rating " + + "} " + + "getTodo(id: \$todoId) { " + + "id " + + "name " + + "} " + + "}") + + return SimpleGraphQLRequest( + document, + mapOf("postId" to postId, "todoId" to todoId), + String::class.java, + GsonVariablesSerializer()) +} +``` + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/50_interceptor.mdx b/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/50_interceptor.mdx new file mode 100644 index 00000000000..4c2e6f96ef1 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/advanced-workflows/50_interceptor.mdx @@ -0,0 +1,76 @@ +To specify your own headers, use the `configureClient()` configuration option on the `AWSApiPlugin`'s builder. Specify the name of one of the configured APIs in your **amplifyconfiguration.json**. Apply customizations to the underlying OkHttp instance by providing a lambda expression as below. + + + + +```java +AWSApiPlugin plugin = AWSApiPlugin.builder() + .configureClient("yourApiName", okHttpBuilder -> { + okHttpBuilder.addInterceptor(chain -> { + Request originalRequest = chain.request(); + Request updatedRequest = originalRequest.newBuilder() + .addHeader("customHeader", "someValue") + .build(); + return chain.proceed(updatedRequest); + }); + }) + .build(); +Amplify.addPlugin(plugin); +``` + + + + +```kotlin +val plugin = AWSApiPlugin.builder() + .configureClient("yourApiName") { okHttpBuilder -> + okHttpBuilder.addInterceptor { chain -> + val originalRequest = chain.request() + val updatedRequest = originalRequest.newBuilder() + .addHeader("customHeader", "someValue") + .build() + chain.proceed(updatedRequest) + } + } + .build() +Amplify.addPlugin(plugin) +``` + + + + +```kotlin +val plugin = AWSApiPlugin.builder() + .configureClient("yourApiName") { okHttpBuilder -> + okHttpBuilder.addInterceptor { chain -> + val originalRequest = chain.request() + val updatedRequest = originalRequest.newBuilder() + .addHeader("customHeader", "someValue") + .build() + chain.proceed(updatedRequest) + } + } + .build() +Amplify.addPlugin(plugin) +``` + + + + +```java +AWSApiPlugin plugin = AWSApiPlugin.builder() + .configureClient("yourApiName", okHttpBuilder -> { + okHttpBuilder.addInterceptor(chain -> { + Request originalRequest = chain.request(); + Request updatedRequest = originalRequest.newBuilder() + .addHeader("customHeader", "someValue") + .build(); + return chain.proceed(updatedRequest); + }); + }) + .build(); +RxAmplify.addPlugin(plugin); +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/authz/10_userpool.mdx b/src/fragments/lib-legacy/graphqlapi/android/authz/10_userpool.mdx new file mode 100644 index 00000000000..28202bbb033 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/authz/10_userpool.mdx @@ -0,0 +1,20 @@ +Add the following code to your app: + + + + +```java +Amplify.addPlugin(new AWSCognitoAuthPlugin()); +Amplify.addPlugin(new AWSApiPlugin()); +``` + + + + +```kotlin +Amplify.addPlugin(AWSCognitoAuthPlugin()) +Amplify.addPlugin(AWSApiPlugin()) +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/authz/20_oidc.mdx b/src/fragments/lib-legacy/graphqlapi/android/authz/20_oidc.mdx new file mode 100644 index 00000000000..0ce1d41f5e7 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/authz/20_oidc.mdx @@ -0,0 +1,30 @@ +Add the following code to your app: + + + + +```java +ApiAuthProviders authProviders = ApiAuthProviders.builder() + .oidcAuthProvider(() -> "[OPEN-ID-CONNECT-TOKEN]") + .build(); +AWSApiPlugin plugin = AWSApiPlugin.builder() + .apiAuthProviders(authProviders) + .build(); +Amplify.addPlugin(plugin); +``` + + + + +```kotlin +val authProviders = ApiAuthProviders.builder() + .oidcAuthProvider { "[OPEN-ID-CONNECT-TOKEN]" } + .build() +val plugin = AWSApiPlugin.builder() + .apiAuthProviders(authProviders) + .build() +Amplify.addPlugin(plugin) +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/authz/21_oidc.mdx b/src/fragments/lib-legacy/graphqlapi/android/authz/21_oidc.mdx new file mode 100644 index 00000000000..7fb8957d57a --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/authz/21_oidc.mdx @@ -0,0 +1,128 @@ +Add the following code to your app to initialize API plugin with OIDC auth provider: + + + + +This implementation uses `CompletableFuture`, which requires `minSdkVersion >= 24`. + +```java +ApiAuthProviders authProviders = ApiAuthProviders.builder() + .oidcAuthProvider(() -> { + CompletableFuture future = new CompletableFuture<>(); + Amplify.Auth.fetchAuthSession( + session -> future.complete(((AWSCognitoAuthSession) session) + .getUserPoolTokens() + .getValue() + .getIdToken()), + future::completeExceptionally + ); + try { + return future.get(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + }) + .build(); +AWSApiPlugin plugin = AWSApiPlugin.builder() + .apiAuthProviders(authProviders) + .build(); +Amplify.addPlugin(plugin); +``` + + + + +This implementation uses `CompletableFuture`, which requires `minSdkVersion >= 24`. + +```kotlin +val authProviders = ApiAuthProviders.builder() + .oidcAuthProvider { + val future = CompletableFuture() + Amplify.Auth.fetchAuthSession( + { future.complete((it as AWSCognitoAuthSession).userPoolTokens.value?.idToken) }, + { future.completeExceptionally(it) } + ) + future.get() + } + .build() +val plugin = AWSApiPlugin.builder() + .apiAuthProviders(authProviders) + .build() +Amplify.addPlugin(plugin) +``` + + + + +This implementation uses `CompletableFuture`, which requires `minSdkVersion >= 24`. + +```kotlin +val authProviders = ApiAuthProviders.builder() + .oidcAuthProvider { + val session = runBlocking { Amplify.Auth.fetchAuthSession() } + return (session as AWSCognitoAuthSession).userPoolTokens.value?.idToken + } + .build() +val plugin = AWSApiPlugin.builder() + .apiAuthProviders(authProviders) + .build() +Amplify.addPlugin(plugin) +``` + + + + +Using the `rxbindings` module can simplify this further. + +```groovy +dependencies { + // other dependencies... + implementation 'com.amplifyframework:rxbindings:ANDROID_VERSION' +} +``` + +```java +ApiAuthProviders authProviders = ApiAuthProviders.builder() + .oidcAuthProvider(() -> RxAmplify.Auth.fetchAuthSession() + .map(session -> ((AWSCognitoAuthSession) session) + .getUserPoolTokens() + .getValue() + .getIdToken()) + .blockingGet()) + .build(); +AWSApiPlugin plugin = AWSApiPlugin.builder() + .apiAuthProviders(authProviders) + .build(); +Amplify.addPlugin(plugin); +``` + + + + +Using the `rxbindings` module can simplify this further. + +```groovy +dependencies { + // other dependencies... + implementation 'com.amplifyframework:rxbindings:ANDROID_VERSION' +} +``` + +```kotlin +val authProviders = ApiAuthProviders.builder() + .oidcAuthProvider { RxAmplify.Auth.fetchAuthSession() + .map { (it as AWSCognitoAuthSession) + .userPoolTokens + .value + ?.idToken } + .blockingGet() } + .build() +val plugin = AWSApiPlugin.builder() + .apiAuthProviders(authProviders) + .build() +Amplify.addPlugin(plugin) +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/authz/22_lambda.mdx b/src/fragments/lib-legacy/graphqlapi/android/authz/22_lambda.mdx new file mode 100644 index 00000000000..1971f3eb922 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/authz/22_lambda.mdx @@ -0,0 +1,30 @@ +Add the following code to your app: + + + + +```java +ApiAuthProviders authProviders = ApiAuthProviders.builder() + .functionAuthProvider(() -> "[AWS-LAMBDA-AUTH-TOKEN]") + .build(); +AWSApiPlugin plugin = AWSApiPlugin.builder() + .apiAuthProviders(authProviders) + .build(); +Amplify.addPlugin(plugin); +``` + + + + +```kotlin +val authProviders = ApiAuthProviders.builder() + .functionAuthProvider { "[AWS-LAMBDA-AUTH-TOKEN]" } + .build() +val plugin = AWSApiPlugin.builder() + .apiAuthProviders(authProviders) + .build() +Amplify.addPlugin(plugin) +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/authz/30_multi.mdx b/src/fragments/lib-legacy/graphqlapi/android/authz/30_multi.mdx new file mode 100644 index 00000000000..411c7675b78 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/authz/30_multi.mdx @@ -0,0 +1,41 @@ +When you have configured multiple APIs, you can specify the name of the API as a parameter as the target for an operation: + + + + +```java +Amplify.API.mutate( + "[FRIENDLY-NAME-API-WITH-API-KEY]", + request, + response -> Log.i("MyAmplifyApp", "Mutation successful"), + error -> Log.e("MyAmplifyApp", "Failed to mutate model.", error) +); +``` + + + + +```kotlin +Amplify.API.mutate( + "[FRIENDLY-NAME-API-WITH-API-KEY]", + request, + { Log.i("MyAmplifyApp", "Mutation successful") }, + { Log.e("MyAmplifyApp", "Failed to mutate model.", it) } +) +``` + + + + +```kotlin +try { + val apiName = "[FRIENDLY-NAME-API-WITH-API-KEY]" + val response = Amplify.API.mutate(request, apiName) + Log.i("MyAmplifyApp", "Mutation successful") +} catch (error: ApiException) { + Log.e("MyAmplifyApp", "Failed to mutate model.", error) +} +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/getting-started.mdx b/src/fragments/lib-legacy/graphqlapi/android/getting-started.mdx new file mode 100644 index 00000000000..e51a788002c --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/getting-started.mdx @@ -0,0 +1,156 @@ +> ### Prerequisites +> * An [Android project](https://developer.android.com/training/basics/firstapp/creating-project) targeting Android API level 16 (Android 4.1) or above +> * [Install and configure](/cli/start/install) the Amplify CLI + +## GraphQL API with Amplify + +The Amplify API category provides a solution for making HTTP requests to REST and GraphQL endpoints. For GraphQL, it supports [AWS AppSync](https://aws.amazon.com/appsync/). + +## Create GraphQL API service + +Run the following command in your project's root folder: + +```bash +amplify add api +``` + +```console +? Please select from one of the below mentioned services: `GraphQL` +? Provide API name: `apiName` +? Choose the default authorization type for the API: `API key` +? Enter a description for the API key: +? After how many days from now the API key should expire (1-365): `30` +? Do you want to configure advanced settings for the GraphQL API `No, I am done.` +? Do you have an annotated GraphQL schema? `No` +? Choose a schema template: `One-to-many relationship (e.g., "Blogs" with "Posts" and "Comments")` +? Do you want to edit the schema now? `No` +``` + +This will create the following schema for us to get started with: +```graphql +type Blog @model { + id: ID! + name: String! + posts: [Post] @hasMany +} + +type Post @model { + id: ID! + title: String! + blog: Blog @hasOne + comments: [Comment] @hasMany +} + +type Comment @model { + id: ID! + content: String + post: Post @belongsTo +} +``` + +To deploy the API, you can use the Amplify `push` command: + +```bash +amplify push +``` + +```console +? Are you sure you want to continue? `Yes` +? Do you want to generate code for your newly created GraphQL API `No` +``` + +When your backend is successfully provisioned, there should be two new generated files : `amplifyconfiguration.json` and `awsconfiguration.json` in your `app/src/main/res/raw` directory. + +To view the deployed services in your project at any time, go to Amplify Console by running the following command: + +```bash +amplify console +``` + +Generate the Java models to easily perform operations on your schema with the following command: + +```console +$ amplify codegen models + +The following types do not have '@auth' enabled. Consider using @auth with @model + - Blog + - Post + - Comment +Learn more about [@auth here](/cli/graphql/authorization-rules). + +GraphQL schema compiled successfully. +``` + +This will generate the Model files to be used with `Amplify.API` to query, mutate, and subscribe to your AppSync endpoint. After build completes, the model files will be generated under `app/src/main/java/com/amplifyframework.datastore.generated.model`. + +Note: You will see import errors in these files until performing the next steps below. + +## Configure your application + +Open your **project** `build.gradle` and add `mavenCentral()` as a repository: + +```groovy +buildscript { + repositories { + mavenCentral() + } +} +``` + +Next, add the following dependencies to your **app** `build.gradle`: + +```groovy +dependencies { + implementation 'com.amplifyframework:aws-api:ANDROID_VERSION' + + // Support for Java 8 features + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' +} +``` +Also in your **app** `build.gradle`, add this piece of code to support the Java 8 features Amplify uses: + +```groovy +android { + compileOptions { + // Support for Java 8 features + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} +``` + +Sync the project with Maven and ensure it builds successfully. + +## Initialize Amplify + +Add the following imports at the top of your MainActivity and code at the bottom of the `onCreate` method (ideally this would go in your Application class but this works for getting started quickly): + +```java +import com.amplifyframework.AmplifyException; +import com.amplifyframework.api.aws.AWSApiPlugin; +import com.amplifyframework.core.Amplify; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + try { + Amplify.addPlugin(new AWSApiPlugin()); + Amplify.configure(getApplicationContext()); + Log.i("ApiQuickstart", "All set and ready to go!"); + } catch (AmplifyException exception) { + Log.e("ApiQuickstart", exception.getMessage(), exception); + } + } +} +``` + +### Summary + +Now you should be able to build and run your Android application and see "All set and ready to go!" in logcat. + +In this example, you setup a GraphQL API using the Amplify CLI, autogenerated Java Model classes from the API schema, and integrated it into your Android application. diff --git a/src/fragments/lib-legacy/graphqlapi/android/getting-started/10_preReq.mdx b/src/fragments/lib-legacy/graphqlapi/android/getting-started/10_preReq.mdx new file mode 100644 index 00000000000..f94352d196b --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/getting-started/10_preReq.mdx @@ -0,0 +1,3 @@ +* An Android application targeting Android API level 16 (Android 4.1) or above + * For a full example of creating Android project, please follow the [project setup walkthrough](/lib/project-setup/create-application) + diff --git a/src/fragments/lib-legacy/graphqlapi/android/getting-started/12_amplifyConfig.mdx b/src/fragments/lib-legacy/graphqlapi/android/getting-started/12_amplifyConfig.mdx new file mode 100644 index 00000000000..cfb6aadddc5 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/getting-started/12_amplifyConfig.mdx @@ -0,0 +1 @@ +Upon completion, `amplifyconfiguration.json` will be updated to reference provisioned backend resources. Note that this file should already be a part of your project if you followed the [Project setup walkthrough](/lib/project-setup/create-application). diff --git a/src/fragments/lib-legacy/graphqlapi/android/getting-started/20_installLib.mdx b/src/fragments/lib-legacy/graphqlapi/android/getting-started/20_installLib.mdx new file mode 100644 index 00000000000..e90eb2659ee --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/getting-started/20_installLib.mdx @@ -0,0 +1,11 @@ +Expand **Gradle Scripts**, open **build.gradle (Module: app)**. You will already have configured Amplify by following the steps in the [Project Setup walkthrough](/lib/project-setup/create-application). + +Add these libraries into the `dependencies` block: +```groovy +dependencies { + implementation 'com.amplifyframework:core:ANDROID_VERSION' + implementation 'com.amplifyframework:aws-api:ANDROID_VERSION' +} +``` + +Click **Sync Now**. diff --git a/src/fragments/lib-legacy/graphqlapi/android/getting-started/30_initapi.mdx b/src/fragments/lib-legacy/graphqlapi/android/getting-started/30_initapi.mdx new file mode 100644 index 00000000000..caee24e26df --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/getting-started/30_initapi.mdx @@ -0,0 +1,89 @@ +Call `Amplify.addPlugin()` to initialize the Amplify API category followed by `Amplify.configure()`. + +Add the following code to your `onCreate()` method in your application class: + + + + +```java +Amplify.addPlugin(new AWSApiPlugin()); +``` + +Your class will look like this: + +```java +public class MyAmplifyApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + try { + // Add these lines to add the AWSApiPlugin plugins + Amplify.addPlugin(new AWSApiPlugin()); + Amplify.configure(getApplicationContext()); + + Log.i("MyAmplifyApp", "Initialized Amplify"); + } catch (AmplifyException error) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error); + } + } +} +``` + + + + +```kotlin +Amplify.addPlugin(AWSApiPlugin()) +``` + +Your class will look like this: + +```kotlin +class MyAmplifyApp : Application() { + override fun onCreate() { + super.onCreate() + + try { + // Add these lines to add the AWSApiPlugin plugins + Amplify.addPlugin(AWSApiPlugin()) + Amplify.configure(applicationContext) + + Log.i("MyAmplifyApp", "Initialized Amplify") + } catch (error: AmplifyException) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error) + } + } +} +``` + + + + +```java +RxAmplify.addPlugin(new AWSApiPlugin()); +``` + +Your class will look like this: + +```java +public class MyAmplifyApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + try { + // Add these lines to add the AWSApiPlugin plugins + RxAmplify.addPlugin(new AWSApiPlugin()); + RxAmplify.configure(getApplicationContext()); + + Log.i("MyAmplifyApp", "Initialized Amplify"); + } catch (AmplifyException error) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error); + } + } +} +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/getting-started/40_codegen.mdx b/src/fragments/lib-legacy/graphqlapi/android/getting-started/40_codegen.mdx new file mode 100644 index 00000000000..13f1f0cd06c --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/getting-started/40_codegen.mdx @@ -0,0 +1,9 @@ +## Generate Todo Model class + +To generate the `Todo` model, change directories to your project folder and **execute the command**: + +```bash +amplify codegen models +``` + +The generated files will be under the `app/src/main/java/com/amplifyframework.datastore.generated.model` directory. It is strongly advised not to put any hand written code in `app/src/main/java/com/amplifyframework.datastore.generated` directory as it gets re-generated each time codegen is run. diff --git a/src/fragments/lib-legacy/graphqlapi/android/getting-started/50_createtodo.mdx b/src/fragments/lib-legacy/graphqlapi/android/getting-started/50_createtodo.mdx new file mode 100644 index 00000000000..fed51a0a363 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/getting-started/50_createtodo.mdx @@ -0,0 +1,66 @@ + + + +```java +Todo todo = Todo.builder() + .name("My first todo") + .description("todo description") + .build(); + +Amplify.API.mutate( + ModelMutation.create(todo), + response -> Log.i("MyAmplifyApp", "Added Todo with id: " + response.getData().getId()), + error -> Log.e("MyAmplifyApp", "Create failed", error) +); +``` + + + + +```kotlin +val todo = Todo.builder() + .name("My first todo") + .description("todo description") + .build() + +Amplify.API.mutate(ModelMutation.create(todo), + { Log.i("MyAmplifyApp", "Added Todo with id: ${it.data.id}") }, + { Log.e("MyAmplifyApp", "Create failed", it) } +) +``` + + + + +```kotlin +val todo = Todo.builder() + .name("My first todo") + .description("todo description") + .build() +try { + val response = Amplify.API.mutate(ModelMutation.create(todo)) + Log.i("MyAmplifyApp", "Added Todo with id: ${response.data.id}") +} catch (error: ApiException) { + Log.e("MyAmplifyApp", "Create failed", error) +} +``` + + + + +```java +Todo todo = Todo.builder() + .name("My first todo") + .description("todo description") + .build(); + +RxAmplify.API.mutate(ModelMutation.create(todo)) + .subscribe( + response -> Log.i("MyAmplifyApp", "Added Todo with id: " + response.getData().getId()), + error -> Log.e("MyAmplifyApp", "Create failed", error) + ); +``` + + + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/mutate-data.mdx b/src/fragments/lib-legacy/graphqlapi/android/mutate-data.mdx new file mode 100644 index 00000000000..1309c6f95f7 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/mutate-data.mdx @@ -0,0 +1,66 @@ +## Run a mutation + +Now that the client is set up, you can run a GraphQL mutation with `Amplify.API.mutate` to create, update, and delete your data. + + + + +```java +Todo todo = Todo.builder() + .name("My todo") + .build(); + +Amplify.API.mutate(ModelMutation.create(todo), + response -> Log.i("MyAmplifyApp", "Todo with id: " + response.getData().getId()), + error -> Log.e("MyAmplifyApp", "Create failed", error) +); +``` + + + + +```kotlin +val todo = Todo.builder() + .name("My todo") + .build() + +Amplify.API.mutate(ModelMutation.create(todo), + { Log.i("MyAmplifyApp", "Todo with id: ${it.data.id}") } + { Log.e("MyAmplifyApp", "Create failed", it) } +) +``` + + + + +```kotlin +val todo = Todo.builder() + .name("My todo") + .build() +try { + val response = Amplify.API.mutate(ModelMutation.create(todo)) + Log.i("MyAmplifyApp", "Todo with id: ${response.data.id}") +} catch (error: ApiException) { + Log.e("MyAmplifyApp", "Create failed", error) +} +``` + + + + +```java +Todo todo = Todo.builder() + .name("My todo") + .build(); + +RxAmplify.API.mutate(ModelMutation.create(todo)) + .subscribe( + response -> Log.i("MyAmplifyApp", "Todo with id: " + response.getData().getId()), + error -> Log.e("MyAmplifyApp", "Create failed", error) + ); +``` + + + + +To update data, use `ModelMutation.update(todo)` instead. To delete data, use `ModelMutation.delete(todo)`. diff --git a/src/fragments/lib-legacy/graphqlapi/android/query-data.mdx b/src/fragments/lib-legacy/graphqlapi/android/query-data.mdx new file mode 100644 index 00000000000..cdad6e5c1f8 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/query-data.mdx @@ -0,0 +1,234 @@ +## Query item + +Now that you were able to make a mutation, take the `Id` that was printed out and use it in your query to retrieve data. + + + + +```java +private void getTodo(String id) { + Amplify.API.query( + ModelQuery.get(Todo.class, id), + response -> Log.i("MyAmplifyApp", ((Todo) response.getData()).getName()), + error -> Log.e("MyAmplifyApp", error.toString(), error) + ); +} +``` + + + + +```kotlin +private fun getTodo(id: String) { + Amplify.API.query(ModelQuery.get(Todo::class.java, id), + { Log.i("MyAmplifyApp", "Query results = ${(it.data as Todo).name}") }, + { Log.e("MyAmplifyApp", "Query failed", it) } + ); +} +``` + + + + +```kotlin +suspend fun getTodo(id: String) { + try { + val response = Amplify.API.query(ModelQuery.get(Todo::class.java, id)) + Log.i("MyAmplifyApp", response.data.name) + } catch (error: ApiException) { + Log.e("MyAmplifyApp", "Query failed", error) + } +} +``` + + + + +```java +private void getTodo(String id) { + RxAmplify.API.query(ModelQuery.get(Todo.class, id)) + .subscribe( + response -> Log.i("MyAmplifyApp", ((Todo) response.getData()).getName()), + error -> Log.e("MyAmplifyApp", error.toString(), error) + ); +} +``` + + + + +## List items + +You can get the list of items that match a condition that you specify in `Amplify.API.query`: + + + + +```java +Amplify.API.query( + ModelQuery.list(Todo.class, Todo.NAME.contains("first")), + response -> { + for (Todo todo : response.getData()) { + Log.i("MyAmplifyApp", todo.getName()); + } + }, + error -> Log.e("MyAmplifyApp", "Query failure", error) +); +``` + + + + +```kotlin +Amplify.API.query( + ModelQuery.list(Todo::class.java, Todo.NAME.contains("first")), + { response -> + response.data.forEach { todo -> + Log.i("MyAmplifyApp", todo.name) + } + }, + { Log.e("MyAmplifyApp", "Query failure", it) } +) +``` + + + + +```kotlin +try { + Amplify.API + .query(ModelQuery.list(Todo::class.java, Todo.NAME.contains("first"))) + .response.data + .items.forEach { todo -> Log.i("MyAmplifyApp", todo.name) } +} catch (error: ApiException) { + Log.e("MyAmplifyApp", "Query failure", error) +} +``` + + + + +```java +RxAmplify.API.query(ModelQuery.list(Todo.class, Todo.NAME.contains("first")) + .subscribe( + response -> { + for (Todo todo : response.getData()) { + Log.i("MyAmplifyApp", todo.getName()); + } + }, + error -> Log.e("MyAmplifyApp", "Query failure", error) + ); +``` + + + + +> **Note**: This approach will only return up to the first 1,000 items. To change this limit or make requests for additional results beyond this limit, use *pagination* as discussed below. + +## List subsequent pages of items + +A list query only returns the first 1,000 items by default, so for large data sets, you'll need to paginate through the results. After receiving a page of results, you can obtain a `GraphQLRequest` for requesting the next page, if there are more results available. The page size is configurable as well, as in the example below. + + + + +```java +public void queryFirstPage() { + query(ModelQuery.list(Todo.class, ModelPagination.limit(1_000))); +} + +private static void query(GraphQLRequest> request) { + Amplify.API.query( + request, + response -> { + if (response.hasData()) { + for (Todo todo : response.getData()) { + Log.d("MyAmplifyApp", todo.getName()); + } + if (response.getData().hasNextResult()) { + query(response.getData().getRequestForNextResult()); + } + } + }, + failure -> Log.e("MyAmplifyApp", "Query failed.", failure) + ); +} +``` + + + + +```kotlin +fun queryFirstPage() { + query(ModelQuery.list(Todo::class.java, ModelPagination.limit(1_000))) +} + +fun query(request: GraphQLRequest>) { + Amplify.API.query(request, + { response -> + if (response.hasData()) { + response.data.items.forEach { todo -> + Log.d("MyAmplifyApp", todo.name) + } + if (response.data.hasNextResult()) { + query(response.data.requestForNextResult) + } + } + }, + { Log.e("MyAmplifyApp", "Query failed", it) } + ) +} +``` + + + + +```kotlin +suspend fun queryFirstPage() { + query(ModelQuery.list(Todo::class.java, + ModelPagination.firstPage().withLimit(1_000))) +} + +suspend fun query(request: GraphQLRequest>) { + try { + val response = Amplify.API.query(request) + response.data.items.forEach { todo -> + Log.d("MyAmplifyApp", todo.name) + } + if (response.data.hasNextResult()) { + query(response.data.requestForNextResult) + } + } catch (error: ApiException) { + Log.e("MyAmplifyApp", "Query failed.", error) + } +} +``` + + + + + +```java +BehaviorSubject>> subject = + BehaviorSubject.createDefault(ModelQuery.list(Todo.class, ModelPagination.limit(1_000))); +subject.concatMap(request -> RxAmplify.API.query(request).toObservable()) + .doOnNext(response -> { + if (response.hasErrors()) { + subject.onError(new Exception(String.format("Query failed: %s", response.getErrors()))); + } else if (!response.hasData()) { + subject.onError(new Exception("Empty response from AppSync.")); + } else if(response.getData().hasNextResult()) { + subject.onNext(response.getData().getRequestForNextResult()); + } else { + subject.onComplete(); + } + }) + .concatMapIterable(GraphQLResponse::getData) + .subscribe( + todo -> Log.d(TAG, "Todo: " + todo), + error -> Log.e(TAG, "Error: " + error) + ); +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/android/subscribe-data.mdx b/src/fragments/lib-legacy/graphqlapi/android/subscribe-data.mdx new file mode 100644 index 00000000000..bafcf83484c --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/android/subscribe-data.mdx @@ -0,0 +1,77 @@ +Subscribe to mutations for creating real-time clients: + + + + +```java +ApiOperation subscription = Amplify.API.subscribe( + ModelSubscription.onCreate(Todo.class), + onEstablished -> Log.i("ApiQuickStart", "Subscription established"), + onCreated -> Log.i("ApiQuickStart", "Todo create subscription received: " + ((Todo) onCreated.getData()).getName()), + onFailure -> Log.e("ApiQuickStart", "Subscription failed", onFailure), + () -> Log.i("ApiQuickStart", "Subscription completed") +); + +// Cancel the subscription listener when you're finished with it +subscription.cancel(); +``` + + + + +```kotlin +val subscription = Amplify.API.subscribe( + ModelSubscription.onCreate(Todo::class.java), + { Log.i("ApiQuickStart", "Subscription established") }, + { Log.i("ApiQuickStart", "Todo create subscription received: ${(it.data as Todo).name}") }, + { Log.e("ApiQuickStart", "Subscription failed", it) }, + { Log.i("ApiQuickStart", "Subscription completed") } +) + +// Cancel the subscription listener when you're finished with it +subscription.cancel(); +``` + + + + +```kotlin +val job = activityScope.launch { + try { + Amplify.API.subscribe(ModelSubscription.onCreate(Todo::class.java)) + .catch { Log.e("ApiQuickStart", "Error on subscription", it) } + .collect { Log.i("ApiQuickStart", "Todo created! ${it.data.name}") } + } catch (notEstablished: ApiException) { + Log.e("ApiQuickStart", "Subscription not established", it) + } +} + +// When done with subscription +job.cancel() +``` + + + + +```java +RxSubscriptionOperation> subscription = + RxAmplify.API.subscribe(request); + +subscription + .observeConnectionState() + .subscribe(connectionStateEvent -> Log.i("ApiQuickStart", String.valueOf(connectionStateEvent))); + +subscription + .observeSubscriptionData() + .subscribe( + data -> Log.i("ApiQuickStart", data), + exception -> Log.e("ApiQuickStart", "Subscription failed.", exception), + () -> Log.i("ApiQuickStart", "Subscription completed.") + ); + +// Cancel the subscription listener when you're finished with it +subscription.cancel(); +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/existing-resources.mdx b/src/fragments/lib-legacy/graphqlapi/existing-resources.mdx new file mode 100644 index 00000000000..a3bf1a801eb --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/existing-resources.mdx @@ -0,0 +1,26 @@ +Existing AWS AppSync resources can be used with the Amplify Libraries by referencing your AWS AppSync **endpoint** and configuring authorization in your `amplifyconfiguration.json` file. + +```json +{ + "api": { + "plugins": { + "awsAPIPlugin": { + "[API NAME]": { + "endpointType": "GraphQL", + "endpoint": "[APPSYNC ENDPOINT]", + "region": "[REGION]", + "authorizationType": "[AUTHORIZATION TYPE]", + ... + } + } + } + } +} +``` + +- **API NAME**: Friendly name for the API (e.g., *api*) + - **endpoint**: The HTTPS endpoint of the AWS AppSync API (e.g. *https://aaaaaaaaaaaaaaaaaaaaaaaaaa.appsync-api.us-east-1.amazonaws.com/graphql*). [Custom domain names](https://docs.aws.amazon.com/appsync/latest/devguide/custom-domain-name.html) can also be supplied here (e.g. *https://api.yourdomain.com/graphql*). Custom domain names can have any format, but must end with `/graphql` (see https://graphql.org/learn/serving-over-http/#uris-routes). + - **region**: AWS Region where the resources are provisioned (e.g. *us-east-1*) + - **authorizationType**: Authorization mode for accessing the API. This can be one of: `AMAZON_COGNITO_USER_POOLS`, `AWS_IAM`, `OPENID_CONNECT`, or `API_KEY`. Each mode requires additional configuration parameters. See [Configure authorization modes](/lib/graphqlapi/authz) for details. + +Note that before you can add an AWS resource to your application, the application must have the Amplify libraries installed. If you need to perform this step, see [Install Amplify Libraries](/lib/project-setup/create-application#n2-install-amplify-libraries). diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/10_example.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/10_example.mdx new file mode 100644 index 00000000000..2b5cc5cefc8 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/10_example.mdx @@ -0,0 +1,8 @@ +```dart +Future createAndMutateTodo() async { + final todo = Todo(name: 'my first todo', description: 'todo description'); + final request = ModelMutations.create(todo); + final response = await Amplify.API.mutate(request: request).response; + print('Response: $response'); +} +``` diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/20_custom.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/20_custom.mdx new file mode 100644 index 00000000000..bbdf664657e --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/20_custom.mdx @@ -0,0 +1,24 @@ +```dart +const getTodo = 'getTodo'; +String graphQLDocument = '''query GetTodo(\$id: ID!) { + $getTodo(id: \$id) { + id + name + } +}'''; +final getTodoRequest = GraphQLRequest( + document: graphQLDocument, + modelType: Todo.classType, + variables: {'id': someTodoId}, + decodePath: getTodo, +); +``` +The `decodePath` specifies which part of the response to deserialize to the `modelType`. You'll need to specify the operation name (as `decodePath`) and `modelType` to deserialize the object at "data.getTodo" successfully into a `Todo` model. + +Then, query for the Todo by a todo id: +```dart +Future queryTodo(GraphQLRequest getTodoRequest) async { + final response = await Amplify.API.query(request: getTodoRequest).response; + print('Response: $response'); +} +``` diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/30_nested.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/30_nested.mdx new file mode 100644 index 00000000000..528bcd8542e --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/30_nested.mdx @@ -0,0 +1,32 @@ +```dart +const getPost = 'getPost'; +String graphQLDocument = '''query GetPost(\$id: ID!) { + $getPost(id: \$id) { + id + title + rating + status + comments { + items { + id + postID + content + } + } + } +}'''; +final getPostRequest = GraphQLRequest( + document: graphQLDocument, + modelType: Post.classType, + variables: {'id': somePostId}, + decodePath: getPost, +); +``` + +Then, query for the `Post` with nested comments included in decoded response: +```dart +Future queryPostWithNestedComments(GraphQLRequest getPostRequest) async { + final response = await Amplify.API.query(request: getTodoRequest).response; + print('Response $response'); +} +``` diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/40_multiple.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/40_multiple.mdx new file mode 100644 index 00000000000..5240683d683 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/40_multiple.mdx @@ -0,0 +1,44 @@ +```dart +const getTodo = 'getTodo'; +const getPost = 'getPost'; +String graphQLDocument = ''' + query GetPostAndTodo(\$todoId: ID!, \$postId: ID!) { + $getTodo(id: \$todoId) { + id + name + } + $getPost(id: \$postId) { + id + title + rating + } + } + +'''; +final multiOperationRequest = GraphQLRequest( + document: graphQLDocument, + variables: { + 'todoId': someTodoId, + 'postId': somePostId + }, +); +``` +Notice here that `modelType` and `decodePath` are omitted. When these decoding variables are omitted, the plugin simply returns the result as a raw `String` from the response. + +Once you have the response data in a `String`, you can parse it using `json.decode()` and pass the resulting `Map` to the model's `fromJson()` method to create an instance of the model. + +```dart +// Do not forget to add this to imports for json.decode +import 'dart:convert'; + +... + +Future queryMultiOperationRequest(GraphQLRequest operation) async { + final response = await Amplify.API.query(request: multiOperationRequest).response; + if (response.data != null) { + final jsonData = (json.decode(response.data) as Map).cast(); + final post = Post.fromJson((jsonData[getPost] as Map).cast); + final todo = Todo.fromJson((jsonData[getTodo] as Map).cast); + } +} +``` diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx new file mode 100644 index 00000000000..f9d9c3e10c2 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx @@ -0,0 +1,5 @@ + + +Custom interceptors are currently not supported in Flutter. Please follow the open [Github Issue](https://github.com/aws-amplify/amplify-flutter/issues/798) for support on this feature. + + diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/authz.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/authz.mdx new file mode 100644 index 00000000000..22f4d2865b5 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/authz.mdx @@ -0,0 +1,139 @@ +AppSync supports authorization by means of API Keys, Amazon IAM credentials, Amazon Cognito User Pools, and 3rd party OIDC providers. The type of authorization being used is determined from the `amplifyconfiguration.dart` file that gets read when you call `Amplify.configure()`. + +#### API key + +API Key is the simplest way to setup and prototype your application with AWS AppSync. While simple, the mechanism is easy to abuse since anyone who discovers your API Key can make requests to your public service. Production applications should authorized requests via Cognito user pool or AWS IAM. Your API Key will expire according to the expiry time that you set when provisioning AWS AppSync. You will need to extend its lifespan, or creating a new API key, if needed. + +#### Amazon Cognito User Pools + +Amazon Cognito's user pool is most commonly used with AWS AppSync when adding authorization check on your API calls. If your application needs to interact with other AWS services besides AWS AppSync, such as Amazon S3, you will need to use AWS IAM credentials with Amazon Cognito's identity pools. Amplify CLI can automatically configure this for you when running `amplify add auth` and will also automatically use the authenticated user from user pools to federate with the identity pools to provide the AWS IAM credentials in the application. [See this for more information about the differences](https://aws.amazon.com/premiumsupport/knowledge-center/cognito-user-pools-identity-pools/). This allows you to have both user pool credentials for AWS AppSync and AWS IAM credentials for other AWS resources. You can learn more about Amplify Auth outlined in the [Accessing credentials section](/lib/auth/access_credentials). For manual configuration, add the following snippet to your `amplifyconfiguration.dart` file, under the `awsCognitoAuthPlugin`: + +```json +{ + ... + "awsCognitoAuthPlugin": { + "CognitoUserPool": { + "Default": { + "PoolId": "[POOL-ID]", + "AppClientId": "[APP-CLIENT-ID]", + "Region": "[REGION]" + } + } + } +} +``` +and under the `awsAPIPlugin` +```json +{ + ... + "awsAPIPlugin": { + " + +#### IAM + +Amazon Cognito identity pools allows you to use credentials from AWS IAM in a mobile application. The Amplify CLI can automatically configure this for you when running `amplify add auth`. For manual configuration, add the following snippet to your `amplifyconfiguration.dart` file: + +```json +{ + ... + "awsCognitoAuthPlugin": { + "CredentialsProvider": { + "CognitoIdentity": { + "Default": { + "PoolId": "[COGNITO-IDENTITY-POOLID]", + "Region": "[REGION]" + } + } + } + } +} +``` +and under the `awsAPIPlugin` +```json +{ + ... + "awsAPIPlugin": { + " + +#### Configure multiple authorization modes + +This section talks about the capability of AWS AppSync to configure multiple authorization modes for a single AWS AppSync endpoint and region. Follow the [AWS AppSync Multi-Auth](https://docs.aws.amazon.com/appsync/latest/devguide/security.html#using-additional-authorization-modes) to configure multiple authorization modes for your AWS AppSync endpoint. + +You can now configure a single GraphQL API to deliver private and public data. Private data requires authenticated access using authorization mechanisms such as IAM, Cognito User Pools, and OIDC. Public data does not require authenticated access and is delivered through authorization mechanisms such as API Keys. You can also configure a single GraphQL API to deliver private data using more than one authorization type. For example, you can configure your GraphQL API to authorize some schema fields using OIDC, while other schema fields through Cognito User Pools and/or IAM. + +As discussed in the above linked documentation, certain fields may be protected by different authorization types. This can lead the same query, mutation, or subscription to have different responses based on the authorization sent with the request; Therefore, it is recommended to use the different `friendly_name_` as the `apiName` parameter in the `Amplify.API` call to reference each authorization type. + +The following snippets highlight the new values in the `amplifyconfiguration.dart` and the client code configurations. + +The `friendly_name` illustrated here is created from Amplify CLI prompt. There are 4 clients in this configuration that connect to the same API except that they use different `AuthMode`. + +```json +{ + "UserAgent": "aws-amplify-cli/2.0", + "Version": "1.0", + "api": { + "plugins": { + "awsAPIPlugin": { + "[FRIENDLY-NAME-API-WITH-API-KEY]": { + "endpointType": "GraphQL", + "endpoint": "[GRAPHQL-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "API_KEY", + "apiKey": "[API_KEY]" + }, + "[FRIENDLY-NAME-API-WITH-IAM": { + "endpointType": "GraphQL", + "endpoint": "[GRAPHQL-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "AWS_IAM", + }, + "[FRIENDLY-NAME-API-WITH-USER-POOLS]": { + "endpointType": "GraphQL", + "endpoint": "https://xyz.appsync-api.us-west-2.amazonaws.com/graphql", + "region": "[REGION]", + "authorizationType": "AMAZON_COGNITO_USER_POOLS", + }, + "[FRIENDLY-NAME-API-WITH-OPENID-CONNECT]": { + "endpointType": "GraphQL", + "endpoint": "https://xyz.appsync-api.us-west-2.amazonaws.com/graphql", + "region": "[REGION]", + "authorizationType": "OPENID_CONNECT", + } + } + } + } +} +``` + +The `GRAPHQL-ENDPOINT` from AWS AppSync will look similar to `https://xyz.appsync-api.us-west-2.amazonaws.com/graphql`. + +import flutter2 from "/src/fragments/lib/graphqlapi/flutter/authz/30_multi.mdx"; + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/authz/10_userpool.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/authz/10_userpool.mdx new file mode 100644 index 00000000000..b114f0978c9 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/authz/10_userpool.mdx @@ -0,0 +1,21 @@ +In case you have not added the Cognito libraries to your application, be sure to add them: + +```yaml + environment: + sdk: ">=2.15.0 <3.0.0" + + dependencies: + flutter: + sdk: flutter + + amplify_flutter: ^0.6.0 + amplify_api: ^0.6.0 + # Be sure that this is added + amplify_auth_cognito: ^0.6.0 +``` + +Afterwards add the following code to your app before you configure Amplify: + +```dart +await Amplify.addPlugins([AmplifyAuthCognito(), AmplifyAPI()]); +``` diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/authz/20_oidc.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/authz/20_oidc.mdx new file mode 100644 index 00000000000..3e8bd488f27 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/authz/20_oidc.mdx @@ -0,0 +1,16 @@ +To start, create a provider class inheriting from `OIDCAuthProvider`. + +```dart +import 'package:amplify_api/amplify_api.dart'; + +class CustomOIDCProvider extends OIDCAuthProvider { + const CustomOIDCProvider(); + + @override + Future getLatestAuthToken() async => '[OPEN-ID-CONNECT-TOKEN]'; +} +``` + +import warning from "/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx"; + + diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/authz/21_oidc.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/authz/21_oidc.mdx new file mode 100644 index 00000000000..03dc4ca4b17 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/authz/21_oidc.mdx @@ -0,0 +1,20 @@ +Change the custom provider to retrieve the current session: + +```dart +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; + +class CustomOIDCProvider extends OIDCAuthProvider { + const CustomOIDCProvider(); + + @override + Future getLatestAuthToken() async { + final session = await Amplify.Auth.fetchAuthSession() as CognitoAuthSession; + return session.userPoolTokens?.idToken; + } +} +``` + +import warning from "/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx"; + + diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/authz/22_lambda.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/authz/22_lambda.mdx new file mode 100644 index 00000000000..cedc5024efe --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/authz/22_lambda.mdx @@ -0,0 +1,14 @@ +To start, create a provider class inheriting from `FunctionAuthProvider`. + +```dart +class CustomFunctionProvider extends FunctionAuthProvider { + const CustomFunctionProvider(); + + @override + Future getLatestAuthToken() async => '[AWS-LAMBDA-AUTH-TOKEN]'; +} +``` + +import warning from "/src/fragments/lib/graphqlapi/flutter/authz/2X_add_plugin.mdx"; + + diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/authz/2X_add_plugin.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/authz/2X_add_plugin.mdx new file mode 100644 index 00000000000..7c6f578e3f1 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/authz/2X_add_plugin.mdx @@ -0,0 +1,16 @@ +Then, include it, along with any other auth providers, in the call to `addPlugin`. + +```dart +await Amplify.addPlugin(AmplifyAPI( + authProviders: const [ + CustomOIDCProvider(), + CustomFunctionProvider(), + ], +)); +``` + + + +**Note**: When using custom auth providers, `getLatestAuthToken` must be called before every API call, so it's important to minimize the amount of work this method performs. Consider caching your token in-memory so that it's available synchronously to the plugin, and only refresh it when necessary. + + diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/authz/30_multi.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/authz/30_multi.mdx new file mode 100644 index 00000000000..9e22d8128ed --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/authz/30_multi.mdx @@ -0,0 +1,15 @@ +When you have configured multiple APIs, you can specify the name of the API as a parameter as the target for an operation: + +```dart +Future main() async { + final operation = Amplify.API.mutate( + request: GraphQLRequest( + document: graphQLDocumentString, + apiName: '[FRIENDLY-NAME-API-WITH-API-KEY]', + ), + ); + final response = await operation.response; + final data = response.data; + print('data: $data'); +} +``` diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/10_preReq.mdx new file mode 100644 index 00000000000..7829d1f7a1a --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/10_preReq.mdx @@ -0,0 +1,5 @@ +* [Install and configure Amplify CLI](https://docs.amplify.aws/cli/start/install) +* A Flutter application targeting Flutter SDK >= 2.10.0 (stable version) with Amplify libraries integrated + * An iOS configuration targeting at least iOS 11.0 + * An Android configuration targeting at least Android API level 21 (Android 5.0) or above + * For a full example please follow the [project setup walkthrough](/lib/project-setup/create-application) diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/12_amplifyConfig.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/12_amplifyConfig.mdx new file mode 100644 index 00000000000..9c43e6c4f31 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/12_amplifyConfig.mdx @@ -0,0 +1 @@ +Upon completion, `amplifyconfiguration.dart` will be updated to reference provisioned backend resources. Note that this file should already be a part of your project if you followed the [Project setup walkthrough](/lib/project-setup/create-application). \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/20_installLib.mdx new file mode 100644 index 00000000000..4a4f0fef357 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/20_installLib.mdx @@ -0,0 +1,12 @@ +Add the following dependencies to your `pubspec.yaml` file and install dependencies when asked: + +```yaml +environment: + sdk: ">=2.15.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + amplify_flutter: ^0.6.0 + amplify_api: ^0.6.0 +``` diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/30_initapi.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/30_initapi.mdx new file mode 100644 index 00000000000..4f0ece449de --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/30_initapi.mdx @@ -0,0 +1,37 @@ +To initialize the Amplify API category you call `Amplify.addPlugin()` method. To complete initialization call `Amplify.configure()`. + +Your code should look like this: + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_example_application/models/ModelProvider.dart'; + + +import 'amplifyconfiguration.dart'; + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void initState() { + super.initState(); + _configureAmplify(); + } + + Future _configureAmplify() async { + final api = AmplifyAPI(modelProvider: ModelProvider.instance); + await Amplify.addPlugin(api); + + try { + await Amplify.configure(amplifyconfig); + } on AmplifyAlreadyConfiguredException { + safePrint( + 'Tried to reconfigure Amplify; this can occur when your app restarts on Android.'); + } + } +} +``` diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/40_codegen.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/40_codegen.mdx new file mode 100644 index 00000000000..135ef9360e2 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/40_codegen.mdx @@ -0,0 +1,15 @@ +## Generate Todo Model class + +To generate the `Todo` model, change directories to your project folder and **execute the command**: + +```bash +amplify codegen models +``` + +The generated files will be under the `lib/models` directory by default. They get re-generated each time codegen is run. + + + +Codegen generates models using Dart null safety by default for a new Flutter project. It also provides a configurable feature flag to generate null safe models for existing Flutter projects. Check [here](https://docs.amplify.aws/lib/project-setup/null-safety/q/platform/flutter) for more details. + + diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/50_createtodo.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/50_createtodo.mdx new file mode 100644 index 00000000000..aba2ccf86ad --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/getting-started/50_createtodo.mdx @@ -0,0 +1,18 @@ +```dart +Future createTodo() async { + try { + final todo = Todo(name: 'my first todo', description: 'todo description'); + final request = ModelMutations.create(todo); + final response = await Amplify.API.mutate(request: request).response; + + final createdTodo = response.data; + if (createdTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + safePrint('Mutation result: ${createdTodo.name}'); + } on ApiException catch (e) { + safePrint('Mutation failed: $e'); + } +} +``` diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/mutate-data.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/mutate-data.mdx new file mode 100644 index 00000000000..24f4adb9d31 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/mutate-data.mdx @@ -0,0 +1,36 @@ +## Run a mutation + +Now that the client is set up, you can run a GraphQL mutation with `Amplify.API.mutate` to create, update, and delete your data. + +import createTodo from "/src/fragments/lib/graphqlapi/flutter/getting-started/50_createtodo.mdx"; + + + +To update the `Todo` with a new name: + +```dart +Future updateTodo(Todo originalTodo) async { + final todoWithNewName = originalTodo.copyWith(name: 'new name'); + + final request = ModelMutations.update(todoWithNewName); + final response = await Amplify.API.mutate(request: request).response; + print('Response: $response'); +} +``` + +To delete the `Todo`: + +```dart +Future deleteTodo(Todo todoToDelete) async { + final request = ModelMutations.delete(todoToDelete); + final response = await Amplify.API.mutate(request: request).response; + print('Response: $response'); +} + +// or delete by ID, ideal if you do not have the instance in memory, yet +Future deleteTodoById(Todo todoToDelete) async { + final request = ModelMutations.deleteById(Todo.classType, '8e0dd2fc-2f4a-4dc4-b47f-2052eda10775'); + final response = await Amplify.API.mutate(request: request).response; + print('Response: $response'); +} +``` diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/query-data.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/query-data.mdx new file mode 100644 index 00000000000..e139c256e3e --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/query-data.mdx @@ -0,0 +1,120 @@ +## Query item + +Now that you were able to make a mutation, take the `id` from the created `Todo` instance and use it to retrieve data. + +```dart +Future queryItem(Todo queriedTodo) async { + try { + final request = ModelQueries.get(Todo.classType, queriedTodo.id); + final response = await Amplify.API.query(request: request).response; + final todo = response.data; + if (todo == null) { + print('errors: ${response.errors}'); + } + return todo; + } on ApiException catch (e) { + print('Query failed: $e'); + return null; + } +} +``` + +## List items + +You can get the list of items in `Amplify.API.query`: + +```dart +Future> queryListItems() async { + try { + final request = ModelQueries.list(Todo.classType); + final response = await Amplify.API.query(request: request).response; + + final todos = response.data?.items; + if (todos == null) { + print('errors: ${response.errors}'); + return []; + } + return todos; + } on ApiException catch (e) { + print('Query failed: $e'); + } + return []; +} +``` + +### List subsequent pages of items + +For large data sets, you'll need to paginate through the results. After receiving the first page of results, you can check if there is a subsequent page and obtain the next page. + +```dart +const limit = 100; + +Future> queryPaginatedListItems() async { + final firstRequest = ModelQueries.list(Todo.classType, limit: limit); + final firstResult = await Amplify.API.query(request: firstRequest).response; + final firstPageData = firstResult.data; + + // Indicates there are > 100 todos and we can get the request for the next set. + if (firstPageData?.hasNextResult ?? false) { + final secondRequest = firstPageData!.requestForNextResult; + final secondResult = await Amplify.API.query(request: secondRequest!).response; + return secondResult.data?.items ?? []; + } else { + return firstPageData?.items ?? []; + } +} +``` + +## Query Predicates + +Models also support the use of query predicates for comparison. These are accessible from the Model's attributes, for example `Blog["attribute"]["operator"]`. + +Supported operators: +- `eq` - equal +- `ne` - not equal +- `gt` - greater than +- `ge` - greater than or equal +- `lt` - less than +- `le` - less than or equal +- `between` - Matches models where the given field begins with the provided value. +- `beginsWith` - Matches models where the given field is between the provided start and end values. +- `contains` - Matches models where the given field contains the provided value. + +### Basic Equality Operator + +Query for equality on a model's attribute. + +```dart +const blogTitle = 'Test Blog 1'; +final queryPredicate = Blog.NAME.eq(blogTitle); + +final request = ModelQueries.list(Blog.classType, where: queryPredicate); +final response = await Amplify.API.query(request: request).response; +final blogFromResponse = response.data?.items.first; +``` + +### Fetch by Parent ID + +Get all Posts by parent ID + +```dart +final blogId = blog.id; + +final request = + ModelQueries.list(Post.classType, where: Post.BLOG.eq(blogId)); +final response = await Amplify.API.query(request: request).response; +final data = response.data?.items ?? []; +``` + +### Less than + +Return Posts with a rating less than 5. + +```dart +const rating = 5; + +final request = ModelQueries.list(Post.classType, where: Post.RATING.lt(rating)); +final response = await Amplify.API.query(request: request).response; + +final data = response.data?.items ?? []; +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/flutter/subscribe-data.mdx b/src/fragments/lib-legacy/graphqlapi/flutter/subscribe-data.mdx new file mode 100644 index 00000000000..b920d68e3a2 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/flutter/subscribe-data.mdx @@ -0,0 +1,61 @@ +Subscribe to mutations for creating real-time clients. + +## Setup subscription with callbacks + +When creating subscriptions, a [`Stream`](https://api.dart.dev/stable/dart-async/Stream-class.html) object will be returned to you. This `Stream` will continue producing events until either the subscription encounters an error or you cancel the subscription. In the case of need for limiting the amount of data that is omitted, you can take advantage of the Stream's helper functions such as `take`. The cancellation occurs when the defined amount of event has occurred: + +```dart +Stream> subscribe() { + final subscriptionRequest = ModelSubscriptions.onCreate(Todo.classType); + final Stream> operation = Amplify.API + .subscribe( + subscriptionRequest, + onEstablished: () => print('Subscription established'), + ) + // Listens to only 5 elements + .take(5) + .handleError( + (error) { + print('Error in subscription stream: $error'); + }, + ); + return operation; +} +``` + +Alternatively, you can call [`Stream.listen`](https://api.dart.dev/stable/dart-async/Stream/listen.html) to create a [`StreamSubscription`](https://api.dart.dev/stable/dart-async/StreamSubscription-class.html) object which can be programmatically canceled. + +```dart +// Be sure to import this +import 'dart:async'; + +... + +StreamSubscription>? subscription; + +void subscribe() { + final subscriptionRequest = ModelSubscriptions.onCreate(Todo.classType); + final Stream> operation = Amplify.API.subscribe( + subscriptionRequest, + onEstablished: () => print('Subscription established'), + ); + subscription = operation.listen( + (event) { + print('Subscription event data received: ${event.data}'); + }, + onError: (Object e) => print('Error in subscription stream: $e'), + ); +} + +void unsubscribe() { + subscription?.cancel(); +} +``` + +Note that in addition to an `onCreate` subscription, you can also call `.onUpdate()` or `.onDelete()`. + +```dart +final onUpdateSubscriptionRequest = ModelSubscriptions.onUpdate(Todo.classType); +// or +final onDeleteSubscriptionRequest = ModelSubscriptions.onDelete(Todo.classType); +``` diff --git a/src/fragments/lib-legacy/graphqlapi/graphql-from-node.mdx b/src/fragments/lib-legacy/graphqlapi/graphql-from-node.mdx new file mode 100644 index 00000000000..966270a473b --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/graphql-from-node.mdx @@ -0,0 +1,314 @@ +We can call an AppSync GraphQL API from a Node.js app or a Lambda function. Take a basic `Todo` app as an example: + +```graphql +type Todo @model @auth(rules: [{ allow: public }]) { + name: String + description: String +} +``` + +This API will have operations available for `Query`, `Mutation`, and `Subscription`. Let's take a look at how to perform both a **query** as well as a **mutation** from a Lambda function using Node.js. + +First, create a Lambda function with `amplify add function` and make sure to give access to your GraphQL API when prompted by the CLI to grant access to other resources in the project. + +```console +amplify add function +? Select which capability you want to add: Lambda function (serverless function) +? Provide an AWS Lambda function name: myfunction +? Choose the runtime that you want to use: NodeJS +? Choose the function template that you want to use: Hello World + +Available advanced settings: +- Resource access permissions +- Scheduled recurring invocation +- Lambda layers configuration +- Environment variables configuration +- Secret values configuration + +? Do you want to configure advanced settings? Yes +? Do you want to access other resources in this project from your Lambda function? Yes +? Select the categories you want this function to have access to. api +? Select the operations you want to permit on Query, Mutation, Subscription + +You can access the following resource attributes as environment variables from your Lambda function + API__GRAPHQLAPIENDPOINTOUTPUT + API__GRAPHQLAPIIDOUTPUT + API__GRAPHQLAPIKEYOUTPUT + ENV + REGION +``` + +The examples on this page use [`node-fetch`](https://www.npmjs.com/package/node-fetch) to make a HTTP request to our GraphQL API. When the Node.js v18 runtime is released for Lambda this dependency can be removed in favor of [native `fetch`](https://nodejs.org/dist/latest-v18.x/docs/api/globals.html#fetch) To get started, add the `node-fetch` module as a dependency: + +```diff +{ + "name": "myfunction", ++ "type": "module", + "version": "2.0.0", + "description": "Lambda function generated by Amplify", + "main": "index.js", + "license": "Apache-2.0", ++ "dependencies": { ++ "node-fetch": "^3.2.3" ++ }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92" + } +} +``` + +## Query + +Using an API Key for authenticating our requests, we can query the GraphQL API to get a list of all `Todo`s. + + +```js +import { default as fetch, Request } from 'node-fetch'; + +const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; +const GRAPHQL_API_KEY = process.env.API__GRAPHQLAPIKEYOUTPUT; + +const query = /* GraphQL */ ` + query LIST_TODOS { + listTodos { + items { + id + name + description + } + } + } +`; + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +export const handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + + /** @type {import('node-fetch').RequestInit} */ + const options = { + method: 'POST', + headers: { + 'x-api-key': GRAPHQL_API_KEY + }, + body: JSON.stringify({ query, variables }) + }; + + const request = new Request(GRAPHQL_ENDPOINT, options); + + let statusCode = 200; + let body; + let response; + + try { + response = await fetch(request); + body = await response.json(); + if (body.errors) statusCode = 400; + } catch (error) { + statusCode = 400; + body = { + errors: [ + { + status: response.status, + message: error.message, + stack: error.stack + } + ] + }; + } + + return { + statusCode, + body: JSON.stringify(body) + }; +}; +``` + +## Mutation + +In this example we create a mutation showing how to pass in variables as arguments to create a `Todo` record. + + +```js +import { default as fetch, Request } from 'node-fetch'; + +const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; +const GRAPHQL_API_KEY = process.env.API__GRAPHQLAPIKEYOUTPUT; + +const query = /* GraphQL */ ` + mutation CREATE_TODO($input: CreateTodoInput!) { + createTodo(input: $input) { + id + name + createdAt + } + } +`; + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +export const handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + + const variables = { + input: { + name: 'Hello, Todo!' + } + }; + + /** @type {import('node-fetch').RequestInit} */ + const options = { + method: 'POST', + headers: { + 'x-api-key': GRAPHQL_API_KEY + }, + body: JSON.stringify({ query, variables }) + }; + + const request = new Request(GRAPHQL_ENDPOINT, options); + + let statusCode = 200; + let body; + let response; + + try { + response = await fetch(request); + body = await response.json(); + if (body.errors) statusCode = 400; + } catch (error) { + statusCode = 400; + body = { + errors: [ + { + status: response.status, + message: error.message, + stack: error.stack + } + ] + }; + } + + return { + statusCode, + body: JSON.stringify(body) + }; +}; +``` + +## IAM Authorization + +Let's take a look at another example schema that uses `iam` authorization. + +```graphql +type Todo @model @auth(rules: [{ allow: private, provider: iam }]) { + name: String + description: String +} +``` + +The CLI will automatically configure the Lambda execution IAM role to call the GraphQL API. Before writing our Lambda function we will first need to install the appropriate AWS SDK v3 dependencies: + +```diff +{ + "name": "myfunction", ++ "type": "module", + "version": "2.0.0", + "description": "Lambda function generated by Amplify", + "main": "index.js", + "license": "Apache-2.0", ++ "dependencies": { ++ "@aws-crypto/sha256-js": "^2.0.1", ++ "@aws-sdk/credential-provider-node": "^3.76.0", ++ "@aws-sdk/protocol-http": "^3.58.0", ++ "@aws-sdk/signature-v4": "^3.58.0", ++ "node-fetch": "^3.2.3" ++ }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92" + } +} +``` + +Then, the following example will sign the request to call the GraphQL API using IAM authorization. + + + +```js +import crypto from '@aws-crypto/sha256-js'; +import { defaultProvider } from '@aws-sdk/credential-provider-node'; +import { SignatureV4 } from '@aws-sdk/signature-v4'; +import { HttpRequest } from '@aws-sdk/protocol-http'; +import { default as fetch, Request } from 'node-fetch'; + +const { Sha256 } = crypto; +const GRAPHQL_ENDPOINT = process.env.API__GRAPHQLAPIENDPOINTOUTPUT; +const AWS_REGION = process.env.AWS_REGION || 'us-east-1'; + +const query = /* GraphQL */ ` + query LIST_TODOS { + listTodos { + items { + id + name + description + } + } + } +`; + +/** + * @type {import('@types/aws-lambda').APIGatewayProxyHandler} + */ +export const handler = async (event) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + + const endpoint = new URL(GRAPHQL_ENDPOINT); + + const signer = new SignatureV4({ + credentials: defaultProvider(), + region: AWS_REGION, + service: 'appsync', + sha256: Sha256 + }); + + const requestToBeSigned = new HttpRequest({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + host: endpoint.host + }, + hostname: endpoint.host, + body: JSON.stringify({ query }), + path: endpoint.pathname + }); + + const signed = await signer.sign(requestToBeSigned); + const request = new Request(endpoint, signed); + + let statusCode = 200; + let body; + let response; + + try { + response = await fetch(request); + body = await response.json(); + if (body.errors) statusCode = 400; + } catch (error) { + statusCode = 500; + body = { + errors: [ + { + message: error.message + } + ] + }; + } + + return { + statusCode, + body: JSON.stringify(body) + }; +}; +``` diff --git a/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/10_example.mdx b/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/10_example.mdx new file mode 100644 index 00000000000..e0735e90828 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/10_example.mdx @@ -0,0 +1,4 @@ +```swift +let todo = Todo(name: "my first todo", description: "todo description") +Amplify.API.mutate(request: .create(todo)) +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/20_custom.mdx b/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/20_custom.mdx new file mode 100644 index 00000000000..12bd5c96fd4 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/20_custom.mdx @@ -0,0 +1,26 @@ +```swift +extension GraphQLRequest { + static func getWithoutDescription(byId id: String) -> GraphQLRequest { + let operationName = "getTodo" + let document = """ + query getTodo($id: ID!) { + \(operationName)(id: $id) { + id + name + } + } + """ + return GraphQLRequest(document: document, + variables: ["id": id], + responseType: Todo.self, + decodePath: operationName) + } +} +``` +The decode path specifies which part of the response to deserialize to the `responseType`. You'll need to specify the operation name to deserialize the object at "data.getTodo" successfully into a Todo model. + +Then, query for the Todo by a todo id +```swift +Amplify.API.query(request: .getWithoutDescription(byId: "[UNIQUE_ID]")) { + // handle result +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/30_nested.mdx b/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/30_nested.mdx new file mode 100644 index 00000000000..6e0377c7ad6 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/30_nested.mdx @@ -0,0 +1,31 @@ +```swift +extension GraphQLRequest { + static func getPostWithComments(byId id: String) -> GraphQLRequest { + let document = """ + query getPost($id: ID!) { + getPost(id: $id) { + id + title + rating + status + comments { + items { + id + postID + content + } + } + } + } + """ + return GraphQLRequest( + document: document, + variables: ["id": id], + responseType: Post.self, + decodePath: "getPost" + ) + } +} + +``` +Query with `Amplify.API.query(request: .getPostWithComments(byId: "[POST_ID]"))`. diff --git a/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/40_multiple.mdx b/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/40_multiple.mdx new file mode 100644 index 00000000000..6b0ed12afb1 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/40_multiple.mdx @@ -0,0 +1,70 @@ +```swift +extension GraphQLRequest { + static func get(byPostId postId: String, todoId: String) -> GraphQLRequest { + let document = """ + query get($postId: ID!, $todoId: ID!) { + getPost(id: $postId) { + id + title + rating + } + getTodo(id: $todoId) { + id + name + } + } + """ + return GraphQLRequest(document: document, + variables: ["postId": postId, + "todoId": todoId], + responseType: JSONValue.self) + } +} +``` +Notice here that `JSONValue` is used as the `responseType`. `JSONValue` is utility type that can be used to represent an arbitrary JSON response. + +Once you have the response data in a `JSONValue`, you can access each object in the JSON structure by encoding it back to Data and decoding it to the expected Model. + +```swift +Amplify.API.query(request: .get(byPostId: "[POST_ID]", todoId: "[TODO_ID]")) { result in + switch result { + case .success(let response): + switch response { + case .success(let data): + if let todoJSON = data.value(at: "getTodo"), + let todoData = try? JSONEncoder().encode(todoJSON), + let todo = try? JSONDecoder().decode(Todo.self, from: todoData) { + print(todo) + } + if let postJSON = data.value(at: "getPost"), + let postData = try? JSONEncoder().encode(postJSON), + let post = try? JSONDecoder().decode(Post.self, from: postData) { + print(post) + } + case .failure(let errorResponse): + print("Response contained errors: \(errorResponse)") + } + case .failure(let apiError): + print("Failed with error: \(apiError)") + } +} +``` + +If you have custom models or your Model has required fields that you have decided not to include in the response, you can create a `Codable` that conforms to the structure of the response data that you expect. From the previous example, the `Codable` would look like this + +```swift +struct PostAndTodoResponse: Codable { + public let getTodo: Todo + public let getPost: Post + struct Todo: Codable { + public let id: String + public var name: String + } + struct Post: Codable { + public let id: String + public var title: String + public var rating: Int + } +} +``` +Then use `PostAndTodoResponse` as the `responseType` of the `GraphQLRequest` instead of using `JSONValue`. \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/50_interceptor.mdx b/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/50_interceptor.mdx new file mode 100644 index 00000000000..6fe32cd78d5 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/advanced-workflows/50_interceptor.mdx @@ -0,0 +1,18 @@ +To include custom headers in your outgoing requests, add an `URLRequestInterceptor` to the `AWSAPIPlugin`. Also specify the name of one of the APIs configured in your **amplifyconfiguration.json** file. + +```swift +struct CustomInterceptor: URLRequestInterceptor { + func intercept(_ request: URLRequest) throws -> URLRequest { + let nsUrlRequest = (request as NSURLRequest) + guard let mutableRequest = nsUrlRequest.mutableCopy() as? NSMutableURLRequest else { + throw APIError.unknown("Could not get mutable request", "") + } + mutableRequest.setValue("headerValue", forHTTPHeaderField: "headerKey") + return mutableRequest as URLRequest + } +} +let apiPlugin = try AWSAPIPlugin() +try Amplify.addPlugin(apiPlugin) +try Amplify.configure() +try apiPlugin.add(interceptor: CustomInterceptor(), for: "yourApiName") +``` diff --git a/src/fragments/lib-legacy/graphqlapi/ios/authz/10_userpool.mdx b/src/fragments/lib-legacy/graphqlapi/ios/authz/10_userpool.mdx new file mode 100644 index 00000000000..2accd1995cf --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/authz/10_userpool.mdx @@ -0,0 +1,12 @@ +Add the following code to your app: + +```swift + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + try! Amplify.add(plugin: AWSCognitoAuthPlugin()) + try! Amplify.add(plugin: AWSAPIPlugin()) + try! Amplify.configure() + print("Amplify initialized") + + return true + } +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/authz/20_oidc.mdx b/src/fragments/lib-legacy/graphqlapi/ios/authz/20_oidc.mdx new file mode 100644 index 00000000000..a9cf4d106a9 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/authz/20_oidc.mdx @@ -0,0 +1,27 @@ + +Add the following code to your app: + +* Create a subclass of `APIAuthProviderFactory` +```swift +class MyAPIAuthProviderFactory: APIAuthProviderFactory { + let myAuthProvider = MyOIDCAuthProvider() + + override func oidcAuthProvider() -> AmplifyOIDCAuthProvider? { + return myAuthProvider + } +} +``` + +* Implement your class which conforms to `AmplifyOIDCAuthProvider`: +```swift +class MyOIDCAuthProvider : AmplifyOIDCAuthProvider { + func getLatestAuthToken() -> Result { + .... + } +} +``` +* Finally, register your instance of `APIAuthProviderFactory` prior to calling `Amplify.configure()`: +```swift +try Amplify.add(plugin: AWSAPIPlugin(apiAuthProviderFactory: MyAPIAuthProviderFactory())) +try Amplify.configure() +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/authz/21_oidc.mdx b/src/fragments/lib-legacy/graphqlapi/ios/authz/21_oidc.mdx new file mode 100644 index 00000000000..3bf7fb83338 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/authz/21_oidc.mdx @@ -0,0 +1,28 @@ +```swift +class MyOIDCAuthProvider : AmplifyOIDCAuthProvider { + func getLatestAuthToken() -> Result { + let semaphore = DispatchSemaphore(value: 0) + var result: Result = .failure(AuthError.unknown("Could not retrieve Cognito token")) + Amplify.Auth.fetchAuthSession { (event) in + defer { + semaphore.signal() + } + switch event { + case .success(let session): + if let cognitoTokenResult = (session as? AuthCognitoTokensProvider)?.getCognitoTokens() { + switch cognitoTokenResult { + case .success(let tokens): + result = .success(tokens.idToken) + case .failure(let error): + result = .failure(error) + } + } + case .failure(let error): + result = .failure(error) + } + } + semaphore.wait() + return result + } +} +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/authz/22_lambda.mdx b/src/fragments/lib-legacy/graphqlapi/ios/authz/22_lambda.mdx new file mode 100644 index 00000000000..f401f21f612 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/authz/22_lambda.mdx @@ -0,0 +1,27 @@ + +Add the following code to your app: + +* Create a subclass of `APIAuthProviderFactory` +```swift +class MyAPIAuthProviderFactory: APIAuthProviderFactory { + let myAuthProvider = MyFunctionAuthProvider() + + open func functionAuthProvider() -> AmplifyFunctionAuthProvider? { + return MyFunctionAuthProvider() + } +} +``` + +* Implement your class which conforms to `AmplifyFunctionAuthProvider`: +```swift +class MyFunctionAuthProvider : AmplifyFunctionAuthProvider { + func getLatestAuthToken() -> Result { + .... + } +} +``` +* Finally, register your instance of `APIAuthProviderFactory` prior to calling `Amplify.configure()`: +```swift +try Amplify.add(plugin: AWSAPIPlugin(apiAuthProviderFactory: MyAPIAuthProviderFactory())) +try Amplify.configure() +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/authz/30_multi.mdx b/src/fragments/lib-legacy/graphqlapi/ios/authz/30_multi.mdx new file mode 100644 index 00000000000..fb0a06897a5 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/authz/30_multi.mdx @@ -0,0 +1,6 @@ +When you have configured multiple APIs, you can specify the name of the API as a parameter as the target for an operation: + +```swift +let request = GraphQLRequest(apiName: "[FRIENDLY-NAME-API-WITH-API-KEY]", ...) +Amplify.API.mutate(request: request, listener: ...) +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/getting-started.mdx b/src/fragments/lib-legacy/graphqlapi/ios/getting-started.mdx new file mode 100644 index 00000000000..cb80db1b9e5 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/getting-started.mdx @@ -0,0 +1,173 @@ +You can integrate with Amplify framework using the following steps: + +1. Setup the API endpoint and authentication information in the client side configuration. +2. Generate Swift Model classes from the API schema. +3. Write app code to run queries, mutations and subscriptions. + +## Set up your backend + +**Prerequisites** +* An iOS project targeting at least iOS 11.0. +* Install and configure the Amplify CLI + +import all0 from "/src/fragments/cli-install-block.mdx"; + + + +```bash +amplify configure +``` + +**Steps** + +Go to your project directory and run the following commands to get a fully functioning AppSync backend with API category. + +Run `amplify init` command as shown: + +```bash +amplify init +``` + +```console +? Enter a name for the project AmplifAPI +? Enter a name for the environment dev +? Choose your default editor: Visual Studio Code +? Choose the type of app that you're building ios +? Do you want to use an AWS profile? Yes +? Please choose the profile you want to use default +``` + +Add API using the command `amplify add api`. Here is an example: + +```console +? Please select from one of the below mentioned services: `GraphQL` +? Provide API name: `apiName` +? Choose the default authorization type for the API: `API key` +? Enter a description for the API key: +? After how many days from now the API key should expire (1-365): `30` +? Do you want to configure advanced settings for the GraphQL API `No, I am done.` +? Do you have an annotated GraphQL schema? `No` +? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description) +? Do you want to edit the schema now? `Yes` +``` + +We'll be using this schema: +```graphql +type Todo @model { + id: ID! + name: String! + description: String +} +``` +Provision the backend with `amplify push` command. Here is an example: + +```console +? Are you sure you want to continue? `Yes` +``` +Answer `No` to `? Do you want to generate code for your newly created GraphQL API`. +Answering `Yes` will generate an `API.swift` file which is only necessary when directly using the AWSAppSync SDK. When you're using Amplify API or Amplify DataStore, you'll use the `amplify codegen models` command to generate Swift models. + +The example above creates a backend with the Todo schema. You can open the AWS Console for AppSync with +`amplify console api` to interact directly with the GraphQL service. When your backend is successfully updated, there should be two newly created files: `amplifyconfiguration.json` and `awsconfiguration.json` in your project folder. + +## Install Amplify libraries and tools + + + + +If this is a new project, add Amplify via the Swift Package Manager: + +- Open your project in Xcode and select **File > Swift Packages > Add Package Dependency**. +- Enter the Amplify iOS GitHub repo URL (`https://github.com/aws-amplify/amplify-ios`) into the search bar and click **Next**. +- Choose the first rule, **Version**, as it will use the latest compatible version of the dependency that can be detected from the `main` branch, then click **Next**. +- Choose the **Amplify** and **AWSAPIPlugin** libraries, then click **Finish**. + + + + + +If this is a new project, run `pod init` to create the `Podfile` to use CocoaPods to manage your dependencies. Add the following to the Podfile: + +```ruby +target :'YOUR-APP-NAME' do + use_frameworks! + pod 'AmplifyPlugins/AWSAPIPlugin' +end +``` + +Close out of the existing Xcode project if you have it open. + +Install the dependencies via CocoaPods +```bash +pod install --repo-update +``` + +Open the `.xcworkspace` file created by CocoaPods + +```bash +open .xcworkspace +``` + + + + + +Build your project and you should see the `amplify` folder, `amplifyxc.config`, `awsconfiguration.json`, and `amplifyconfiguration.json`. + +## Initialize Amplify + +Initialize Amplify and AWSAPIPlugin. + +Add the following imports to the top of your `AppDelegate.swift` file + + + + +```swift +import Amplify +import AWSAPIPlugin +``` + + + +```swift +import Amplify +import AmplifyPlugins +``` + + + + +Add the follow code to your AppDelegate's `application:didFinishLaunchingWithOptions` method +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + let apiPlugin = AWSAPIPlugin(modelRegistration: AmplifyModels()) + do { + try Amplify.add(plugin: apiPlugin) + try Amplify.configure() + print("Amplify initialized") + } catch { + print("Failed to configure Amplify \(error)") + } + return true +} +``` +## Add configuration files + +3. Click on the top level project on the left panel. +4. Click on your app under Targets in the left panel that contains Project and Targets. +5. Click on Build Phases +6. Expand the Copy Bundle Resources +7. Click on the + button, and select `awsconfiguration.json` and `amplifyconfiguration.json` to add. +8. Build and run (`Cmd+R`) the app and make sure Amplify is initialized. + +## Running code generator + +1. Run `amplify codegen models` using Amplify CLI. +2. A new `AmplifyModels` group will be added to your Xcode project containing the generated model files. + +Make sure it builds and runs (`Cmd+R`) successfully before moving onto the next section. + +## API Reference + +For the complete API documentation for API, visit our [API Reference](https://aws-amplify.github.io/amplify-ios/docs/Classes/AmplifyAPICategory.html) diff --git a/src/fragments/lib-legacy/graphqlapi/ios/getting-started/10_preReq.mdx b/src/fragments/lib-legacy/graphqlapi/ios/getting-started/10_preReq.mdx new file mode 100644 index 00000000000..370fc743236 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/getting-started/10_preReq.mdx @@ -0,0 +1,3 @@ +* An iOS application targeting at least iOS 11.0 with Amplify libraries integrated + * For a full example of please follow the [project setup walkthrough](/lib/project-setup/create-application) + diff --git a/src/fragments/lib-legacy/graphqlapi/ios/getting-started/12_amplifyConfig.mdx b/src/fragments/lib-legacy/graphqlapi/ios/getting-started/12_amplifyConfig.mdx new file mode 100644 index 00000000000..738b62d41cd --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/getting-started/12_amplifyConfig.mdx @@ -0,0 +1 @@ +Upon completion, `amplifyconfiguration.json` will be updated to reference provisioned backend resources. Note that this file should already be a part of your project if you followed the [Project setup walkthrough](/lib/project-setup/create-application). \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/getting-started/20_installLib.mdx b/src/fragments/lib-legacy/graphqlapi/ios/getting-started/20_installLib.mdx new file mode 100644 index 00000000000..cc3ea9cabd3 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/getting-started/20_installLib.mdx @@ -0,0 +1,39 @@ + + + + +import ios0 from "/src/fragments/lib/ios-spm.mdx"; + + + +3. Lastly, choose **AWSAPIPlugin** and **Amplify** and click **Add Package**. + + + + + +To install the Amplify API to your application, **add `AmplifyPlugins/AWSAPIPlugin` to your `Podfile`**. Your `Podfile` should look similar to: + +```ruby +target 'MyAmplifyApp' do + use_frameworks! + pod 'Amplify' + pod 'AmplifyPlugins/AWSAPIPlugin' +end +``` + +To install, download and resolve these pods, **execute the command**: + +```ruby +pod install --repo-update +``` + +Now you can **open your project** by opening the `.xcworkspace` file using the following command: + +```ruby +xed . +``` + + + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/getting-started/30_initapi.mdx b/src/fragments/lib-legacy/graphqlapi/ios/getting-started/30_initapi.mdx new file mode 100644 index 00000000000..039093d1ea9 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/getting-started/30_initapi.mdx @@ -0,0 +1,94 @@ +To initialize the Amplify API category, we are required to use the `Amplify.add()` method for the plugin followed by calling `Amplify.configure()`. + +**Add the following imports** to the top of your `AppDelegate.swift` file: + + + + + +```swift +import Amplify +import AWSAPIPlugin +``` + + + + + +```swift +import Amplify +import AmplifyPlugins +``` + + + + + +**Add the following code** + + + + + +Add to your AppDelegate's `application:didFinishLaunchingWithOptions` method + +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + do { + try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels())) + try Amplify.configure() + print("Amplify configured with API plugin") + } catch { + print("Failed to initialize Amplify with \(error)") + } + + return true +} +``` + + + + + +Create a custom `AppDelegate`, and add to your `application:didFinishLaunchingWithOptions` method +```swift +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + do { + try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels())) + try Amplify.configure() + print("Amplify configured with API plugin") + } catch { + print("Failed to initialize Amplify with \(error)") + } + + return true + } +} +``` + +Then in the `App` scene, use `UIApplicationDelegateAdaptor` property wrapper to use your custom `AppDelegate` +```swift +@main +struct MyAmplifyApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} +``` + + + + + +Upon building and running this application you should see the following in your console window: + +```console +Amplify configured with API plugin +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/getting-started/40_codegen.mdx b/src/fragments/lib-legacy/graphqlapi/ios/getting-started/40_codegen.mdx new file mode 100644 index 00000000000..c57955bff48 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/getting-started/40_codegen.mdx @@ -0,0 +1,15 @@ +## Generate Todo Model class + +To generate the `Todo` model, change directories to your project folder and **execute the command**: + +```bash +amplify codegen models +``` + +The generated files will be under the `amplify/generated/models` directory. It is strongly advised not to put any hand written code in `amplify/generated` directory as it gets re-generated each time codegen is run. +``` +AmplifyModels.swift +Todo.swift +Todo+Schema.swift +``` +Drag and place the entire `models` directory into Xcode to add them to your project. diff --git a/src/fragments/lib-legacy/graphqlapi/ios/getting-started/50_createtodo.mdx b/src/fragments/lib-legacy/graphqlapi/ios/getting-started/50_createtodo.mdx new file mode 100644 index 00000000000..07e58b40c05 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/getting-started/50_createtodo.mdx @@ -0,0 +1,58 @@ + + + + + +```swift +func createTodo() { + let todo = Todo(name: "my first todo", description: "todo description") + Amplify.API.mutate(request: .create(todo)) { event in + switch event { + case .success(let result): + switch result { + case .success(let todo): + print("Successfully created the todo: \(todo)") + case .failure(let graphQLError): + print("Failed to create graphql \(graphQLError)") + } + case .failure(let apiError): + print("Failed to create a todo", apiError) + } + } +} +``` + + + + + +Make sure you have the additional import at the top of your file +```swift +import Combine +``` + +```swift +func createTodo() -> AnyCancellable { + let todo = Todo(name: "my first todo", description: "todo description") + let sink = Amplify.API.mutate(request: .create(todo)) + .resultPublisher + .sink { completion in + if case let .failure(error) = completion { + print("Failed to create graphql \(error)") + } + } + receiveValue: { result in + switch result { + case .success(let todo): + print("Successfully created the todo: \(todo)") + case .failure(let graphQLError): + print("Could not decode result: \(graphQLError)") + } + } + return sink +} +``` + + + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/mutate-data.mdx b/src/fragments/lib-legacy/graphqlapi/ios/mutate-data.mdx new file mode 100644 index 00000000000..391cbe2c8b0 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/mutate-data.mdx @@ -0,0 +1,81 @@ +## Run a mutation + +Now that the client is set up, you can run a GraphQL mutation with `Amplify.API.mutate` to create, update, and delete your data. + + + + + +Make sure you have the following imports at the top of your file +```swift +import Amplify +import AWSPluginsCore +``` + +```swift +func updateTodo() { + // Retrieve your Todo using Amplify.API.query + var todo = Todo(name: "my first todo", description: "todo description") + todo.description = "updated description" + Amplify.API.mutate(request: .update(todo)) { event in + switch event { + case .success(let result): + switch result { + case .success(let todo): + print("Successfully created todo: \(todo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + case .failure(let error): + print("Got failed event with error \(error)") + } + } +} +``` + + + + +Make sure you have the following imports at the top of your file +```swift +import Amplify +import AWSPluginsCore +import Combine +``` + +```swift +func updateTodo() -> AnyCancellable { + // Retrieve your Todo using Amplify.API.query + var todo = Todo(name: "my first todo", description: "todo description") + todo.description = "updated description" + let sink = Amplify.API.mutate(request: .update(todo)) + .resultPublisher + .sink { + if case let .failure(error) = $0 { + print("Got failed event with error \(error)") + } + } + receiveValue: { result in + switch result { + case .success(let todo): + print("Successfully created todo: \(todo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } + return sink +} +``` + + + + + +To create data, replace the request with `.create` +```swift +Amplify.API.mutate(request: .create(todo)) +``` +To delete data, replace the request with `.delete` +```swift +Amplify.API.mutate(request: .delete(todo)) +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/ios/query-data.mdx b/src/fragments/lib-legacy/graphqlapi/ios/query-data.mdx new file mode 100644 index 00000000000..670b793498c --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/query-data.mdx @@ -0,0 +1,390 @@ +## Query by Id + +Now that you were able to make a mutation, take the `Id` that was printed out and use it in your query to retrieve data. + + + + + +```swift +func getTodo() { + Amplify.API.query(request: .get(Todo.self, byId: "9FCF5DD5-1D65-4A82-BE76-42CB438607A0")) { event in + switch event { + case .success(let result): + switch result { + case .success(let todo): + guard let todo = todo else { + print("Could not find todo") + return + } + print("Successfully retrieved todo: \(todo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + case .failure(let error): + print("Got failed event with error \(error)") + } + } +} +``` + + + + + +```swift +func getTodo() -> AnyCancellable { + Amplify.API + .query(request: .get(Todo.self, byId: "9FCF5DD5-1D65-4A82-BE76-42CB438607A0")) + .resultPublisher + .sink { + if case let .failure(error) = $0 { + print("Got failed event with error \(error)") + } + } + receiveValue: { result in + switch result { + case .success(let todo): + guard let todo = todo else { + print("Could not find todo") + return + } + print("Successfully retrieved todo: \(todo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } +} +``` + + + + + +## List Query + +You can get the list of items using `.paginatedList` with optional parameters `limit` and `where` to specify the page size and condition. By default, the page size is 1000. + + + + + + + + + + +```swift +func listTodos() { + let todo = Todo.keys + let predicate = todo.name == "my first todo" && todo.description == "todo description" + Amplify.API.query(request: .paginatedList(Todo.self, where: predicate, limit: 1000)) { event in + switch event { + case .success(let result): + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + case .failure(let error): + print("Got failed event with error \(error)") + } + } +} +``` + + + + + +```swift +func listTodos() -> AnyCancellable { + let todo = Todo.keys + let predicate = todo.name == "my first todo" && todo.description == "todo description" + let sink = Amplify.API.query(request: .paginatedList(Todo.self, where: predicate, limit: 1000)) + .resultPublisher + .sink { + if case let .failure(error) = $0 { + print("Got failed event with error \(error)") + } + } + receiveValue: { result in + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } + return sink +} +``` + + + + + + + + + + + + + +```swift +func listTodos() { + let todo = Todo.keys + let predicate = todo.name == "my first todo" && todo.description == "todo description" + let request = GraphQLRequest.list(Todo.self, where: predicate, limit: 1000) + Amplify.API.query(request: request) { event in + switch event { + case .success(let result): + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + case .failure(let error): + print("Got failed event with error \(error)") + } + } +} +``` + + + + + +```swift +func listTodos() -> AnyCancellable { + let todo = Todo.keys + let predicate = todo.name == "my first todo" && todo.description == "todo description" + let request = GraphQLRequest.list(Todo.self, where: predicate, limit: 1000) + let sink = Amplify.API.query(request: request) + .resultPublisher + .sink { + if case let .failure(error) = $0 { + print("Got failed event with error \(error)") + } + } + receiveValue: { result in + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } + return sink +} +``` + + + + + + + + + +### List subsequent pages of items + +For large data sets, you'll need to paginate through the results. After receiving the first page of results, you can check if there is a subsequent page and obtain the next page. + + + + + +```swift +var todos: [Todo] = [] +var currentPage: List? + +func listFirstPage() { + let todo = Todo.keys + let predicate = todo.name == "my first todo" && todo.description == "todo description" + Amplify.API.query(request: .paginatedList(Todo.self, where: predicate, limit: 1000)) { event in + switch event { + case .success(let result): + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + self.currentPage = todos + self.todos.append(contentsOf: todos) + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + case .failure(let error): + print("Got failed event with error \(error)") + } + } +} + +func listNextPage() { + if let current = currentPage, current.hasNextPage() { + current.getNextPage { result in + switch result { + case .success(let todos): + self.todos.append(contentsOf: todos) + self.currentPage = todos + case .failure(let coreError): + print("Failed to get next page \(coreError)") + } + } + } +} +``` + + + + +```swift +var todos: [Todo] = [] +var currentPage: List? + +func listFirstPage() { + let todo = Todo.keys + let predicate = todo.name == "my first todo" && todo.description == "todo description" + let request = GraphQLRequest.list(Todo.self, where: predicate, limit: 1000) + Amplify.API.query(request: request) { event in + switch event { + case .success(let result): + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + self.currentPage = todos + self.todos.append(contentsOf: todos) + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + case .failure(let error): + print("Got failed event with error \(error)") + } + } +} + +func listNextPage() { + if let current = self.currentPage, current.hasNextPage() { + current.getNextPage { result in + switch result { + case .success(let todos): + self.todos.append(contentsOf: todos) + self.currentPage = todos + case .failure(let coreError): + print("Failed to get next page \(coreError)") + } + } + } +} +``` + + + + + +## List all pages + +If you want to get all pages, retrieve the subsequent page when you have successfully retrieved the first or next page. + +1. Update the above method `listFirstPage()` to `listAllPages()` +2. Call `listNextPageRecursively()` in the success block of the query in `listAllPages()` +2. Update the `listNextPage()` to `listNextPageRecursively()` +3. Call `listNextPageRecursively()` in the success block of the query in `listNextPageRecursively()` + +The completed changes should look like this + + + + + +```swift +var todos: [Todo] = [] +var currentPage: List? + +func listAllPages() { // 1. Updated from `listFirstPage()` + let todo = Todo.keys + let predicate = todo.name == "my first todo" && todo.description == "todo description" + Amplify.API.query(request: .paginatedList(Todo.self, where: predicate, limit: 1000)) { event in + switch event { + case .success(let result): + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + self.currentPage = todos + self.todos.append(contentsOf: todos) + self.listNextPageRecursively() // 2. Added + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + case .failure(let error): + print("Got failed event with error \(error)") + } + } +} + +func listNextPageRecursively() { // 3. Updated from `listNextPage()` + if let current = currentPage, current.hasNextPage() { + current.getNextPage { result in + switch result { + case .success(let todos): + self.todos.append(contentsOf: todos) + self.currentPage = todos + self.listNextPageRecursively() // 4. Added + case .failure(let coreError): + print("Failed to get next page \(coreError)") + } + } + } +} +``` + + + + + +```swift +var todos: [Todo] = [] +var currentPage: List? + +func listAllPages() { // 1. Updated from `listFirstPage()` + let todo = Todo.keys + let predicate = todo.name == "my first todo" && todo.description == "todo description" + let request = GraphQLRequest.list(Todo.self, where: predicate, limit: 1000) + Amplify.API.query(request: request) { event in + switch event { + case .success(let result): + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + self.currentPage = todos + self.todos.append(contentsOf: todos) + self.listNextPageRecursively() // 2. Added + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + case .failure(let error): + print("Got failed event with error \(error)") + } + } +} + +func listNextPageRecursively() { // 3. Updated from `listNextPage()` + if let current = currentPage, current.hasNextPage() { + current.getNextPage { result in + switch result { + case .success(let todos): + self.todos.append(contentsOf: todos) + self.currentPage = todos + self.listNextPageRecursively() // 4. Added + case .failure(let coreError): + print("Failed to get next page \(coreError)") + } + } + } +} +``` + + + diff --git a/src/fragments/lib-legacy/graphqlapi/ios/subscribe-data.mdx b/src/fragments/lib-legacy/graphqlapi/ios/subscribe-data.mdx new file mode 100644 index 00000000000..c4f51356633 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/ios/subscribe-data.mdx @@ -0,0 +1,114 @@ +Subscribe to mutations for creating real-time clients. + +Because the lifetime of the subscription will last longer than the lifetime of a single function, you can create an instance variable at the top of your class: + + + + + +```swift +var subscription: GraphQLSubscriptionOperation? +``` + + + + + +```swift +var subscription: GraphQLSubscriptionOperation? +var dataSink: AnyCancellable? +``` + + + + + +To listen to creation updates, you can use the following code sample: + + + + + +```swift +func createSubscription() { + subscription = Amplify.API.subscribe(request: .subscription(of: Todo.self, type: .onCreate), valueListener: { (subscriptionEvent) in + switch subscriptionEvent { + case .connection(let subscriptionConnectionState): + print("Subscription connect state is \(subscriptionConnectionState)") + case .data(let result): + switch result { + case .success(let createdTodo): + print("Successfully got todo from subscription: \(createdTodo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } + }) { result in + switch result { + case .success: + print("Subscription has been closed successfully") + case .failure(let apiError): + print("Subscription has terminated with \(apiError)") + } + } +} +``` + + + + +```swift +func createSubscription() { + subscription = Amplify.API.subscribe(request: .subscription(of: Todo.self, type: .onCreate)) + dataSink = subscription?.subscriptionDataPublisher.sink { + if case let .failure(apiError) = $0 { + print("Subscription has terminated with \(apiError)") + } else { + print("Subscription has been closed successfully") + } + } + receiveValue: { result in + switch result { + case .success(let createdTodo): + print("Successfully got todo from subscription: \(createdTodo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } +} +``` + + + + + +### Unsubscribing from updates + +#### Listener (iOS 11+) + +To unsubscribe from updates, you can call `cancel()` on the subscription + +```swift +func cancelSubscription() { + // Cancel the subscription listener when you're finished with it + subscription?.cancel() +} +``` + +#### Combine (iOS 13+) + +With the Combine flavor of the `subscribe()` API, you have the option of canceling just the downstream Combine subscriber, or the entire GraphQL subscription. + +Calling `cancel()` on the subscription will disconnect the subscription from the backend. Any downstream subscribers will also be cancelled. On the other hand, calling `cancel()` on the Combine subscriber (e.g., the `AnyCancellable` returned from `sink()` will cause that Combine subscriber to stop receiving updates, but any other attached subscribers will continue to receive subscription data. For example: + +```swift +let subscription = Amplify.API.subscribe(...) +let allUpdates = subscription.subscriptionDataPublisher.sink(...) +let filteredUpdates = subscription.subscriptionDataPublisher.filter{...}.sink(...) + +allUpdates.cancel() // subscription is still connected, + // filteredUpdates will still receive data + +subscription.cancel() // subscription is now disconnected + // filteredUpdates will no longer receive data +``` diff --git a/src/fragments/lib-legacy/graphqlapi/js/authz.mdx b/src/fragments/lib-legacy/graphqlapi/js/authz.mdx new file mode 100644 index 00000000000..6d824128425 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/js/authz.mdx @@ -0,0 +1,66 @@ +## Using Amplify GraphQL client + +Each AppSync API is set with a __default__ authorization mode. + +AWS AppSync also supports [multiple authorization modes on a single API](https://docs.aws.amazon.com/appsync/latest/devguide/security.html#using-additional-authorization-modes) enabling you to add additional authorization modes. + +In order to use this feature with the Amplify GraphQL Client the `API.graphql({...})` function accepts an optional parameter called `authMode`, its value will be one of the supported auth modes: + +- `API_KEY` +- `AWS_IAM` +- `OPENID_CONNECT` +- `AMAZON_COGNITO_USER_POOLS` +- `AWS_LAMBDA` + +
+ +This is an example of using `AWS_IAM` as an authorization mode: + +```js +// Creating a post is restricted to IAM +const createdTodo = await API.graphql({ + query: queries.createTodo, + variables: {input: todoDetails}, + authMode: 'AWS_IAM' +}); +``` + +Previous examples uses `graphqlOperation` function. That function only creates an object with two attributes `query` and `variables`. In order to use `authMode` you need to pass this object as is mentioned on the previous example. + + + +When using __AWS_IAM__ for public API access, unauthenticated logins must be enabled. To enable unauthenticated logins, run `amplify update auth` from the command line and choose __Walkthrough all the auth configurations__. + + + +### AWS Lambda + +You can implement your own custom API authorization logic using an AWS Lambda function. To add a Lambda as an authorization mode for your AppSync API, go to the **Settings** section of the **AppSync console**. + +If you are using a Lambda function as an authorization mode with your AppSync API, you will need to pass an authentication token with each API request and will need to manage token refresh in your application. + +The following example assumes `AWS_LAMBDA` is configured as the default authentication type for your API: +```js +const getAuthToken = () => 'myAuthToken'; +const lambdaAuthToken = getAuthToken(); + +const createdTodo = await API.graphql({ + query: queries.createTodo, + variables: {input: todoDetails}, + authToken: lambdaAuthToken +}); +``` + +If you have a different default authentication type and would like to use `AWS_LAMBDA` with a request: +```javascript +const getAuthToken = () => 'myAuthToken'; +const lambdaAuthToken = getAuthToken(); + +const createdTodo = await API.graphql({ + query: queries.createTodo, + variables: {input: todoDetails}, + authMode: 'AWS_LAMBDA', + authToken: lambdaAuthToken +}); +``` + diff --git a/src/fragments/lib-legacy/graphqlapi/js/cancel-request.mdx b/src/fragments/lib-legacy/graphqlapi/js/cancel-request.mdx new file mode 100644 index 00000000000..49f13f382dd --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/js/cancel-request.mdx @@ -0,0 +1,35 @@ +## CANCEL API requests + +You may cancel any query or mutation request made through API category by keeping a reference to the promise returned. + +```javascript +const promise = API.graphql(graphqlOperation(...)); + +try { + await promise; +} catch (error) { + console.log(error); + // If the error is because the request was cancelled we can confirm here. + if(API.isCancel(error)) { + console.log(error.message); // "my message for cancellation" + // handle user cancellation logic + } +} + +... + +// To cancel the above request +API.cancel(promise, "my message for cancellation"); +``` + +You need to ensure that the promise returned from `API.graphql()` has not been modified. Typically async functions wrap the promise being returned into another promise. For example, the following will not work + +```javascript +async function makeAPICall() { + return API.graphql(graphqlOperation(...)); +} +const promise = makeAPICall(); + +// The following will NOT cancel the request. +API.cancel(promise, "my error message"); +``` diff --git a/src/fragments/lib-legacy/graphqlapi/js/complex-objects.mdx b/src/fragments/lib-legacy/graphqlapi/js/complex-objects.mdx new file mode 100644 index 00000000000..0594a09e7db --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/js/complex-objects.mdx @@ -0,0 +1,85 @@ +## Complex objects +Many times you might want to create logical objects that have more complex data, such as images or videos, as part of their structure. For example, you might create a Person type with a profile picture or a Post type that has an associated image. With AWS AppSync, you can model these as GraphQL types, referred to as complex objects. If any of your mutations have a variable with bucket, key, region, mimeType and localUri fields, the SDK uploads the file to Amazon S3 for you. + +For a complete working example of this feature, see [aws-amplify-graphql](https://github.com/aws-samples/aws-amplify-graphql) on GitHub. + +The GraphQL transformer will configure your resolvers to write to DynamoDB and point at S3 objects when using the `S3Object` type. For example, run the following in an Amplify project: + +```bash +amplify add auth #Select default configuration +amplify add storage #Select S3 with read/write access +amplify add api #Select Cognito User Pool for authorization type +``` + +When prompted, use the following schema: +```graphql +type Todo @model { + id: ID! + name: String! + description: String! + file: S3Object +} + +type S3Object { + bucket: String! + key: String! + region: String! +} + +input CreateTodoInput { + id: ID + name: String! + description: String + file: S3ObjectInput # This input type will be generated for you +} +``` +Save and run `amplify push` to deploy changes. + +To use complex objects you need AWS Identity and Access Management credentials for reading and writing to Amazon S3 which `amplify add auth` configured in the default setting along with a Cognito user pool. These can be separate from the other auth credentials you use in your AWS AppSync client. Credentials for complex objects are set using the `complexObjectsCredentials` parameter, which you can use with AWS Amplify and the complex objects feature like so: + +```javascript +const client = new AWSAppSyncClient({ + url: ENDPOINT, + region: REGION, + auth: { ... }, //Can be User Pools or API Key + complexObjectsCredentials: () => Auth.currentCredentials(), +}); + +(async () => { + let file; + + if (selectedFile) { // selectedFile is the file to be uploaded, typically comes from an + const { name, type: mimeType } = selectedFile; + const [, , , extension] = /([^.]+)(\.(\w+))?$/.exec(name); + + const bucket = aws_config.aws_user_files_s3_bucket; + const region = aws_config.aws_user_files_s3_bucket_region; + const visibility = 'private'; + const { identityId } = await Auth.currentCredentials(); + + const key = `${visibility}/${identityId}/${uuid()}${extension && '.'}${extension}`; + + file = { + bucket, + key, + region, + mimeType, + localUri: selectedFile, + }; + } + + const result = await client.mutate({ + mutation: gql(createTodo), + variables: { + input: { + name: 'Upload file', + description: 'Uses complex objects to upload', + file: file, + } + } + }); + +})(); +``` + +When you run the above mutation a record will be in a DynamoDB table for your AppSync API as well as the corresponding file in an S3 bucket. \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/js/create-or-re-use-existing-backend.mdx b/src/fragments/lib-legacy/graphqlapi/js/create-or-re-use-existing-backend.mdx new file mode 100644 index 00000000000..da5eb5c2795 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/js/create-or-re-use-existing-backend.mdx @@ -0,0 +1,149 @@ +## Create new AppSync GraphQL API + +To create a new GraphQL API, you can use the Amplify CLI `api` category: + +```bash +amplify add api +``` + +When prompted, select **GraphQL**. + +The CLI prompts will help you to customize the options for your GraphQL API. With the provided options, you can: +- Choose the API name +- Choose the default authorization type +- If using an API key as the authorization type, choose the expiration date for the API key +- Configure additional authorization types +- Enable conflict detection (for use with [DataStore](/lib/datastore/getting-started)) +- Choose to either reference an existing GraphQL schema or be given starter GraphQL schema boilerplates + +After configuring your GraphQL API options, update your backend: + +```bash +amplify push +``` + +The `aws-exports.js` configuration file will be updated with the new API details, + +## Re-use existing AppSync GraphQL API + +As an alternative to automatic configuration, you can manually enter AWS AppSync configuration parameters in your app. Authentication type options are `API_KEY`, `AWS_IAM`, `AMAZON_COGNITO_USER_POOLS` and `OPENID_CONNECT`. + +### Using API_KEY + +```javascript +const myAppConfig = { + // ... + 'aws_appsync_graphqlEndpoint': 'https://xxxxxx.appsync-api.us-east-1.amazonaws.com/graphql', + 'aws_appsync_region': 'us-east-1', + 'aws_appsync_authenticationType': 'API_KEY', + 'aws_appsync_apiKey': 'da2-xxxxxxxxxxxxxxxxxxxxxxxxxx', + // ... +} + +Amplify.configure(myAppConfig); +``` + +### Using AWS_IAM + +```javascript +const myAppConfig = { + // ... + 'aws_appsync_graphqlEndpoint': 'https://xxxxxx.appsync-api.us-east-1.amazonaws.com/graphql', + 'aws_appsync_region': 'us-east-1', + 'aws_appsync_authenticationType': 'AWS_IAM', + // ... +} + +Amplify.configure(myAppConfig); +``` + +### Using AMAZON_COGNITO_USER_POOLS + +```javascript +const myAppConfig = { + // ... + 'aws_appsync_graphqlEndpoint': 'https://xxxxxx.appsync-api.us-east-1.amazonaws.com/graphql', + 'aws_appsync_region': 'us-east-1', + 'aws_appsync_authenticationType': 'AMAZON_COGNITO_USER_POOLS', // You have configured Auth with Amazon Cognito User Pool ID and Web Client Id + // ... +} + +Amplify.configure(myAppConfig); +``` + +### Using OPENID_CONNECT + +```javascript +const myAppConfig = { + // ... + 'aws_appsync_graphqlEndpoint': 'https://xxxxxx.appsync-api.us-east-1.amazonaws.com/graphql', + 'aws_appsync_region': 'us-east-1', + 'aws_appsync_authenticationType': 'OPENID_CONNECT', // Before calling API.graphql(...) is required to do Auth.federatedSignIn(...) check authentication guide for details. + // ... +} + +Amplify.configure(myAppConfig); +``` + +### Using with an AppSync custom domain name + +[Custom domain names](https://docs.aws.amazon.com/appsync/latest/devguide/custom-domain-name.html) can have any format, but must end with `/graphql` (see https://graphql.org/learn/serving-over-http/#uris-routes). + +```javascript +const myAppConfig = { + // ... + 'aws_appsync_graphqlEndpoint': 'https://api.yourdomain.com/graphql', + 'aws_appsync_region': 'us-east-1', + 'aws_appsync_authenticationType': 'API_KEY' // All auth modes are supported + // ... +} + +Amplify.configure(myAppConfig); +``` + + +## Using a non-AppSync GraphQL Server + +To access a non-AppSync GraphQL API with your app, you need to configure the endpoint URL in your app’s configuration. Add the following line to your setup: + +```js +import { Amplify, API } from 'aws-amplify'; +import awsconfig from './aws-exports'; + +// Considering you have an existing aws-exports.js configuration file +Amplify.configure(awsconfig); + +// Configure a custom GraphQL endpoint +Amplify.configure({ + API: { + graphql_endpoint: 'https:/www.example.com/my-graphql-endpoint' + } +}); +``` + +### Set Custom Request Headers for non-AppSync GraphQL APIs + +When working with a non-AppSync GraphQL endpoint, you may need to set request headers for authorization purposes. This is done by passing a `graphql_headers` function into the configuration: + +```js +Amplify.configure({ + API: { + graphql_headers: async () => ({ + 'My-Custom-Header': 'my value' + }) + } +}); +``` + +### Signing Request with IAM + +AWS Amplify provides the ability to sign requests automatically with AWS Identity Access Management (IAM) for GraphQL requests that are processed through AWS API Gateway. Add the `graphql_endpoint_iam_region` parameter to your GraphQL configuration statement to enable signing: + +```js +Amplify.configure({ + API: { + graphql_endpoint: 'https://www.example.com/my-graphql-endpoint', + graphql_endpoint_iam_region: 'my_graphql_apigateway_region' + } +}); +``` diff --git a/src/fragments/lib-legacy/graphqlapi/js/delta-sync.mdx b/src/fragments/lib-legacy/graphqlapi/js/delta-sync.mdx new file mode 100644 index 00000000000..04c7f4608ed --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/js/delta-sync.mdx @@ -0,0 +1,367 @@ +## Delta Sync + +DeltaSync allows you to perform automatic synchronization with an AWS AppSync GraphQL server. The client will perform reconnection, exponential backoff, and retries when network errors take place for simplified data replication to devices. It does this by taking the results of a GraphQL query and caching it in the local Apollo cache. The DeltaSync API manages writes to the Apollo cache for you, and all rendering in your app (such as from React components, Angular bindings) should be done through a read-only fetch. + +In the most basic form, you can use a single query with the API to replicate the state from the backend to the client. This is referred to as a "Base Query" and could be a list operation for a GraphQL type which might correspond to a DynamoDB table. For large tables where the content changes frequently and devices switch between offline and online frequently as well, pulling all changes for every network reconnect can result in poor performance on the client. In these cases you can provide the client API a second query called the "Delta Query" which will be merged into the cache. When you do this the Base Query is run an initial time to hydrate the cache with data, and on each network reconnect the Delta Query is run to just get the changed data. The Base Query is also run on a regular bases as a "catch-up" mechanism. By default this is every 24 hours however you can make it more or less frequent. + +By allowing clients to separate the base hydration of the cache using one query and incremental updates in another query, you can move the computation from your client application to the backend. This is substantially more efficient on the clients when regularly switching between online and offline states. This could be implemented in your AWS AppSync backend in different ways such as using a DynamoDB Query on an index along with a conditional expression. You can also leverage Pipeline Resolvers to partition your records to have the delta responses come from a second table acting as a journal. [A full sample with CloudFormation is available in the AppSync documentation](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-delta-sync.html). The rest of this documentation will focus on the client usage. + +You can also use Delta Sync functionality with GraphQL subscriptions, taking advantage of both only sending changes to the clients when they switch network connectivity but also when they are online. In this case you can pass a third query called the "Subscription Query" which is a standard GraphQL subscription statement. When the device is connected, these are processed as normal and the client API simply helps make setting up realtime data easy. However, when the device transitions from offline to online, to account for high velocity writes the client will execute the resubscription along with synchronization and message processing in the following order: + +1. Subscribe to any queries defined and store results in an incoming queue +2. Run the appropriate query (If `baseRefreshIntervalInSeconds` has elapsed, run the Base Query otherwise only run the Delta Query) +3. Update the cache with results from the appropriate query +4. Drain the subscription queue and continue processing as normal + +Finally, you might have other queries which you wish to represent in your application other than the base cache hydration. For instance a `getItem(id:ID)` or other specific query. If your alternative query corresponds to items which are already in the normalized cache, you can point them at these cache entries with the `cacheUpdates` function which returns an array of queries and their variables. The DeltaSync client will then iterate through the items and populate a query entry for each item on your behalf. If you wish to use additional queries which don't correspond to items in your base query cache, you can always create another instance of the `client.sync()` process. + +## Usage + +```typescript +// Start DeltaSync +const subscription = client.sync(options) +/* +Under the covers, this is actually an Observable that the AppSync client automatically subscribes to for you, so the returned object is a "subscription". This means that you can automatically stop the synchronization process like so: +*/ +// Stop DeltaSync +subscription.unsubscribe(); +``` + +**The `options` object** + +**baseQuery** + - `query`: A `DocumentNode` for the base data (e.g. as returned by [`gql`](https://github.com/apollographql/graphql-tag#gql)) + - `variables` [optional]: An object with the query variables, if any. + - `baseRefreshIntervalInSeconds` [optional]: Number of seconds after which the base query will be run again. Default value: `86400` (24 hrs) + - `update` [optional]: A function to update the cache, see: [Apollo's `update` function](https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-mutation-options-update) + +**subscriptionQuery** + - `query`: A `DocumentNode` for the subscription (e.g. as returned by [`gql`](https://github.com/apollographql/graphql-tag#gql)) + - `variables` [optional]: An object with the query variables, if any. + - `update` [optional]: A function to update the cache, see: [Apollo's `update` function](https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-mutation-options-update) + +**deltaQuery** + - `query`: A `DocumentNode` for the deltas (e.g. as returned by [`gql`](https://github.com/apollographql/graphql-tag#gql)) + - `variables` [optional]: An object with the query variables, if any. + - `update` [optional]: A function to update the cache, see: [Apollo's `update` function](https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-mutation-options-update) + +## The buildSync helper + +The quickest way to get started with the DeltaSync feature is by using the `buildSync` helper function. This helper function will build an `options` object with the appropriate `update` functions that will update the cache for you in a similar fashion to the [offline helpers](https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/master/OFFLINE_HELPERS). + +The first argument you need to pass is the GraphQL `__typename` for your base query. The second argument is the `options` object from the previous section (without the `update` keys, since those will be generated for you by this helper function). + +You can **optionally** pass a `cacheUpdates` parameter to the second argument with the following structure: +- **deltaRecord**: A function which receives a `deltaRecord` (e.g. an individual item in the cache populated by the base/delta/subscription query) and returns an array of GraphQL queries and it's variables to be written to the cache. + +Example: + +```typescript + client.sync( + buildSync("Post", { + baseQuery: { + query: DeltaSync.BaseQuery + }, + subscriptionQuery: { + query: DeltaSync.Subscription + }, + deltaQuery: { + query: DeltaSync.DeltaSync + }, + cacheUpdates: ( deltaRecord ) => { + const id = deltaRecord.id; + return [{ query: DeltaSync.GetItem, variables: { id: id } }]; + } + }) + ) +``` + +### Requirements for helper function +- Your `baseQuery` returns a list, not a nested type +- Your `deltaQuery` expects a parameter called `lastSync` of type `AWSTimestamp` and returns a list with the same fields as your `baseQuery` (an optionally, an `aws_ds` field with a value of `'DELETE'` for deletions, any other value for insert/update) +- The mutations that trigger the subscription in your `subscriptionQuery` should return a single record with the same fields as the items from your `baseQuery`, (an optionally, an `aws_ds` field with a value of `'DELETE'` for deletions, any other value for insert/update) + +### Example + +The schema for this sample is below. [A full sample with CloudFormation is available in the AppSync documentation](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-delta-sync.html). + +```graphql +input CreatePostInput { + author: String! + title: String! + content: String! + url: String + ups: Int + downs: Int +} + +enum DeltaAction { + DELETE +} + +type Mutation { + createPost(input: CreatePostInput!): Post + updatePost(input: UpdatePostInput!): Post + deletePost(id: ID!): Post +} + +type Post { + id: ID! + author: String! + title: String! + content: String! + url: AWSURL + ups: Int + downs: Int + createdDate: String + aws_ds: DeltaAction +} + +type Query { + getPost(id: ID!): Post + listPosts: [Post] + listPostsDelta(lastSync: AWSTimestamp): [Post] +} + +type Subscription { + onDeltaPost: Post + @aws_subscribe(mutations: ["createPost","updatePost","deletePost"]) +} + +input UpdatePostInput { + id: ID! + author: String + title: String + content: String + url: String + ups: Int + downs: Int +} + +schema { + query: Query + mutation: Mutation + subscription: Subscription +} +``` + +### Sample queries + +```graphql +query Base { + listPosts { + id + title + author + content + } +} + +query Delta($lastSync: AWSTimestamp!) { + listPostsDelta( + lastSync: $lastSync + ) { + id + title + author + content + aws_ds + } +} + +subscription Subscription { + onDeltaPost { + id + title + author + content + aws_ds + } +} +``` + +Define the queries from above in a `./graphql/DeltaSync.js` file to import in your app: + +```javascript +import gql from "graphql-tag"; + +export const BaseQuery = gql`query Base{ + listPosts { + id + title + author + content + } +}`; + +export const GetItem = gql`query GetItem($id: ID!){ + getPost(id: $id) { + id + title + author + content + } +}`; + +export const Subscription = gql`subscription Subscription { + onDeltaPost { + id + title + author + content + } +}`; + +export const DeltaSync = gql`query Delta($lastSync: AWSTimestamp!) { + listPostsDelta( + lastSync: $lastSync + ) { + id + title + author + content + aws_ds + } +}`; +``` + +```typescript +import { AWSAppSyncClient, buildSync } from "aws-appsync"; +import * as DeltaSync from "./graphql/DeltaSync"; + +const client = new AWSAppSyncClient({ + // ... +}); + +const subscription = client.sync( + buildSync('Post', { + baseQuery: { query: DeltaSync.BaseQuery }, + subscriptionQuery: { query: DeltaSync.Subscription }, + deltaQuery: { query: DeltaSync.DeltaSync }, + cacheUpdates : ({id}) => [{query: DeltaSync.getItem, variables: {id}] + }) +); +``` + +### React example + +Suppose you have an app created with [Create React App](https://github.com/facebook/create-react-app) with the following structure: + +- App.js + - Sets up `AWSAppSyncClient` and `client.sync` as above + - Renders `` and `` +- AllPosts.jsx exports `` +- GetPost.jsx exports `` + +`App.js` + +```typescript +const client = new AWSAppSyncClient({ + url: awsconfig.aws_appsync_graphqlEndpoint, + region: awsconfig.aws_appsync_region, + auth: { + type: awsconfig.aws_appsync_authenticationType, + apiKey: awsconfig.aws_appsync_apiKey + } +}); + +client.hydrated().then(() => + client.sync( + buildSync("Post", { + baseQuery: { + query: DeltaSync.BaseQuery + }, + subscriptionQuery: { + query: DeltaSync.Subscription + }, + deltaQuery: { + query: DeltaSync.DeltaSync + }, + cacheUpdates: ({ id }) => [ + { query: DeltaSync.GetItem, variables: { id } } + ] + }) + ) +); + +const App = () => ( + + +
+ +
+ +
+
+
+); +``` + +In `AllPosts.jsx` you would have code like so: + +```typescript +const AllPosts = ({ postsList }) => ( +
+
+      {JSON.stringify(postsList, null, 2)}
+    
+
+); + +export default graphql(DeltaSync.BaseQuery, { + options: { + fetchPolicy: "cache-only" + }, + props: ({ data }) => ({ + postsList: data.listPosts || [] + }) +})(AllPosts); +``` + +In `GetPost.jsx` you would have: + +```typescript +const OnePost = ({ post }) => ( +
+
{JSON.stringify(post, null, 2)}
+
+); + +export default graphql(DeltaSync.GetItem, { + options: ({ id }) => ({ + variables: { id }, + fetchPolicy: "cache-only" + }), + props: ({ data: { getPost } }) => ({ + post: getPost + }) +})(OnePost); +``` + +**Note**: The `fetchPolicy` is `cache-only` as all of the network requests are handled automatically by the `client.sync()` operation. You should use this if using different queries in other components as the `client.sync()` API manages the cache lifecycle. If you use another `fetch-policy` such as `cache-and-network` then extra network requests may take place negating the Delta Sync benefits. + +## Writing update functions + +If you do not want to use the `buildSync` helper then you are responsible for managing cache updates in your application code. Note that this can be a complex process as you will need to manage create, update, and deletes appropriately. An example of this would be updating the cache with a delta record as below, noting that you must update the returned type to match the type from your base query. + +```javascript +client.sync({ + baseQuery: { query: DeltaSyncQueries.BaseQuery }, + deltaQuery: { + query: DeltaSyncQueries.DeltaSync, + update: (cache, { data: { listPostsDelta } }) => { + const query = DeltaSyncQueries.GetItem; + + listPostsDelta.forEach(deltaRecord => { + const variables = { id: deltaRecord.id }; + + cache.writeQuery({ + query, + variables, + data: { getPost: { ...deltaRecord, __typename: 'Post' } } + }); + }); + } + } + }); +``` diff --git a/src/fragments/lib-legacy/graphqlapi/js/getting-started.mdx b/src/fragments/lib-legacy/graphqlapi/js/getting-started.mdx new file mode 100644 index 00000000000..89c9b6b63dd --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/js/getting-started.mdx @@ -0,0 +1,218 @@ +> Prerequisite: [Install, configure and init an Amplify project](/cli/start/install) with Amplify CLI + +In this section, you'll learn how to deploy an AWS AppSync GraphQL API and connect to it from a JavaScript client application. + +## Create the GraphQL API + +To create a GraphQL API, use the Amplify `add` command: + +```bash +amplify add api +``` + +```console +? Please select from one of the below mentioned services: + > GraphQL +? Here is the GraphQL API that we will create. Select a setting to edit or continue: + > Continue +? Choose a schema template: + > Single object with fields (e.g., “Todo” with ID, name, description) +? Do you want to edit the schema now? + > Yes +``` + +The CLI should open this GraphQL schema in your text editor. + +__amplify/backend/api/myapi/schema.graphql__ + +```graphql +type Todo @model { + id: ID! + name: String! + description: String +} +``` + +To deploy the API, you can use the Amplify `push` command: + +```bash +amplify push +``` + +```console +? Are you sure you want to continue? Y + +? Do you want to generate code for your newly created GraphQL API? Y +? Choose the code generation language target: javascript (or your preferred language target) +? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js +? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions? Y +? Enter maximum statement depth [increase from default if your schema is deeply nested]: 2 +``` + +Now the API has been deployed and you can start using it. + +Because the `Todo` type was decorated with an `@model` directive of the [GraphQL Transform](/cli/graphql/data-modeling) library, the CLI created the additional schema and resolvers for queries, mutations, and subscriptions as well as a DynamoDB table to hold the Todos. + +To view the deployed services in your project at any time, go to Amplify Console by running the Amplify `console` command: + +```bash +amplify console +``` + +## Configure your application + +Add Amplify to your app with `yarn` or `npm`: + +```bash +npm install aws-amplify +``` + +In your app's entry point i.e. App.js, import and load the configuration file: + +```javascript +import { Amplify, API, graphqlOperation } from 'aws-amplify'; +import awsconfig from './aws-exports'; +Amplify.configure(awsconfig); +``` + +## Enable queries, mutations, and subscriptions + +Now that the GraphQL API has deployed, it’s time to learn how to interact with it from a JavaScript client application. With GraphQL, you typically have the following types of operations: + +- __Mutations__ - write data to the API (create, update, delete operations) + +```js +import { API, graphqlOperation } from 'aws-amplify'; +import { createTodo, updateTodo, deleteTodo } from './graphql/mutations'; + +const todo = { name: "My first todo", description: "Hello world!" }; + +/* create a todo */ +await API.graphql(graphqlOperation(createTodo, {input: todo})); + +/* update a todo */ +await API.graphql(graphqlOperation(updateTodo, { input: { id: todoId, name: "Updated todo info" }})); + +/* delete a todo */ +await API.graphql(graphqlOperation(deleteTodo, { input: { id: todoId }})); +``` +- __Queries__ - read data from the API (list, get operations) + +```js +import { API, graphqlOperation } from 'aws-amplify'; +import { listTodos } from './graphql/queries'; + +const todos = await API.graphql(graphqlOperation(listTodos)); +``` + +- __Subscriptions__ - subscribe to changes in data for real-time functionality (onCreate, onUpdate, onDelete) + +```js +import { API, graphqlOperation } from 'aws-amplify'; +import { onCreateTodo } from './graphql/subscriptions'; + +// Subscribe to creation of Todo +const subscription = API.graphql( + graphqlOperation(onCreateTodo) +).subscribe({ + next: (todoData) => { + console.log(todoData); + // Do something with the data + } +}); + +// Stop receiving data updates from the subscription +subscription.unsubscribe(); +``` + +### Updating Your GraphQL Schema + +When you create a GraphQL backend with the CLI, the schema definition for your backend data structure is saved in one of two places: + +1. By default, schemas are saved in *amplify/backend/api/YOUR-API-NAME/schema.graphql*. If the `schema.graphql` file exists, it will take precedence over option 2. +2. Optionally, schemas may be saved as a set of `.graphql` files stored in the *amplify/backend/api/YOUR-API-NAME/schema/* directory. E.g. you might have files `Query.graphql`, `User.graphql`, and `Post.graphql`. + +Once your API is deployed, updating the schema is easy with the CLI. You can edit the schema file(s) and run *amplify push* command to update your GraphQL backend. + +For example, a sample GraphQL schema will look like this: + +```graphql +type Todo @model { + id: ID! + name: String! + description: String +} +``` + +Add a *priority* field to your Todo type: + +```graphql +type Todo @model { + id: ID! + name: String! + description: String + priority: String +} +``` + +Save your schema file and update your GraphQL backend: + +```bash +amplify push +``` + +When you run the *push* command, you will notice that your schema change is automatically detected, and your backend will be updated respectively. + +```console +| Category | Resource name | Operation | Provider plugin | +| -------- | --------------- | --------- | ----------------- | +| Api | myapi | Update | awscloudformation | +``` + +When the update is complete, you can see the changes to your backend by running the following command and select GraphQL option. + +```bash +amplify api console +? Please select from one of the below mentioned services: (Use arrow keys) +❯ GraphQL + REST +``` + +### Using GraphQL Transformers + +As you can notice in the sample schema file above, the schema has a `@model` directive. The `@model` directive leverages a set of libraries that can help simplify the process of bootstrapping highly scalable, serverless GraphQL APIs on AWS. The `@model` directive tells the GraphQL Transform that we would like to store Todo objects in an Amazon DynamoDB table and configure CRUD operations for it. When you create or update your backend with *push* command, the CLI will automatically create and configure a new DynamoDB table that works with your AppSync API. The `@model` directive is just one of multiple transformers that can be used by annotating your *schema.graphql*. + +The following directives are available to be used when defining your schema: + +| Directive | Description | +| --- | --- | +| [@model](/cli/graphql/data-modeling) on Object | Store objects in DynamoDB and configure CRUD resolvers. | +| [@auth](/cli/graphql/authorization-rules) on Object | Define authorization strategies for your API. | +| [@hasOne, @hasMany, @belongsTo, @manyToMany](/cli/graphql/data-modeling) on Field | Specify relationships between @model object types. | +| [@searchable](/cli/graphql/search-and-result-aggregations) on Object | Stream data of an @model object type to the Amazon OpenSearch Service. | +| [@primaryKey and @index](/cli/graphql/data-modeling) on Object | Index your data with keys. | +| [@function](/cli/graphql/custom-business-logic#lambda-function-resolver) on Field | Connect Lambda resolvers to your API. | +| [@predictions](/cli/graphql/connect-to-machine-learning-services/) on Field | Connect machine learning services. | +| [@http](/cli/graphql/custom-business-logic#http-resolver) on Field | Configure HTTP resolvers within your API. | + +You may also write your own transformers to implement reproducible patterns that you find useful. + +### Mocking and Local Testing + +Amplify supports running a local mock server for testing your application with AWS AppSync, including debugging of resolvers, before pushing to the cloud. Please see the [CLI Toolchain documentation](/cli/usage/mock) for more details. + +### Generate client types from a GraphQL schema + +When working with GraphQL data it is useful to import types from your schema for type safety. You can do this with the Amplify CLI's automated code generation feature. The CLI automatically downloads GraphQL Introspection Schemas from the defined GraphQL endpoint and generates TypeScript or Flow classes for you. Every time you push your GraphQL API, the CLI will provide you the option to generate types and statements. + +If you want to generate your GraphQL statements and types, run: + +```bash +amplify codegen +``` + +A TypeScript or Flow type definition file will be generated in your target folder. + +### API configuration in the amplify folder + +The Amplify CLI will create an `amplify/backend/api` folder that will hold the existing GraphQL schema, resolvers, and additional configuration around the API. To learn more about how the CLI manages this configuration, check out the documentation [here](/cli/graphql/overview). To learn how to configure custom GraphQL resolvers, check out the documentation [here](/cli/graphql/custom-business-logic#override-amplify-generated-resolvers). diff --git a/src/fragments/lib-legacy/graphqlapi/js/mutate-data.mdx b/src/fragments/lib-legacy/graphqlapi/js/mutate-data.mdx new file mode 100644 index 00000000000..480d9979803 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/js/mutate-data.mdx @@ -0,0 +1,90 @@ +## Using Amplify GraphQL client + +### Mutations + +In GraphQL, mutations are used to create, update, or delete data. Here are some examples of creating, updating, and deleting items using the Amplify GraphQL client. + +#### Creating an item + +```javascript +import { API } from "aws-amplify"; +import * as mutations from './graphql/mutations'; + +const todoDetails = { + name: 'Todo 1', + description: 'Learn AWS AppSync' +}; + +const newTodo = await API.graphql({ query: mutations.createTodo, variables: {input: todoDetails}}); +``` + +You do not have to pass in `createdAt` and `updatedAt` fields, AppSync manages this for you. + +You can optionally import the `graphqlOperation` helper function to help you construct the argument object: + +```javascript +import { API, graphqlOperation } from 'aws-amplify'; +// ... +const newTodo = await API.graphql(graphqlOperation(mutations.createTodo, {input: todoDetails})); // equivalent to above example +``` + +#### Updating an item + +```javascript +import { API } from "aws-amplify"; +import * as mutations from './graphql/mutations'; + +const todoDetails = { + id: 'some_id', + description: 'My updated description!' +}; + +const updatedTodo = await API.graphql({ query: mutations.updateTodo, variables: {input: todoDetails}}); +``` + +Notes: + +- You do not have to pass in `createdAt` and `updatedAt` fields, AppSync manages this for you. +- If you pass in *extra* input fields not expected by the AppSync schema, this query will fail. You can see this in the `error` field returned by the query (the query itself does not throw, per GraphQL design). + +#### Deleting an item + +```javascript +import { API } from "aws-amplify"; +import * as mutations from './graphql/mutations'; + +const todoDetails = { + id: 'some_id', +}; + +const deletedTodo = await API.graphql({ query: mutations.deleteTodo, variables: {input: todoDetails}}); +``` + +Only an `id` is needed. + + +Join table records must be deleted prior to deleting the associated records. For example, for a many-to-many relationship between Posts and Tags, delete the PostTags join record prior to deleting a Post or Tag. + + +### Custom authorization mode + +By default, each AppSync API will be set with a default authorization mode when you configure your app. If you would like to override the default authorization mode, you can do so by passing in an `authMode` property. + +#### Mutation with custom authorization mode + +```js +import { API } from "aws-amplify"; +import * as mutations from './graphql/mutations'; + +const todoDetails = { + id: 'some_id', + name: 'My todo!', + description: 'Hello world!' +}; + +const todo = await API.graphql({ + query: mutations.createTodo, + variables: {input: todoDetails}, + authMode: 'AWS_IAM' +}); +``` diff --git a/src/fragments/lib-legacy/graphqlapi/js/offline.mdx b/src/fragments/lib-legacy/graphqlapi/js/offline.mdx new file mode 100644 index 00000000000..0876e06e9b4 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/js/offline.mdx @@ -0,0 +1,5 @@ +For offline scenarios, Amplify provides [DataStore](/lib/datastore/getting-started). + +[Amplify DataStore](/lib/datastore/getting-started) uses AWS AppSync to make it easy to build apps that need to support offline and low-latency scenarios. DataStore also makes working with distributed, cross-user data just as simple as working with local-only data by providing a programming model for leveraging shared and distributed data without writing additional code. + +To learn more about building offline-first apps with DataStore, check out the documentation [here](/lib/datastore/getting-started). diff --git a/src/fragments/lib-legacy/graphqlapi/js/query-data.mdx b/src/fragments/lib-legacy/graphqlapi/js/query-data.mdx new file mode 100644 index 00000000000..2fed73b4476 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/js/query-data.mdx @@ -0,0 +1,156 @@ +## Using Amplify GraphQL Client + +The API category provides a GraphQL client for working with queries, mutations, and subscriptions. + +### Query Declarations + +The Amplify CLI codegen automatically generates all possible GraphQL statements (queries, mutations and subscriptions) and for JavaScript applications saves it in `src/graphql` folder + +```javascript +import * as queries from './graphql/queries'; +import * as mutations from './graphql/mutations'; +import * as subscriptions from './graphql/subscriptions'; +``` + +### Simple Query + +Running a GraphQL query is simple. Import the generated query and execute it with `API.graphql`: + +```javascript +import { API } from 'aws-amplify'; +import * as queries from './graphql/queries'; + +// Simple query +const allTodos = await API.graphql({ query: queries.listTodos }); +console.log(allTodos); // result: { "data": { "listTodos": { "items": [/* ..... */] } } } + +// Query using a parameter +const oneTodo = await API.graphql({ + query: queries.getTodo, + variables: { id: 'some id' } +}); +``` + +The TypeScript signature of API.graphql returns a `Promise | Observable`. [For now](https://github.com/aws-amplify/amplify-js/issues/6369), you need to assert the type before `await`ing: + +```typescript +const allTodos = await( + API.graphql({ query: queries.listTodos }) as Promise +); +``` + +You can optionally import the `graphqlOperation` helper function to help you construct this argument object: + +```javascript +import { API, graphqlOperation } from 'aws-amplify'; + +const oneTodo = await API.graphql( + graphqlOperation(queries.getTodo, { id: 'some id' }) +); +// equivalent to +// const oneTodo = await API.graphql({ query: queries.getTodo, variables: { id: 'some id' }})); +``` + +### Custom authorization mode + +By default, each AppSync API will be set with a default authorization mode when you configure your app. If you would like to override the default authorization mode, you can do so by passing in an `authMode` property. For example, this is useful when you have public reads via API Key auth and authenticated reads via IAM auth. + +#### Query with custom authorization mode + +```js +import { API } from 'aws-amplify'; +import * as queries from './graphql/queries'; + +const todos = await API.graphql({ + query: queries.listTodos, + authMode: 'AWS_IAM' +}); +``` + +## Filtered and Paginated Queries + +As your data grows, you will want to do pagination and filtering at the AppSync level instead of on the client. Fortunately, this is already built in to `API.graphql`, but you need to understand the schema of these queries. [This is explained in the AppSync docs](https://docs.aws.amazon.com/appsync/latest/devguide/using-your-api.html), but here we will translate them to the `API.graphql` equivalent. + +You can find the input schemas in the Docs pane of the GraphQL explorer or inside your autogenerated `/graphql` folder. They look like this: + +```graphql +listProducts( + filter: ModelTodoFilterInput + limit: Int + nextToken: String): ModelTodoConnection + +input ModelTodoFilterInput { + id: ModelIDInput + priority: ModelIntInput + # ... all your other Todo fields here + and: [ModelTodoFilterInput] + or: [ModelTodoFilterInput] + not: ModelTodoFilterInput +} +``` + +### Filtering Queries + +Those input types in your schema indicate what kinds of filtering you can perform on them. For example, an integer field like `ModelIntInput` has this schema: + +```graphql +input ModelIntInput { + ne: Int # "not equal to" + eq: Int # "equal to" + le: Int # "less than or equal to" + lt: Int # "less than" + ge: Int # "greater than or equal to" + gt: Int # "greater than" + between: [Int] + attributeExists: Boolean + attributeType: ModelAttributeTypes +} +``` + +These vary based on the type of the field, but are linked to corresponding [DynamoDB queries](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html). + +```js +// Query with filters, limits, and pagination +let filter = { + priority: { + eq: 1 // filter priority = 1 + } +}; +await API.graphql({ query: listProducts, variables: { filter: filter}})); +``` + +### Compound Filters + +You can combine filters with `and`, `or`, and `not` boolean logic. Observe, in the autogenerated schema above, that `ModelTodoFilterInput` is recursive in respect to those fields. So if, for example, you wanted to filter for `priority` values of 1 OR 2, you would do this: + +```js +let filter = { + or: [{ priority: {eq:1} }, + { priority: {eq:2} }] + }; +await API.graphql({ query: listProducts, variables: { filter: filter}})); +``` + +Note that querying for `priority` of 1 AND 2 would return no results, because this is boolean logic instead of natural language. + +### Paginating Queries + +Pagination in AppSync is done by making a request with a `limit`, and getting back a `nextToken` in order to get a cursor for the next page in your query: + +```js +// page 1 of query +const { data: { listProducts: { items: itemsPage1, nextToken } } } = await API.graphql({ query: listProducts, variables: { limit: 20, /* add filter as needed */ }})); +// // we are assuming that `listProducts` includes a query for `nextToken`, which is the case for autogenerated GraphQL query strings. + +// page 2 of query +const { data: { listProducts: { items: itemsPage2 } } } = await API.graphql({ query: listProducts, variables: { limit: 20, nextToken }})); +``` + +A `nextToken` is a very long string that looks like `"eyJ2ZXJzaW9uejE1a2RPanZPQzFCMlFTdGNxdUFBQUJxekNDQWFjR0NTcUdTSWIzRFFFSEJxQ0NBWmd3Z2dHVUFnRUFNSUlCalFZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5eEtWQjUvTlJxS2o5ZHBYc0NBUkNBZ2dGZUZFNW1MbTRkb25BOUg0U0FpOGhSZ1lucmRndmQz"` which represents the cursor to the starting item of the next query made with these filters. + +### Frequently Asked Questions + +- There is no API to get a total page count at this time. Note that scanning all items is a [potentially expensive operation](https://github.com/aws-amplify/amplify-js/issues/2901). +- Sorting is [available in DataStore](https://docs.amplify.aws/lib/datastore/data-access/q/platform/js#predicates) but not in AppSync. +- AppSync schemas do not follow the edges/nodes of the [Relay spec](https://relay.dev/docs/en/graphql-server-specification.html) but are spiritually similar. +- You [cannot query by `page` number](https://github.com/aws-amplify/amplify-cli/issues/5086), you have to query by `nextToken`. diff --git a/src/fragments/lib-legacy/graphqlapi/js/subscribe-data.mdx b/src/fragments/lib-legacy/graphqlapi/js/subscribe-data.mdx new file mode 100644 index 00000000000..05f6c4e511f --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/js/subscribe-data.mdx @@ -0,0 +1,60 @@ +## Using Amplify GraphQL client + +Subscriptions is a GraphQL feature allowing the server to send data to its clients when a specific event happens. You can enable real-time data integration in your app with a subscription. + +```javascript +import { Amplify, API, graphqlOperation } from 'aws-amplify'; +import * as subscriptions from './graphql/subscriptions'; + +// Subscribe to creation of Todo +const subscription = API.graphql( + graphqlOperation(subscriptions.onCreateTodo) +).subscribe({ + next: ({ provider, value }) => console.log({ provider, value }), + error: (error) => console.warn(error) +}); + +// Stop receiving data updates from the subscription +subscription.unsubscribe(); +``` + +When using **AWS AppSync** subscriptions, be sure that your AppSync configuration is at the root of the configuration object, instead of being under API: + +```javascript +Amplify.configure({ + Auth: { + identityPoolId: 'xxx', + region: 'xxx', + cookieStorage: { + domain: 'xxx', + path: 'xxx', + secure: true + } + }, + aws_appsync_graphqlEndpoint: 'xxxx', + aws_appsync_region: 'xxxx', + aws_appsync_authenticationType: 'xxxx', + aws_appsync_apiKey: 'xxxx' +}); +``` + +### Subscription connection status updates + +Now that your application is setup and using subscriptions, you may want to know when the subscription is finally established, or reflect to your users when the subscription isn't healthy. You can monitor the connection state for changes via Hub. + +```typescript +import { CONNECTION_STATE_CHANGE, ConnectionState } from '@aws-amplify/pubsub'; +import { Hub } from 'aws-amplify'; + +Hub.listen('api', (data: any) => { + const { payload } = data; + if (payload.event === CONNECTION_STATE_CHANGE) { + const connectionState = payload.data.connectionState as ConnectionState; + console.log(connectionState); + } +}); +``` + +import jsConnectionStates from '/src/fragments/lib/pubsub/js/connection-states.mdx'; + + diff --git a/src/fragments/lib-legacy/graphqlapi/native_common/advanced-workflows/common.mdx b/src/fragments/lib-legacy/graphqlapi/native_common/advanced-workflows/common.mdx new file mode 100644 index 00000000000..746effd5e13 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/native_common/advanced-workflows/common.mdx @@ -0,0 +1,165 @@ +This section describes different use cases for constructing your own custom GraphQL requests and how to approach it. You may want to construct your own GraphQL request if you want to +- retrieve only a subset of the data to reduce data transfer +- retrieve nested objects at a depth that you choose +- combine multiple operations into a single request +- send custom headers to your AppSync endpoint + +A GraphQL request is automatically generated for you when using AWSAPIPlugin with the existing workflow. For example, if you have a Todo model, a mutation request to save the Todo will look like this: + +import ios0 from "/src/fragments/lib/graphqlapi/ios/advanced-workflows/10_example.mdx"; + + + +import android1 from "/src/fragments/lib/graphqlapi/android/advanced-workflows/10_example.mdx"; + + + +import flutter1 from "/src/fragments/lib/graphqlapi/flutter/advanced-workflows/10_example.mdx"; + + + +Underneath the covers, a request is generated with a GraphQL document and variables and sent to the AppSync service. + +```json +{ + "query": "mutation createTodo($input: CreateTodoInput!) { + createTodo(input: $input) { + id + name + description + } + }", + "variables": "{ + "input": { + "id": "[UNIQUE-ID]", + "name": "my first todo", + "description": "todo description" + } + } +} +``` + +The different parts of the document are described as follows +- `mutation` - the operation type to be performed, other operation types are `query` and `subscription` +- `createTodo($input: CreateTodoInput!)` - the name and input of the operation. +- `$input: CreateTodoInput!` - the input of type `CreateTodoInput!` referencing the variables containing JSON input +- `createTodo(input: $input)` - the mutation operation which takes a variable input from `$input` +- the selection set containing `id`, `name`, and `description` are fields specified to be returned in the response + +You can learn more about the structure of a request from [GraphQL Query Language](https://graphql.org/learn/) and [AppSync documentation](https://docs.aws.amazon.com/appsync/latest/devguide/graphql-overview.html). To test out constructing your own requests, open the AppSync console using `amplify console api` and navigate to the Queries tab. + +## Subset of data + +The selection set of the document specifies which fields are returned in the response. For example, if you are displaying a view of the Todo without the description, you can construct the document to omit the field. You can learn more about selection sets [here](https://spec.graphql.org/draft/#sec-Selection-Sets). + +``` +query getTodo($id: ID!) { + getTodo(id: $id) { + id + name + } +} +``` +The response data will look like this +```json +{ + "data": { + "getTodo": { + "id": "111", + "name": "my first todo" + } + } +} +``` +First, create your own `GraphQLRequest` + +import ios2 from "/src/fragments/lib/graphqlapi/ios/advanced-workflows/20_custom.mdx"; + + + +import android3 from "/src/fragments/lib/graphqlapi/android/advanced-workflows/20_custom.mdx"; + + + +import flutter3 from "/src/fragments/lib/graphqlapi/flutter/advanced-workflows/20_custom.mdx"; + + + +## Nested Data +If you have a relational model, you can retrieve the nested object by creating a `GraphQLRequest` with a selection set containing the nested object's fields. For example, in this schema, the Post can contain multiple comments and notes. + +```graphql +enum PostStatus { + ACTIVE + INACTIVE +} + +type Post @model { + id: ID! + title: String! + rating: Int! + status: PostStatus! + comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) + notes: [Note] @hasMany(indexName: "byNote", fields: ["id"]) +} + +type Comment @model { + id: ID! + postID: ID! @index(name: "byPost", sortKeyFields: ["content"]) + post: Post! @belongsTo(fields: ["postID"]) + content: String! +} + +type Note @model { + id: ID! + postID: ID! @index(name: "byNote", sortKeyFields: ["content"]) + post: Post! @belongsTo(fields: ["postID"]) + content: String! +} +``` + +If you only want to retrieve the comments, without the notes, create a `GraphQLRequest` for the Post with nested fields only containing the comment fields. + +import ios4 from "/src/fragments/lib/graphqlapi/ios/advanced-workflows/30_nested.mdx"; + + + +import android5 from "/src/fragments/lib/graphqlapi/android/advanced-workflows/30_nested.mdx"; + + + +import flutter5 from "/src/fragments/lib/graphqlapi/flutter/advanced-workflows/30_nested.mdx"; + + + +## Combining Multiple Operations + +When you want to perform more than one operation in a single request, you can place them within the same document. For example, to retrieve a Post and a Todo + +import ios6 from "/src/fragments/lib/graphqlapi/ios/advanced-workflows/40_multiple.mdx"; + + + +import android7 from "/src/fragments/lib/graphqlapi/android/advanced-workflows/40_multiple.mdx"; + + + +import flutter7 from "/src/fragments/lib/graphqlapi/flutter/advanced-workflows/40_multiple.mdx"; + + + +## Adding Headers to Outgoing Requests + +By default, the API plugin includes appropriate authorization headers on your outgoing requests. However, you may have an advanced use case where you wish to send additional request headers to AppSync. + +import ios8 from "/src/fragments/lib/graphqlapi/ios/advanced-workflows/50_interceptor.mdx"; + + + +import android9 from "/src/fragments/lib/graphqlapi/android/advanced-workflows/50_interceptor.mdx"; + + + +import flutter8 from "/src/fragments/lib/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx"; + + diff --git a/src/fragments/lib-legacy/graphqlapi/native_common/authz/common.mdx b/src/fragments/lib-legacy/graphqlapi/native_common/authz/common.mdx new file mode 100644 index 00000000000..b1585fb14b2 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/native_common/authz/common.mdx @@ -0,0 +1,327 @@ +For client authorization AppSync supports API Keys, Amazon IAM credentials, Amazon Cognito User Pools, and 3rd party OIDC providers. This is inferred from the `amplifyconfiguration.json`/`.dart` file when you call `Amplify.configure()`. You can configure auth modes for an API using the Amplify CLI or manual configuration. + +## Auth Modes + +### API key + +API Key is the easiest way to setup and prototype your application with AWS AppSync. This means it is also prone to abuse since anyone can easily discover the API Key and make requests to your public service. To have authorization checks, use the other auth modes such as Cognito user pool or AWS IAM. API Key will expiry according to the expiry time set when provisioning AWS AppSync and will require extending it or creating a new one if needed. Default API Key expiry time is 7 days. + +### Amazon Cognito User Pools + +Amazon Cognito User Pools is most commonly used with AWS AppSync when adding authorization check on your API calls. If your application needs to interact with other AWS services besides AWS AppSync, such as Amazon S3, you will need to use AWS IAM credentials with Amazon Cognito Identity Pools. Amplify CLI can automatically configure this for you and will also automatically use the authenticated user from User Pools to federate with the Identity Pools to provide the AWS IAM credentials in the application. [See this for more information about the differences](https://aws.amazon.com/premiumsupport/knowledge-center/cognito-user-pools-identity-pools/). This allows you to have both User Pools' credentials for AWS AppSync and AWS IAM credentials for other AWS resources. You can learn more about Amplify Auth outlined in the [Accessing credentials section](/lib/auth/access_credentials). + +### IAM + +Amazon Cognito Identity Pools allows you to use credentials from AWS IAM in your app. AWS IAM helps you securely control access to AWS resources. You use IAM to control who is authenticated (signed in) and authorized (has permissions) to use AWS resources. [Learn more about IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html?icmpid=docs_iam_console) . The Amplify CLI can automatically configure this for you. + +### OpenID Connect (OIDC) + +If you are using a 3rd party OIDC provider you will need to configure it and manage the details of token refreshes yourself. + +### AWS Lambda + +You can implement your own custom API authorization logic using an AWS Lambda function. To add a Lambda function as an authorization mode for your AppSync API, go to the **Settings** section of the **AppSync console**. + +You will need to manage the details of token refreshes in your application code yourself. + +## Use Amplify CLI to configure authorization modes + +Amplify CLI can automatically configure the auth modes for you when running `amplify add api` or `amplify update api` if you want to change the auth mode. + +If you already have auth configured, then you need to run `amplify update api` to use this pre-configured auth mode and CLI will not ask for auth settings again. + +```bash +amplify update api +``` + +```console +? Please select from one of the below mentioned services: + > `GraphQL` +? Select a setting to edit: + > `Authorization modes` +? Choose the default authorization type for the API + API key + Amazon Cognito User Pool +❯ IAM + OpenID Connect +``` + +## Manual Configuration + +### API Key + +Add the following snippet to your `amplifyconfiguration.json`/`.dart` file, under the `awsAPIPlugin`: + +```json +{ + ... + "awsAPIPlugin": { + "[YOUR-GRAPHQLENDPOINT-NAME]": { + "endpointType": "GraphQL", + "endpoint": "[GRAPHQL-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "API_KEY", + "apiKey": "[API-KEY]" + } + } +} +``` + +### Amazon Cognito User Pools + +Add the following snippet to your `amplifyconfiguration.json`/`.dart` file, under the `awsCognitoAuthPlugin`: + +```json +{ + ... + "awsCognitoAuthPlugin": { + "CognitoUserPool": { + "Default": { + "PoolId": "[POOL-ID]", + "AppClientId": "[APP-CLIENT-ID]", + "Region": "[REGION]" + } + } + } +} +``` +and under the `awsAPIPlugin` +```json +{ + ... + "awsAPIPlugin": { + "[YOUR-GRAPHQLENDPOINT-NAME]": { + "endpointType": "GraphQL", + "endpoint": "[GRAPHQL-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "AMAZON_COGNITO_USER_POOLS", + } + } +} + +``` + +import ios0 from "/src/fragments/lib/graphqlapi/ios/authz/10_userpool.mdx"; + + + +import android1 from "/src/fragments/lib/graphqlapi/android/authz/10_userpool.mdx"; + + + +import flutter2 from "/src/fragments/lib/graphqlapi/flutter/authz/10_userpool.mdx"; + + + +### IAM + +Add the following snippet to your `amplifyconfiguration.json`/`.dart` file: + +```json +{ + ... + "awsCognitoAuthPlugin": { + "CredentialsProvider": { + "CognitoIdentity": { + "Default": { + "PoolId": "[COGNITO-IDENTITY-POOLID]", + "Region": "[REGION]" + } + } + } + } +} +``` +and under the `awsAPIPlugin` +```json +{ + ... + "awsAPIPlugin": { + "[YOUR-GRAPHQLENDPOINT-NAME]": { + "endpointType": "GraphQL", + "endpoint": "[GRAPHQL-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "AWS_IAM", + } + } +} +``` + +### OIDC + +Update the `amplifyconfiguration.json`/`.dart` file and code snippet as follows: + +```json +{ + ... + "awsAPIPlugin": { + "[YOUR-GRAPHQLENDPOINT-NAME]": { + "endpointType": "GraphQL", + "endpoint": "[GRAPHQL-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "OPENID_CONNECT", + } + } +} +``` + +import ios3 from "/src/fragments/lib/graphqlapi/ios/authz/20_oidc.mdx"; + + + +import android4 from "/src/fragments/lib/graphqlapi/android/authz/20_oidc.mdx"; + + + +import flutter5 from "/src/fragments/lib/graphqlapi/flutter/authz/20_oidc.mdx"; + + + +If you are using Cognito's user pool as the authorization type, this will by default retrieve and use the Access Token for your requests. If you would like to override this behavior and use the ID Token instead, you can treat Cognito user pool as your OIDC provider and use `Amplify.Auth` to retrieve the ID Token for your requests. + +import ios6 from "/src/fragments/lib/graphqlapi/ios/authz/21_oidc.mdx"; + + + +import android7 from "/src/fragments/lib/graphqlapi/android/authz/21_oidc.mdx"; + + + +import flutter8 from "/src/fragments/lib/graphqlapi/flutter/authz/21_oidc.mdx"; + + + +### AWS Lambda + +Amplify CLI does not currently allow you to configure Lambda as an authorization mode for your GraphQL API. To add a Lambda function as an authorization mode for your AppSync API, go to the **Settings** section of the **AppSync console**. Then, update the `authorizationType` value in the `amplifyconfiguration.json`/`.dart` file and code snippet as follows: + +```json +{ + ... + "awsAPIPlugin": { + "[YOUR-GRAPHQLENDPOINT-NAME]": { + "endpointType": "GraphQL", + "endpoint": "[GRAPHQL-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "AWS_LAMBDA", + } + } +} +``` + +import ios9 from "/src/fragments/lib/graphqlapi/ios/authz/22_lambda.mdx"; + + + +import android10 from "/src/fragments/lib/graphqlapi/android/authz/22_lambda.mdx"; + + + +import flutter11 from "/src/fragments/lib/graphqlapi/flutter/authz/22_lambda.mdx"; + + + +### NONE +You can also set authorization mode to `NONE` so that the library will not provide any request interception logic. You can use this when your API does not require any authorization or when you want to manipulate the request yourself, such as adding header values or authorization data. + +```json +{ + ... + "awsAPIPlugin": { + "[YOUR-GRAPHQLENDPOINT-NAME]": { + "endpointType": "GraphQL", + "endpoint": "[GRAPHQL-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "NONE", + } + } +} +``` + +You can register your own request interceptor to intercept the request and perform an action or inject something into your request before it is performed. + +import ios12 from "/src/fragments/lib/graphqlapi/ios/advanced-workflows/50_interceptor.mdx"; + + + +import android13 from "/src/fragments/lib/graphqlapi/android/advanced-workflows/50_interceptor.mdx"; + + + +import flutter14 from "/src/fragments/lib/graphqlapi/flutter/advanced-workflows/50_interceptor.mdx"; + + + +## Configure multiple authorization modes + + + +There is currently [a known issue](https://github.com/aws-amplify/amplify-flutter/issues/1867) where enabling multi-auth modes for the API plugin may break DataStore functionality if the DataStore plugin is also used in the same App. + +If you are planning to enable multi-auth for _only_ the DataStore plugin, please refer to the [configure multiple authorization types](/lib/datastore/setup-auth-rules/#configure-multiple-authorization-types) section in DataStore documentation. + +If you are planning to enable multi-auth for both the API and DataStore plugins, please monitor the aforementioned issue for the latest progress and updates. + + + +This section talks about the capability of AWS AppSync to configure multiple authorization modes for a single AWS AppSync endpoint and region. Follow the [AWS AppSync Multi-Auth](https://docs.aws.amazon.com/appsync/latest/devguide/security.html#using-additional-authorization-modes) to configure multiple authorization modes for your AWS AppSync endpoint. + +You can now configure a single GraphQL API to deliver private and public data. Private data requires authenticated access using authorization mechanisms such as IAM, Cognito User Pools, and OIDC. Public data does not require authenticated access and is delivered through authorization mechanisms such as API Keys. You can also configure a single GraphQL API to deliver private data using more than one authorization type. For example, you can configure your GraphQL API to authorize some schema fields using OIDC, while other schema fields through Cognito User Pools and/or IAM. + +As discussed in the above linked documentation, certain fields may be protected by different authorization types. This can lead the same query, mutation, or subscription to have different responses based on the authorization sent with the request; Therefore, it is recommended to use the different `friendly_name_` as the `apiName` parameter in the `Amplify.API` call to reference each authorization type. + +The following snippets highlight the new values in the `amplifyconfiguration.json`/`.dart` and the client code configurations. + +The `friendly_name` illustrated here is created from Amplify CLI prompt. There are 4 clients in this configuration that connect to the same API except that they use different `AuthMode`. + +```json +{ + "UserAgent": "aws-amplify-cli/2.0", + "Version": "1.0", + "api": { + "plugins": { + "awsAPIPlugin": { + "[FRIENDLY-NAME-API-WITH-API-KEY]": { + "endpointType": "GraphQL", + "endpoint": "[GRAPHQL-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "API_KEY", + "apiKey": "[API_KEY]" + }, + "[FRIENDLY-NAME-API-WITH-IAM]": { + "endpointType": "GraphQL", + "endpoint": "[GRAPHQL-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "AWS_IAM", + }, + "[FRIENDLY-NAME-API-WITH-USER-POOLS]": { + "endpointType": "GraphQL", + "endpoint": "https://xyz.appsync-api.us-west-2.amazonaws.com/graphql", + "region": "[REGION]", + "authorizationType": "AMAZON_COGNITO_USER_POOLS", + }, + "[FRIENDLY-NAME-API-WITH-OPENID-CONNECT]": { + "endpointType": "GraphQL", + "endpoint": "https://xyz.appsync-api.us-west-2.amazonaws.com/graphql", + "region": "[REGION]", + "authorizationType": "OPENID_CONNECT", + } + } + } + } +} +``` + +The `GRAPHQL-ENDPOINT` from AWS AppSync will look similar to `https://xyz.appsync-api.us-west-2.amazonaws.com/graphql`. + +import ios15 from "/src/fragments/lib/graphqlapi/ios/authz/30_multi.mdx"; + + + +import android16 from "/src/fragments/lib/graphqlapi/android/authz/30_multi.mdx"; + + + +import flutter17 from "/src/fragments/lib/graphqlapi/flutter/authz/30_multi.mdx"; + + diff --git a/src/fragments/lib-legacy/graphqlapi/native_common/concepts.mdx b/src/fragments/lib-legacy/graphqlapi/native_common/concepts.mdx new file mode 100644 index 00000000000..4d3ffb626c7 --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/native_common/concepts.mdx @@ -0,0 +1,30 @@ + +## AWS AppSync + +The Amplify Framework uses AWS AppSync, a managed service that uses GraphQL to make it easy for applications to get exactly the data they need. With AppSync, you can build scalable applications, including those requiring real-time updates, on a range of data sources such as NoSQL data stores, relational databases, HTTP APIs, and your custom data sources with AWS Lambda. + +For mobile and web apps, AppSync additionally provides SDKs that support local data access when devices go offline, and data synchronization with customizable conflict resolution, when they are back online. + +### The API Category + +The API category provides a solution for making HTTP requests to both GraphQL as well as REST endpoints. It includes a [AWS Signature Version 4](http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) signer class which automatically signs all AWS API requests for you as well as methods to use API Keys, Amazon Cognito User Pools, or 3rd party OIDC providers. + +The AWS Amplify API module supports AWS AppSync or any other GraphQL backends. + + + +To learn more about GraphQL, please visit the [GraphQL website](http://graphql.org/learn/). + + + +## Using AWS AppSync + +AWS AppSync helps you build data-driven apps with real-time and offline capabilities. Learn more about [AWS AppSync](https://aws.amazon.com/appsync/) by visiting [AWS AppSync Developer Guide](https://docs.aws.amazon.com/appsync/latest/devguide/welcome.html). + +The Amplify Framework offers three SDK options for AppSync. + +__[Amplify GraphQL client](/lib/graphqlapi/query-data)__ - a light weight option if you're looking for a simple way to leverage GraphQL features and do not need the offline capabilities or caching. If you need those features, please look at [Amplify DataStore](/lib/datastore/getting-started). + +__[Amplify DataStore](/lib/datastore/getting-started)__ - makes it easy to build apps that need to support offline and low-latency scenarios. DataStore also makes working with distributed, cross-user data just as simple as working with local-only data by providing a programming model for leveraging shared and distributed data without writing additional code. + +The Amplify CLI provides support for AppSync that make this process easy. Using the CLI, you can configure an AWS AppSync API, download required client side configuration files, and generate client side code within minutes by running a few simple commands on the command line. \ No newline at end of file diff --git a/src/fragments/lib-legacy/graphqlapi/native_common/getting-started/common.mdx b/src/fragments/lib-legacy/graphqlapi/native_common/getting-started/common.mdx new file mode 100644 index 00000000000..215e5ee286a --- /dev/null +++ b/src/fragments/lib-legacy/graphqlapi/native_common/getting-started/common.mdx @@ -0,0 +1,146 @@ + + +The Amplify API category provides an interface for retrieving and persisting your model data. The API category comes with default built-in support for AWS AppSync. The Amplify CLI allows you to define your API and provision a GraphQL service with CRUD operations and real-time functionality. + +## Goal + +To setup and configure your application with Amplify API to save items in the backend. + +## Prerequisites + +import ios1 from "/src/fragments/lib/graphqlapi/ios/getting-started/10_preReq.mdx"; + + + +import android2 from "/src/fragments/lib/graphqlapi/android/getting-started/10_preReq.mdx"; + + + +import flutter3 from "/src/fragments/lib/graphqlapi/flutter/getting-started/10_preReq.mdx"; + + + +## Configure API + +To start provisioning API resources in the backend, go to your project directory and **execute the command**: + +```bash +amplify add api +``` + +Enter the following when prompted: +```console +? Please select from one of the below mentioned services: + `GraphQL` +# The part below will show you some options you can change, if you wish to change any of them you can navigate with +# your arrow keys and update any field, otherwise you can click on `Continue` to move on +? Here is the GraphQL API that we will create. Select a setting to edit or continue + `Continue` +? Choose a schema template: + `Single object with fields (e.g., “Todo” with ID, name, description)` +? Do you want to edit the schema now? + `No` +``` + + + +**Troubleshooting:** The AWS API plugins do not support conflict detection. If AppSync returns errors about missing `_version` and `_lastChangedAt` fields, or unhandled conflicts, disable **conflict detection**. Run `amplify update api`, and choose **Disable conflict detection**. If you started with the Getting Started guide, which introduces DataStore, this step is required. + + + +The guided schema creation will output `amplify/backend/api/{api_name}/schema.graphql` containing the following: +```graphql +type Todo @model { + id: ID! + name: String! + description: String +} +``` + +To push your changes to the cloud, **execute the command**: + +```bash +amplify push +``` + +import ios4 from "/src/fragments/lib/graphqlapi/ios/getting-started/12_amplifyConfig.mdx"; + + + +import android5 from "/src/fragments/lib/graphqlapi/android/getting-started/12_amplifyConfig.mdx"; + + + +import flutter6 from "/src/fragments/lib/graphqlapi/flutter/getting-started/12_amplifyConfig.mdx"; + + + +import ios7 from "/src/fragments/lib/graphqlapi/ios/getting-started/40_codegen.mdx"; + + + +import android8 from "/src/fragments/lib/graphqlapi/android/getting-started/40_codegen.mdx"; + + + +import flutter7 from "/src/fragments/lib/graphqlapi/flutter/getting-started/40_codegen.mdx"; + + + +## Install Amplify Libraries + +import ios9 from "/src/fragments/lib/graphqlapi/ios/getting-started/20_installLib.mdx"; + + + +import android10 from "/src/fragments/lib/graphqlapi/android/getting-started/20_installLib.mdx"; + + + +import flutter11 from "/src/fragments/lib/graphqlapi/flutter/getting-started/20_installLib.mdx"; + + + +## Initialize Amplify API + +import ios12 from "/src/fragments/lib/graphqlapi/ios/getting-started/30_initapi.mdx"; + + + +import android13 from "/src/fragments/lib/graphqlapi/android/getting-started/30_initapi.mdx"; + + + +import flutter14 from "/src/fragments/lib/graphqlapi/flutter/getting-started/30_initapi.mdx"; + + + +## Create a Todo + +import ios15 from "/src/fragments/lib/graphqlapi/ios/getting-started/50_createtodo.mdx"; + + + +import android16 from "/src/fragments/lib/graphqlapi/android/getting-started/50_createtodo.mdx"; + + + +import flutter17 from "/src/fragments/lib/graphqlapi/flutter/getting-started/50_createtodo.mdx"; + + + +Upon successfully executing this code, you should see an instance of `todo` persisted in your dynamoDB table. + +To navigate to your backend, run `amplify console api` and choose `GraphQL`. This will open the AppSync console to your GraphQL service. Select `Data Sources` and select the Resource link in your `TodoTable` to bring you to the DynamoDB Console. Select the `items` tab to see the `Todo` object that has been persisted in your database. + +## Next steps + +Congratulations! You've created a `Todo` object in your database. Check out the following links to see other Amplify API use cases: + +* [Fetch data](/lib/graphqlapi/query-data) +* [Update data](/lib/graphqlapi/mutate-data) +* [Subscribe to data](/lib/graphqlapi/subscribe-data) +* [Concepts](/lib/graphqlapi/concepts) +* [Configure authorization modes](/lib/graphqlapi/authz) + + diff --git a/src/fragments/lib-legacy/in-app-messaging/js/clear-messages.mdx b/src/fragments/lib-legacy/in-app-messaging/js/clear-messages.mdx new file mode 100644 index 00000000000..59224558adb --- /dev/null +++ b/src/fragments/lib-legacy/in-app-messaging/js/clear-messages.mdx @@ -0,0 +1,12 @@ +Once messages have been synced to your user’s device, `InAppMessaging.clearMessages()` can be used to clear the synced messages. + +```js +await InAppMessaging.clearMessages(); +``` + + + NOTE: If your app has authentication implemented, we recommend calling + InAppMessaging.clearMessages() in between user log-ins to remove messages + targeted for specific user segments. This is especially important if + you anticipate your application will be used in shared device scenarios. + diff --git a/src/fragments/lib-legacy/in-app-messaging/js/create-campaign.mdx b/src/fragments/lib-legacy/in-app-messaging/js/create-campaign.mdx new file mode 100644 index 00000000000..4bc4e33550f --- /dev/null +++ b/src/fragments/lib-legacy/in-app-messaging/js/create-campaign.mdx @@ -0,0 +1,60 @@ +A campaign is a messaging initiative that engages a specific audience [segment](https://docs.aws.amazon.com/pinpoint/latest/userguide/segments.html). A campaign sends tailored messages according to a schedule that you define. You can use the AWS console to create a campaign that sends messages through any single channel that is supported by Amazon Pinpoint: Mobile Push, In-App, Email, SMS or Custom channels. [Learn how to create a campaign using Amazon Pinpoint](https://docs.aws.amazon.com/pinpoint/latest/userguide/campaigns.html) to continue integrating in-app messages in your app with Amplify. + +1. Login to the [AWS Console](https://console.aws.amazon.com/console/home), and Search for **Pinpoint**. +2. Click on your project from the list of available project. Your project name would have been setup with the CLI as your pinpoint resource name when the `amplify add analytics` command was used. +3. Click on **Campaigns** from the left navigation menu, and then click on **Create a campaign** + + ![In-App-Messaging](/images/lib/in-app-messaging/create-pinpoint-campaign.png) + +4. Add a name to your campaign, and keep the following options as follows and then click Next: + + 1. Campaign type: **Standard campaign** + 2. Channel: **In-App messaging** + 3. set prioritization: **Fairly important** + +5. Click on the **Create a segment** radio button, and add a name for your segment, and then click **Next**. + + 1. You can add as many segments as needed to the campaign. For this quickstart, you can use **Include any audiences** under the _Segment group 1_ section. + 2. You can add a criteria to your segments to ensure that audiences that satisfy that criteria can receive the in-app message. + 3. If you see an error message titled _Segment might include multiple channels_, click **I understand** to proceed. + + ![In-App-Messaging](/images/lib/in-app-messaging/choose-campaign-segment.png) + +6. Click on the **Create a new in-app message** radio button. +7. You have the ability to customize the following attributes of the in-app message: + - **Layout**: Which includes all of the different messaging layout options. + - **Header**: Title of the in-app message, including the text color/alignment. + - **Message**: The body of the Message, including the text color/alignment. + - **Background**: Control the background color of the in-app message. + - **Image URL**: Add an image to be displayed as part of the in-app message body. + - **Primary button**: Allows the addition of a button to add functionality to the in-app message. + - **Secondary button**: Allows the addition of an extra button for additional functionality. + - **Custom Data**: Allows the in-app message to pass additional data to the frontend app once it is triggered by an event. + + + As React Native does not support SVG rendering out of the box, Amplify cannot render SVG images by default. For SVG image support with In-App Messaging a custom UI implementation is required. + + + For this tutorial you can create a simple message as shown below. Customers in your application will see the same message once the event is triggered. + + ![In-App-Messaging](/images/lib/in-app-messaging/in-app-message-details.png) + +8. Once you have finished customizing your in-app message, click on **Next**. +9. Under _Trigger events_, add the name of the analytics trigger that will be sent from your frontend app. + - You have the ability to customize the trigger to allow only certain attributes or metrics that are passed with the analytics event to trigger the in-app message. (Optional) + +![In-App-Messaging](/images/lib/in-app-messaging/campaign-setup.png) + +10. By default, the number of messages shown per session is 1. You can update this threshold during campaign setup. + +![In-App-Messaging](/images/lib/in-app-messaging/campaign-settings.png) + +11. Review your campaign, and then click on **Launch campaign**. + +Your campaign is now setup, and you are ready to start integrating the In-App Messaging functionality into your React-Native app. + + + +Note: Campaign start time must be at least 15 minutes in future. In-app messages can only be synced to local device once the campaign becomes active (Status should be "In Progress" in the campaigns screen of the Pinpoint console). + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/in-app-messaging/js/customize.mdx b/src/fragments/lib-legacy/in-app-messaging/js/customize.mdx new file mode 100644 index 00000000000..2e9984f72b8 --- /dev/null +++ b/src/fragments/lib-legacy/in-app-messaging/js/customize.mdx @@ -0,0 +1,109 @@ +## Integrate Custom Components + +You may provide your own In-App Messaging UI components to override the default Amplify provided UI components by utilizing the `components` prop of `InAppMessagingProvider`. + +```jsx +import { + InAppMessageDisplay, + InAppMessagingProvider +} from 'aws-amplify-react-native'; +import { Home } from './src/Home'; + +const MyBannerMessage = (props) => { + // ...Do something with props +}; + +const App = () => { + return ( + + + + + ); +}; +``` + +Override component keys: + +- `BannerMessage`: override default message Banner UI component (top, middle, and bottom layouts) +- `CarouselMessage`: override default message Carousel UI component +- `FullScreenMessage`: override default message FullScreen UI component +- `ModalMessage`: override default message Modal UI component + + + For developer preview, only the BannerMessage and FullScreenMessage UI + components are provided by Amplify. `CarouselMessage` and `ModalMessage` + components must be manually provided to render Carousel and Modal layouts + + +## Integrate Custom Style + +Custom component styling can be applied to the default In-App Messaging UI components by passing the `style` prop to the `InAppMessagingProvider`, these styles will take precedence over default and in-app message payload style. + +```jsx +import { + InAppMessageDisplay, + InAppMessagingProvider +} from 'aws-amplify-react-native'; +import { Home } from './src/Home'; + +const myInAppMessageStyle = { + BannerMessage: { container: { backgroundColor: 'lightgrey' } }, + FullScreenMessage: { body: { fontSize: 24 } } +}; + +const App = () => { + return ( + + + + + ); +}; +``` + +The following optional UI component style properties can be applied per component: + +- `body`: text style applied to the message body +- `closeIconButton`: view style applied to the close button +- `closeIconColor`: string color value applied to close icon +- `container`: view style to the primary container of the message component +- `header`: text style applied to the message header +- `image`: image style applied to the message image +- `primaryButton`: view (`container`) and text (`text`) style applied to the primary message button +- `secondaryButton`: view (`container`) and text (`text`) style applied to the secondary message button + +## Advanced UI + +In some use cases, you may want to forego the usage of the Amplify default UI handling altogether while still leveraging the Amplify provided In-App Messaging React context and provider component for in-app message context state. This can be achieved by wrapping your application in an `InAppMessagingProvider` and utilizing the `useInAppMessaging` hook to expose the values of the `InAppMessagingContext`. + +```jsx +import { + InAppMessagingProvider, + useInAppMessaging +} from 'aws-amplify-react-native'; +import { Home } from './src/Home'; + +const MyInAppMessageDisplay = () => { + const { inAppMessage } = useInAppMessaging(); + + // Do something with inAppMessage +}; + +const App = () => { + return ( + + + + + ); +}; +``` + +`useInAppMessaging` exposes the following functions and values of the `InAppMessagingContext`: + +- `clearInAppMessage`: removes the current in-app message (if any) from context state +- `components`: custom UI components passed to the `InAppMessagingProvider` +- `displayInAppMessage`: manually render a local in-app message +- `inAppMessage`: current in-app message (if any) loaded in context state +- `style`: custom style passed to the `InAppMessagingProvider` diff --git a/src/fragments/lib-legacy/in-app-messaging/js/display-message.mdx b/src/fragments/lib-legacy/in-app-messaging/js/display-message.mdx new file mode 100644 index 00000000000..f0ab728b9e1 --- /dev/null +++ b/src/fragments/lib-legacy/in-app-messaging/js/display-message.mdx @@ -0,0 +1,33 @@ +In-app messages are displayed when an In-App Messaging or analytics event is sent and matches the criteria set forth by your active In-App Messaging campaigns. + +## Analytics event + +Now that messages have been synced to your users’ devices, Amplify In-App Messaging will allow you to start displaying them with Amplify Analytics events with no additional integration steps. Any events you record or are already recording using the `Analytics.record` API are automatically picked up and processed by In-App Messaging. If the event matches the attributes and criteria defined in an in-app message, that message will be displayed. + +```js +const event = { + name: 'first_event', + attributes: { color: 'red' }, + metrics: { quantity: 10 } +}; + +Analytics.record(event); +``` + +If the event name, attributes, and metrics match those set forth by one of your In-App Messaging campaigns, you should see the in-app message displayed in your app. + +## In-App Messaging event + +In addition to or instead of Amplify Analytics events, you can also dispatch In-App Messaging events to trigger an in-app message display programmatically. + +```js +const event = { + name: 'first_event', + attributes: { color: 'red' }, + metrics: { quantity: 10 } +}; + +InAppMessaging.dispatchEvent(event); +``` + +If the event name, attributes, and metrics match those set forth by one of your In-App Messaging campaigns, you should see the in-app message displayed in your app. diff --git a/src/fragments/lib-legacy/in-app-messaging/js/getting-started.mdx b/src/fragments/lib-legacy/in-app-messaging/js/getting-started.mdx new file mode 100644 index 00000000000..ce427071105 --- /dev/null +++ b/src/fragments/lib-legacy/in-app-messaging/js/getting-started.mdx @@ -0,0 +1,126 @@ +Make sure you have completed the below steps: + +- [Completed the prerequisite steps.](/lib/in-app-messaging/prerequisites) +- [Created a campaign from the pinpoint console.](/lib/in-app-messaging/create-campaign) + +## Integrate your application + +In your application directory, you should first install the necessary dependencies. Although Amplify In-App Messaging can be used as a standalone JavaScript library, the UI integration is currently available only for _React Native_. The integrations steps here assume a React Native application as the implementation target. + +```bash +npm install -E aws-amplify@in-app-messaging aws-amplify-react-native@in-app-messaging amazon-cognito-identity-js @react-native-community/netinfo @react-native-async-storage/async-storage @react-native-picker/picker react-native-get-random-values react-native-url-polyfill +``` + +You will also need to install pod dependencies for `iOS`. Navigate to the `iOS` directory in your project and run the install command. + +```bash +pod install +``` + +The AWS SDK requires React Native applications to polyfill `crypto.getRandomValues` and `URL`. Import it at the top of your application's root entry point (in most React Native apps this will be the top level `index.js`). + +```js +// Example index.js +import 'react-native-get-random-values'; +import 'react-native-url-polyfill/auto'; + +import { AppRegistry } from 'react-native'; +import App from './App'; +import { name as appName } from './app.json'; + +AppRegistry.registerComponent(appName, () => App); +``` + +Next, you should import and load the `aws-exports.js` configuration file created and modified in an earlier step into your application. Add the Amplify configuration step to your application’s root entry point. For example, `index.js`. + +```js +import { Amplify } from 'aws-amplify'; +import awsconfig from './src/aws-exports'; + +Amplify.configure(awsconfig); +``` + +Finally, integrate the Amplify React Native UI components for In-App Messaging in your application’s root component. For example, `App.js`. + +```jsx +import { + InAppMessagingProvider, + InAppMessageDisplay +} from 'aws-amplify-react-native'; + +const App = () => ( + + {/* Your application */} + + +); +``` + +Now your application is set up with Amplify In-App Messaging. To interact with Amplify In-App Messaging APIs, you will first need to import the `Notifications` category. + +```js +import { Notifications } from 'aws-amplify'; +``` + +The In-App Messaging feature is a subcategory of Notifications. To make it easier to access throughout your application, you can de-structure it. + +```js +const { InAppMessaging } = Notifications; +``` + +Below is an example of what your `App.jsx` file should look like: + +```jsx +import React, { useEffect } from 'react'; +import { SafeAreaView, Button } from 'react-native'; +import { Analytics, Notifications } from 'aws-amplify'; +import { + InAppMessagingProvider, + InAppMessageDisplay +} from 'aws-amplify-react-native'; + +const { InAppMessaging } = Notifications; + +// To display your in-app message, make sure this event name matches one you created +// in an In-App Messaging campaign! +const myFirstEvent = { name: 'my_first_event' }; + +const App = () => { + useEffect(() => { + // Messages from your campaigns need to be synced from the backend before they + // can be displayed. You can trigger this anywhere in your app. Here we are + // syncing just once when this component (your app) renders for the first time. + InAppMessaging.syncMessages(); + }, []); + + return ( + + + {/* This button has an example of an analytics event triggering the in-app message. */} +
+
+ ); +} + +function EntityIdentification() { + const [response, setResponse] = useState("Click upload for test ") + const [src, setSrc] = useState(""); + + function identifyFromFile(event) { + setResponse('searching...'); + + const { target: { files } } = event; + const [file,] = files || []; + + if (!file) { + return; + } + Predictions.identify({ + entities: { + source: { + file, + }, + /**For using the Identify Entities advanced features, enable collection:true and comment out celebrityDetection + * Then after you upload a face with PredictionsUpload you'll be able to run this again + * and it will tell you if the photo you're testing is in that Collection or not and display it*/ + //collection: true + celebrityDetection: true + } + }).then(result => { + console.log(result); + const entities = result.entities; + let imageId = "" + let names = "" + entities.forEach(({ boundingBox, metadata: { name = "", externalImageId = "" } }) => { + const { + width, // ratio of overall image width + height, // ratio of overall image height + left, // left coordinate as a ratio of overall image width + top // top coordinate as a ratio of overall image height + } = boundingBox; + imageId = externalImageId; + if (name) { + names += name + " ."; + } + console.log({ name }); + }) + if (imageId) { + Storage.get("", { + customPrefix: { + public: imageId + }, + level: "public", + }).then(setSrc); // this should be better but it works + } + console.log({ entities }); + setResponse(names); + }) + .catch(err => console.log(err)) + } + + return ( +
+
+

Entity identification

+ +

{response}

+ { src && } +
+
+ ); +} + +function PredictionsUpload() { + /* This is Identify Entities Advanced feature + * This will upload user images to the appropriate bucket prefix + * and a Lambda trigger will automatically perform indexing + */ + function upload(event) { + const { target: { files } } = event; + const [file,] = files || []; + Storage.put(file.name, file, { + level: 'protected', + customPrefix: { + protected: 'protected/predictions/index-faces/', + } + }); + } + + return ( +
+
+

Upload to predictions s3

+ +
+
+ ); +} + +function LabelsIdentification() { + const [response, setResponse] = useState("Click upload for test ") + + function identifyFromFile(event) { + const { target: { files } } = event; + const [file,] = files || []; + + if (!file) { + return; + } + Predictions.identify({ + labels: { + source: { + file, + }, + type: "ALL" // "LABELS" will detect objects , "UNSAFE" will detect if content is not safe, "ALL" will do both default on aws-exports.js + } + }).then(result => setResponse(JSON.stringify(result, null, 2))) + .catch(err => setResponse(JSON.stringify(err, null, 2))) + } + + return ( +
+
+

Labels identification

+ +

{response}

+
+
+ ); +} + +function SpeechToText(props) { + const [response, setResponse] = useState("Press 'start recording' to begin your transcription. Press STOP recording once you finish speaking.") + + function AudioRecorder(props) { + const [recording, setRecording] = useState(false); + const [micStream, setMicStream] = useState(); + const [audioBuffer] = useState( + (function() { + let buffer = []; + function add(raw) { + buffer = buffer.concat(...raw); + return buffer; + } + function newBuffer() { + console.log("resetting buffer"); + buffer = []; + } + + return { + reset: function() { + newBuffer(); + }, + addData: function(raw) { + return add(raw); + }, + getData: function() { + return buffer; + } + }; + })() + ); + + async function startRecording() { + console.log('start recording'); + audioBuffer.reset(); + + window.navigator.mediaDevices.getUserMedia({ video: false, audio: true }).then((stream) => { + const startMic = new mic(); + + startMic.setStream(stream); + startMic.on('data', (chunk) => { + var raw = mic.toRaw(chunk); + if (raw == null) { + return; + } + audioBuffer.addData(raw); + + }); + + setRecording(true); + setMicStream(startMic); + }); + } + + async function stopRecording() { + console.log('stop recording'); + const { finishRecording } = props; + + micStream.stop(); + setMicStream(null); + setRecording(false); + + const resultBuffer = audioBuffer.getData(); + + if (typeof finishRecording === "function") { + finishRecording(resultBuffer); + } + + } + + return ( +
+
+ {recording && } + {!recording && } +
+
+ ); + } + + function convertFromBuffer(bytes) { + setResponse('Converting text...'); + + Predictions.convert({ + transcription: { + source: { + bytes + }, + // language: "en-US", // other options are "en-GB", "fr-FR", "fr-CA", "es-US" + }, + }).then(({ transcription: { fullText } }) => setResponse(fullText)) + .catch(err => setResponse(JSON.stringify(err, null, 2))) + } + + return ( +
+
+

Speech to text

+ +

{response}

+
+
+ ); +} + +function TextToSpeech() { + const [response, setResponse] = useState("...") + const [textToGenerateSpeech, setTextToGenerateSpeech] = useState("write to speech"); + + function generateTextToSpeech() { + setResponse('Generating audio...'); + Predictions.convert({ + textToSpeech: { + source: { + text: textToGenerateSpeech, + }, + voiceId: "Amy" // default configured on aws-exports.js + // list of different options are here https://docs.aws.amazon.com/polly/latest/dg/voicelist.html + } + }).then(result => { + let AudioContext = window.AudioContext || window.webkitAudioContext; + console.log({ AudioContext }); + const audioCtx = new AudioContext(); + const source = audioCtx.createBufferSource(); + audioCtx.decodeAudioData(result.audioStream, (buffer) => { + + source.buffer = buffer; + source.connect(audioCtx.destination); + source.start(0); + }, (err) => console.log({err})); + + setResponse(`Generation completed, press play`); + }) + .catch(err => setResponse(err)) + } + + function setText(event) { + setTextToGenerateSpeech(event.target.value); + } + + return ( +
+
+

Text To Speech

+ + +

{response}

+
+
+ ); +} + +function TextTranslation() { + const [response, setResponse] = useState("Input some text and click enter to test") + const [textToTranslate, setTextToTranslate] = useState("write to translate"); + + function translate() { + Predictions.convert({ + translateText: { + source: { + text: textToTranslate, + // language : "es" // defaults configured on aws-exports.js + // supported languages https://docs.aws.amazon.com/translate/latest/dg/how-it-works.html#how-it-works-language-codes + }, + // targetLanguage: "en" + } + }).then(result => setResponse(JSON.stringify(result, null, 2))) + .catch(err => setResponse(JSON.stringify(err, null, 2))) + } + + function setText(event) { + setTextToTranslate(event.target.value); + } + + return ( +
+
+

Text Translation

+ + +

{response}

+
+
+ ); +} + +function TextInterpretation() { + const [response, setResponse] = useState("Input some text and click enter to test") + const [textToInterpret, setTextToInterpret] = useState("write some text here to interpret"); + + function interpretFromPredictions() { + Predictions.interpret({ + text: { + source: { + text: textToInterpret, + }, + type: "ALL" + } + }).then(result => setResponse(JSON.stringify(result, null, 2))) + .catch(err => setResponse(JSON.stringify(err, null, 2))) + } + + function setText(event) { + setTextToInterpret(event.target.value); + } + + return ( +
+
+

Text interpretation

+ + +

{response}

+
+
+ ); +} + +function App() { + return ( +
+ Translate Text + +
+ Speech Generation + +
+ Transcribe Audio + +
+ Identify Text + +
+ Identify Entities + +
+ Identify Entities (Advanced) + +
+ Label Objects + +
+ Text Interpretation + +
+ ); +} + +export default App; +``` + +Now run `yarn start` and press the buttons to demo the app. + +## Sample Ionic app + +First, be sure you have the latest Ionic CLI installed, then generate a new app (for this example you can use any template, but it's simplest to start with the Blank template to start): + +```bash +npm i -g ionic +ionic start predictions blank # the first argument is your project name, the second the template +``` + +Update the `src/polyfills.ts` and add to the top of the file `(window as any).global = window;`. Then, update the `src/tsconfig.app.json` file and add the "node" types: + +``` +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "types": ["node"] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} + +``` + +`src/app/home/home.page.ts` + +```javascript +import { Component } from '@angular/core'; +import Predictions from '@aws-amplify/predictions'; +import { TextToSpeechOutput } from '@aws-amplify/predictions/lib/types'; + +@Component({ + selector: 'app-home', + templateUrl: 'home.page.html', + styleUrls: ['home.page.scss'], +}) +export class HomePage { + + public textToTranslate = "Hello Amplify"; + public translateResult = ""; + public srcLang = "en"; + public targetLang = "de"; + public voiceId = "Salli"; + public speechUrl:string; + public speakResult:boolean; + + constructor() {} + + public async translate() { + const result = await Predictions.convert({ + translateText: { + source: { + text: this.textToTranslate, + language : this.srcLang + }, + targetLanguage: this.targetLang + } + }); + this.translateResult = result.text || "Error"; + if (this.speakResult) { + this.generateSpeech(result.text); + } + } + + public async generateSpeech(textToGenerateSpeech: string) { + const result:TextToSpeechOutput = await Predictions.convert({ + textToSpeech: { + source: { + text: textToGenerateSpeech, + }, + voiceId: this.voiceId + } + }); + const audioCtx = new ((window as any).AudioContext || (window as any).webkitAudioContext)(); + const source = audioCtx.createBufferSource(); + audioCtx.decodeAudioData(result.audioStream, (buffer) => { + source.buffer = buffer; + source.connect(audioCtx.destination); + source.start(audioCtx.currentTime); + }, (err) => console.log({err})); + } + + public selectSource(value: string) { + this.srcLang = value; + } + + public selectTarget(value: string) { + this.targetLang = value; + } + +} + +``` + +#### `src/app/home/home.page.html` + +```html + + + + Amplify Predictions + + + + + + + + Convert + Translate languages + + + + + + Source Language + + English + Spanish + German + Norwegian + + + + + Target Language + + English + Spanish + German + Norwegian + + + + + + + + + + + + + Translate + + +   + Speak Result + + + + + + + + + + + + + + +``` diff --git a/src/fragments/lib-legacy/predictions/js/text-speech.mdx b/src/fragments/lib-legacy/predictions/js/text-speech.mdx new file mode 100644 index 00000000000..4e66e03dfcb --- /dev/null +++ b/src/fragments/lib-legacy/predictions/js/text-speech.mdx @@ -0,0 +1,37 @@ +## Set up the backend + +If you haven't already done so, run `amplify init` inside your project and then `amplify add auth` (we recommend selecting the *default configuration*). + +Run `amplify add predictions` and select **Convert**. Then use the following answers: + +```console +? What would you like to convert? + Convert text into a different language +❯ Convert text to speech + Convert speech to text + Learn More + +? Who should have access? Auth and Guest users +``` + +Now run `amplify push` which will generate your `aws-exports.js` and create resources in the cloud. You can now either add this to your backend or skip and add more features to your app. + +Services used: Amazon Polly + +## Working with the API + +Generate an audio buffer for playback from a text input. + +```javascript +Predictions.convert({ + textToSpeech: { + source: { + text: textToGenerateSpeech + }, + voiceId: "Amy" // default configured on aws-exports.js + // list of different options are here https://docs.aws.amazon.com/polly/latest/dg/voicelist.html + } +}) +.then(result => console.log({ result })) +.catch(err => console.log({ err })); +``` diff --git a/src/fragments/lib-legacy/predictions/js/transcribe.mdx b/src/fragments/lib-legacy/predictions/js/transcribe.mdx new file mode 100644 index 00000000000..bc100ee0b54 --- /dev/null +++ b/src/fragments/lib-legacy/predictions/js/transcribe.mdx @@ -0,0 +1,36 @@ +## Set up the backend + +IIf you haven't already done so, run `amplify init` inside your project and then `amplify add auth` (we recommend selecting the *default configuration*). + +Run `amplify add predictions` and select **Convert**. Then use the following answers: + +```console +? What would you like to convert? + Convert text into a different language + Convert text to speech +❯ Convert speech to text + Learn More + +? Who should have access? Auth and Guest users +``` + +Now run `amplify push` which will generate your `aws-exports.js` and create resources in the cloud. You can now either add this to your backend or skip and add more features to your app. + +Services used: Amazon Transcribe + +## Working with the API + +You can transcribe a PCM Audio byte buffer to Text, such as a recording from microphone. + +```javascript +Predictions.convert({ + transcription: { + source: { + bytes + }, + // language: "en-US", // other options are "en-GB", "fr-FR", "fr-CA", "es-US" + } +}) +.then(({ transcription: { fullText } }) => console.log({ fullText })) +.catch(err => console.log({ err })); +``` diff --git a/src/fragments/lib-legacy/predictions/js/translate.mdx b/src/fragments/lib-legacy/predictions/js/translate.mdx new file mode 100644 index 00000000000..8ddbc64784d --- /dev/null +++ b/src/fragments/lib-legacy/predictions/js/translate.mdx @@ -0,0 +1,36 @@ +## Set up the backend + +If you haven't already done so, run `amplify init` inside your project and then `amplify add auth` (we recommend selecting the *default configuration*). + +Run `amplify add predictions` and select **Convert**. Then use the following answers: + +```console +? What would you like to convert? (Use arrow keys) +❯ Convert text into a different language + Convert text to speech + Convert speech to text + Learn More + +? Who should have access? Auth and Guest users +``` + +Now run `amplify push` which will generate your `aws-exports.js` and create resources in the cloud. You can now either add this to your backend or skip and add more features to your app. + +## Working with the API + +Translate text from one source language to a destination language. + +```javascript +Predictions.convert({ + translateText: { + source: { + text: textToTranslate, + // language : "es" // defaults configured on aws-exports.js + // supported languages https://docs.aws.amazon.com/translate/latest/dg/how-it-works.html#how-it-works-language-codes + }, + // targetLanguage: "en" + } +}) +.then(result => console.log({ result })) +.catch(err => console.log({ err })); +``` diff --git a/src/fragments/lib-legacy/predictions/native_common/getting-started/common.mdx b/src/fragments/lib-legacy/predictions/native_common/getting-started/common.mdx new file mode 100644 index 00000000000..bb4306497c5 --- /dev/null +++ b/src/fragments/lib-legacy/predictions/native_common/getting-started/common.mdx @@ -0,0 +1,112 @@ +The Predictions category enables you to integrate machine learning into your application without any prior machine learning experience. It supports translating text from one language to another, converting text to speech, text recognition from an image, entities recognition, labeling real world objects, interpretation of text, and uploading images for automatic training. This functionality is powered by AWS services including: [Amazon Translate](https://docs.aws.amazon.com/translate/latest/dg/what-is.html), [Amazon Polly](https://docs.aws.amazon.com/polly/latest/dg/what-is.html), [Amazon Transcribe](https://docs.aws.amazon.com/transcribe/latest/dg/what-is-transcribe.html), [Amazon Rekognition](https://docs.aws.amazon.com/rekognition/latest/dg/what-is.html), [Amazon Textract](https://docs.aws.amazon.com/textract/latest/dg/what-is.html), and [Amazon Comprehend](https://docs.aws.amazon.com/comprehend/latest/dg/what-is.html). + +import ios0 from "/src/fragments/lib/predictions/ios/getting-started/10_coreml.mdx"; + + + +## Goal + +To setup and configure your application with Amplify Predictions and go through a simple example of translating text. + +## Prerequisites + +import ios1 from "/src/fragments/lib/predictions/ios/getting-started/20_preReq.mdx"; + + + +import android2 from "/src/fragments/lib/predictions/android/getting-started/20_preReq.mdx"; + + + +## Provision Backend Services + +To use the predictions category we will need to setup the auth backend resources. To provision auth resources in the backend, go to your project directory and **execute the command**: + +```bash +amplify add predictions +``` + +Enter the following when prompted: + +```console +? Please select from one of the categories below + `Convert` +? You need to add auth (Amazon Cognito) to your project in order to add storage for user files. Do you want to add auth now? (Y/n) + `Y` +? Do you want to use the default authentication and security configuration? + `Default configuration` +? How do you want users to be able to sign in? + `Email` +? Do you want to configure advanced settings? + `No, I am done.` +? What would you like to convert? + `Translate text into a different language` +? Provide a friendly name for your resource + `transTextSample` +? What is the source language? + `English` +? What is the target language? + `Italian` +? Who should have access? + ` Auth and Guest users` +``` + +Note that the languages selected during this stage will be the default language your app will translate to/from. These source and target languages can be overridden when we write the code in our application. + +To push your change to the cloud, **execute the command**: + +```bash +amplify push +``` + +Upon completion, `amplifyconfiguration.json` will be updated to reference the newly provisioned backend resources. + +## Install Amplify Libraries + +import ios3 from "/src/fragments/lib/predictions/ios/getting-started/30_installLib.mdx"; + + + +import android4 from "/src/fragments/lib/predictions/android/getting-started/30_installLib.mdx"; + + + +## Initialize Amplify Predictions + +import ios5 from "/src/fragments/lib/predictions/ios/getting-started/40_init.mdx"; + + + +import android6 from "/src/fragments/lib/predictions/android/getting-started/40_init.mdx"; + + + +## Translating text + +To translate text from one language to another, specify the text you want translated, a source language, and a target language. The source and target language parameters will override any choice you made while adding this resource using the Amplify CLI. + +import ios7 from "/src/fragments/lib/predictions/ios/getting-started/50_translate.mdx"; + + + +import android8 from "/src/fragments/lib/predictions/android/getting-started/50_translate.mdx"; + + + +As a result of executing this code, you will see the following printed to your console: + +```console +Me gusta comer espaguetis +``` + +## Next steps + +Congratulations! You've translated text from one language to another. Check out the following links to explore other Amplify Predictions use cases: + +import ios9 from "/src/fragments/lib/predictions/ios/getting-started/60_nextSteps.mdx"; + + + +import android10 from "/src/fragments/lib/predictions/android/getting-started/60_nextSteps.mdx"; + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/project-setup/android/async/async.mdx b/src/fragments/lib-legacy/project-setup/android/async/async.mdx new file mode 100644 index 00000000000..54e41d7f94f --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/android/async/async.mdx @@ -0,0 +1,70 @@ +Most functionalities in Amplify Android are exposed as asynchronous functions. These functions return immediately, returning a result at a later time: + +```kotlin +val post = Post.builder() + .title("My First Post") + .build() + +// Time 0: save() is called. +Amplify.DataStore.save(post, + { + // Time 2, later: a result or error is emitted + Log.i("MyAmplifyApp", "Saved a post") + }, + { + // Time 2, later: a result of error is emitted + Log.e("MyAmplifyApp", "Save failed", it) + } +) +// Time 1: save() yields execution to following lines, +// but no result available, yet. + +``` + +This is a familiar pattern to many Android developers, and so is the default way of interacting with Amplify Android. + +However, this development model has some challenges. Let's say you need to wait for an operation to complete, so that you can perform additional logic that depends on its result. This can quickly become unmaintainable, resulting in a situation known as "callback hell": + +Consider a relational model where the creation of a `Post` also requires the creation of a `User` for the editor, and a `PostEditor` object to link the two together: + +```java +Post post = Post.builder() + .title("My First Post") + .build(); + +User editor = User.builder() + .username("Nadia") + .build(); + +PostEditor postEditor = PostEditor.builder() + .post(post) + .editor(editor) + .build(); +``` + +Using callbacks, you can save these objects via: + +```kotlin +Amplify.DataStore.save(post, + { + Log.i("MyAmplifyApp", "Post saved") + Amplify.DataStore.save(editor, + { + Log.i("MyAmplifyApp", "Editor saved") + Amplify.DataStore.save(postEditor, + { Log.i("MyAmplifyApp", "PostEditor saved") }, + { Log.e("MyAmplifyApp", "PostEditor not saved", it) } + ) + }, + { Log.e("MyAmplifyApp", "Editor not saved", it) } + ) + }, + { Log.e("MyAmplifyApp", "Post not saved", it) } +) +``` + +After three calls, we're no longer writing down the page, we're writing down-and-right. As our program grows, this may become difficult to scale. + +There are a variety of different technologies that aim to solve this particular problem: Promises/Futures, RxJava, Kotlin Coroutines, and more. + +Amplify Android includes optional support for **[Kotlin Coroutines](/lib/project-setup/coroutines)** and **[RxJava](/lib/project-setup/rxjava)**. diff --git a/src/fragments/lib-legacy/project-setup/android/coroutines/coroutines.mdx b/src/fragments/lib-legacy/project-setup/android/coroutines/coroutines.mdx new file mode 100644 index 00000000000..561a6863cc6 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/android/coroutines/coroutines.mdx @@ -0,0 +1,125 @@ +Amplify provides an optional and separate API surface which is entirely focused on using Kotlin's [coroutines](https://developer.android.com/kotlin/coroutines) and [flows](https://developer.android.com/kotlin/flow). + +To use it, import **`Amplify`** facade from `core-kotlin` instead of from `core`. See the Installation notes below for more details. + +With the Coroutines APIs, most Amplify functions are expressed as `suspend` functions. Suspending functions can be launched using one of the [lifecycle-aware coroutine scopes](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope) in the Android Architecture components: + +```kotlin +import com.amplifyframework.kotlin.core.Amplify +// ... + +val post = Post.builder() + .title("My First Post") + .build() + +lifecycleScope.launch { + try { + Amplify.DataStore.save(post) // This is suspending function! + Log.i("AmplifyKotlinDemo", "Saved a post") + } catch (failure: DataStoreException) { + Log.e("AmplifyKotlinDemo", "Save failed", failure) + } +} +``` + +Coroutines can greatly improve the readability of dependent, asynchronous calls. Moreover, you can use scopes, dispatchers, and other Kotlin coroutine primitives to get more control over your execution context. + +Let's consider what happens when we have three dependent operations. We want to save a `Post`, then an `Editor`, and finally a `PostEditor`. With Amplify's coroutines interface, we can write these operations sequentially: + +```kotlin +lifecycleScope.launch { + try { + listOf(post, editor, postEditor) + .forEach { Amplify.DataStore.save(it) } + Log.i("AmplifyKotlinDemo", "Post, Editor, and PostEditor saved") + } catch (failure: DataStoreException) { + Log.e("AmplifyKotlinDemo", "An item failed to save", failure) + } +} +``` + +In Amplify's vanilla APIs, this would have created a large block of code with three nested callbacks. + +## Installation + +Amplify's coroutine support is included in an optional module, `core-kotlin`. + +1. Under **Gradle Scripts**, open **build.gradle (Module: [YourApplicationName])**, and add the following line in `dependencies`: + + ```groovy + dependencies { + // Add the below line in `dependencies` + implementation 'com.amplifyframework:core-kotlin:ANDROID_KOTLIN_VERSION' + } + ``` + +2. Wherever you use the **`Amplify`** facade, import `com.amplifyframework.kotlin.core.Amplify` instead of `com.amplifyframework.core.Amplify`: + + ```java + import com.amplifyframework.kotlin.core.Amplify + ``` + +## Usage + +Amplify tries to map the behavior of our callback-based APIs to Kotlin primitives in an intuitive way. Functions whose callbacks emit a single value (or error) are now expressed as suspending functions, returning the value instead. Functions whose callbacks emit a stream of values will now return Kotlin `Flow`s, instead. + +## Special cases + +Some APIs return an operation which can be cancelled. Examples include realtime subscriptions to an API, and uploading/downloading objects from Storage. + +### API subscriptions + +The API category's `subscribe()` function uses both a suspend function _and_ a Flow. The function suspends until the API subscription is established. Then, it starts emitting values over the Flow. + +```kotlin +lifecycleScope.async { + try { + Amplify.API.subscribe(request) // Suspends until subscription established + .catch { Log.e("AmplifyKotlinDemo", "Error on subscription", it) } + .collect { Log.i("AmplifyKotlinDemo", "Data on subscription = $it") } + } catch (error: ApiException) { + Log.e("AmplifyKotlinDemo", "Failed to establish subscription", error) + } +} +``` + +### Storage upload & download operations + +The Storage category's `downloadFile()` and `uploadFile()` functions are bit more complex. These APIs allow you to observe transfer progress, and also to obtain a result. Progress results are delivered over a Flow, returned from the `progress()` function. Completion events are delivered by a suspending `result()` function. + +```kotlin +// Download +val download = Amplify.Storage.downloadFile(remoteKey, localFile) + +lifecycleScope.async { + download + .progress() + .collect { Log.i("AmplifyKotlinDemo", "Download progress = $it") } +} + +lifecycleScope.async { + try { + val result = download.result() + Log.i("AmplifyKotlinDemo", "Download finished! ${result.file.path}") + } catch (failure: StorageException) { + Log.e("AmplifyKotlinDemo", "Download failed", failure) + } +} + +// Upload +val upload = Amplify.Storage.uploadFile(remoteKey, localFile) + +lifecycleScope.async { + upload + .progress() + .collect { Log.i("AmplifyKotlinDemo", "Upload progress = $it") } +} +lifecycleScope.async { + try { + val result = upload.result() + Log.i("AmplifyKotlinDemo", "Upload finished! ${result.key}") + } catch (failure: StorageException) { + Log.e("AmplifyKotlinDemo", "Upload failed", failure) + } +} +``` diff --git a/src/fragments/lib-legacy/project-setup/android/create-application/10_createProject.mdx b/src/fragments/lib-legacy/project-setup/android/create-application/10_createProject.mdx new file mode 100644 index 00000000000..1be60f95633 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/android/create-application/10_createProject.mdx @@ -0,0 +1,22 @@ +**Open Android Studio.** Select **+ Create New Project.** + +![](/images/lib/getting-started/android/set-up-android-studio-welcome.png) + +In **Select a Project Template**, select **Empty Activity**. Press **Next**. + +![](/images/lib/getting-started/android/set-up-android-studio-select-project-template.png) + +Next, configure your project: + + - Enter *MyAmplifyApp* in the **Name** field + - Select either *Java* or *Kotlin* from the **Language** dropdown menu + - Select *API 16: Android 4.1 (Jelly Bean)* from the **Minimum SDK** dropdown menu + - Press **Finish** + +![](/images/lib/getting-started/android/set-up-android-studio-configure-your-project.png) + +Android Studio will open your project with a tab opened to either *MainActivity.java* or *MainActivity.kt* depending upon if you created a Java or Kotlin project respectively. + +![](/images/lib/getting-started/android/set-up-android-studio-successful-setup.png) + +You now have an empty Android project into which you'll add Amplify in the next steps. diff --git a/src/fragments/lib/devpreview/android/getting_started/40_installLib.mdx b/src/fragments/lib-legacy/project-setup/android/create-application/20_gradle.mdx similarity index 80% rename from src/fragments/lib/devpreview/android/getting_started/40_installLib.mdx rename to src/fragments/lib-legacy/project-setup/android/create-application/20_gradle.mdx index 6f8f5830d14..8388f6ffcf5 100644 --- a/src/fragments/lib/devpreview/android/getting_started/40_installLib.mdx +++ b/src/fragments/lib-legacy/project-setup/android/create-application/20_gradle.mdx @@ -16,7 +16,7 @@ android { dependencies { // Amplify core dependency - implementation 'com.amplifyframework:core:ANDROID_DEVPREVIEW' + implementation 'com.amplifyframework:core:ANDROID_VERSION' // Support for Java 8 features coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' @@ -26,6 +26,10 @@ dependencies { - Set `coreLibraryDesugaringEnabled`, `sourceCompatibility`, and `targetCompatibility` to allow your application to make use of Java 8 features like Lambda expressions - Add Amplify Core and Desugaring libraries to the `dependencies` block + +Amplify Android supports API levels 16 and higher. If your are supporting a min SDK less than 21, follow Android's documentation on adding multidex support. + + Run **Gradle Sync** Android Studio requires you to sync your project with your new configuration. To do this, click **Sync Now** in the notification bar above the file editor. diff --git a/src/fragments/lib-legacy/project-setup/android/create-application/30_provisionBackend.mdx b/src/fragments/lib-legacy/project-setup/android/create-application/30_provisionBackend.mdx new file mode 100644 index 00000000000..d58c785514a --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/android/create-application/30_provisionBackend.mdx @@ -0,0 +1,30 @@ +To start provisioning resources in the backend, change directories to your project directory and run `amplify init`: + +```bash +amplify init +``` + +Enter the following when prompted: + +```console +? Enter a name for the project + `MyAmplifyApp` +? Initialize the project with the above configuration? + `No` +? Enter a name for the environment + `dev` +? Choose your default editor: + `Android Studio` +? Choose the type of app that you're building + `android` +? Where is your Res directory: + `app/src/main/res` +? Select the authentication method you want to use: + `AWS profile` +? Please choose the profile you want to use + `default` +``` + +Upon successfully running `amplify init`, you will see a configuration file created in `./app/src/main/res/raw/` called `amplifyconfiguration.json`. + +This file will be bundled into your application so that the Amplify libraries know how to reach your provisioned backend resources at runtime. diff --git a/src/fragments/lib-legacy/project-setup/android/create-application/40_verifyAmplifyLibraries.mdx b/src/fragments/lib-legacy/project-setup/android/create-application/40_verifyAmplifyLibraries.mdx new file mode 100644 index 00000000000..d98beb8183b --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/android/create-application/40_verifyAmplifyLibraries.mdx @@ -0,0 +1,78 @@ +Create an `Application` class and add the Amplify initialization into its `onCreate()` to initialize Amplify once in your application. + +Right-click on your namespace (e.g. `com.example.MyAmplifyApp`), click **New**, and click **Java Class** or **Kotlin File/Class** depending on which language you choose. + + + + +Configure the new class in **New Java Class**: + +- Enter *MyAmplifyApp* in the **Name** field +- Extend *MyAmplifyApp* from *android.app.Application* by adding `extends Application` to your class +- Press **OK** + +Initialize Amplify by adding an `onCreate` method with the following code: + +```java + public void onCreate() { + super.onCreate(); + + try { + Amplify.configure(getApplicationContext()); + Log.i("MyAmplifyApp", "Initialized Amplify"); + } catch (AmplifyException error) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error); + } + } +``` + + + + + +Configure the new class in **New Kotlin File/Class**: + +- Enter *MyAmplifyApp* in the **Name** field +- Extend *MyAmplifyApp* from *android.app.Application* by adding `: Application()` to your class +- Press enter + +Initialize Amplify by adding an `onCreate` method with the following code: + +```kotlin +override fun onCreate() { + super.onCreate() + + try { + Amplify.configure(applicationContext) + Log.i("MyAmplifyApp", "Initialized Amplify") + } catch (error: AmplifyException) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error) + } +} +``` + + + + +This overrides the `onCreate()` to initialize Amplify when your application is launched. + +Next, configure your application to use your new custom `Application` class. Open **manifests** > **AndroidManifest.xml**, and add a `android:name` attribute with the value of your new class name: + +```xml + + + + + + +``` + +Next, build and run the application. In logcat, you'll see a log line indicating success: + +```console +com.example.MyAmplifyApp I/MyAmplifyApp: Initialized Amplify +``` diff --git a/src/fragments/lib-legacy/project-setup/android/prereq/prereq.mdx b/src/fragments/lib-legacy/project-setup/android/prereq/prereq.mdx new file mode 100644 index 00000000000..fa2202e9348 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/android/prereq/prereq.mdx @@ -0,0 +1,2 @@ +- [Android Studio](https://developer.android.com/studio/index.html#downloads) version 4.0 or higher +- [Android SDK](https://developer.android.com/studio/releases/platforms) API level 16 (Jelly Bean) or higher diff --git a/src/fragments/lib-legacy/project-setup/android/rxjava/rxjava.mdx b/src/fragments/lib-legacy/project-setup/android/rxjava/rxjava.mdx new file mode 100644 index 00000000000..6951694286a --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/android/rxjava/rxjava.mdx @@ -0,0 +1,124 @@ +Amplify also provides a set of APIs that expose [Reactive Extensions](http://reactivex.io/), a cross-platform library for asynchronous and event-based programs. + +To use it, you'll interact with the **`RxAmplify`** facade instead of the default `Amplify` facade. + +```java +import com.amplifyframework.rx.RxAmplify; +// ... + +Post post = Post.builder() + .title("My First Post") + .build(); + +RxAmplify.DataStore.save(post) + .subscribe( + () -> Log.i("RxAmplifyDemo", "Saved a post"), + failure -> Log.e("RxAmplifyDemo", "Save failed", failure) + ); +``` + +Compared to the traditional callback API, this doesn't make a big difference when used for a single method call. + +However, it greatly improves readability when chaining asynchronous calls. Moreover, you can use standard RxJava operators to compose other complex functionality into readable chunks. + +Let's revisit our nested example where we saved `Post`, `Editor`, and `PostEditor`. With Amplify's RxJava interface we can merge these operations together. + +```java +Completable.mergeArray( + RxAmplify.DataStore.save(post), + RxAmplify.DataStore.save(editor) +).andThen( + RxAmplify.DataStore.save(postEditor) +).subscribe( + () -> Log.i("RxAmplifyDemo", "Post, Editor, and PostEditor saved"), + failure -> Log.e("RxAmplifyDemo", "One or more items not saved", failure) +); +``` + +Compared to nesting these dependent calls in callbacks, this provides a much more readable pattern. + +## Installation + +Amplify's RxJava support is included in an optional module, `rxbindings`. To start using the Rx APIs, add the following dependency to your application's Gradle file: + +Under **Gradle Scripts**, open **build.gradle (Module: [YourApplicationName])**. + +Add the following line in `dependencies`: + +```groovy +dependencies { + // Add the below line in `dependencies` + implementation 'com.amplifyframework:rxbindings:ANDROID_VERSION' +} +``` + +## Usage + +Amplify tries to map the behavior of our callback-based APIs to well-known Rx primitives in an intuitive way. Functions whose callbacks emit a single value (or error) will now return Rx `Single`s, instead. Functions whose callbacks emit no particular value will now return Rx `Completable`s, instead. Lastly, functions whose callbacks emit a stream of values will now return `Observable`s, instead. + +## Special cases + +Some APIs return an operation which can be cancelled. Examples include subscribing to an API or uploading or downloading objects from Storage. + +### API subscriptions + +The API category's `subscribe()` method exposes two `Observable`s: one for subscription data, and one for connection state. You can access these `Observable`s using `observeConnectionState()` and `observeSubscriptionData()` on the returned operation: + +```java +RxSubscriptionOperation> subscription = + RxAmplify.API.subscribe(request); + +subscription + .observeConnectionState() + .subscribe( + connectionStateEvent -> Log.i("RxAmplifyDemo", String.valueOf(connectionStateEvent)) + ); + +subscription + .observeSubscriptionData() + .subscribe( + data -> Log.i("RxAmplifyDemo", "Data on subscription = " + data), + failure -> Log.e("RxAmplifyDemo", "Subscription failed", failure), + () -> Log.i("RxAmplifyDemo", "Subscription completed") + ); +``` + +### Storage upload & download operations + +The Storage category's `downloadFile()` and `uploadFile()` work largely the same way. `uploadFile()` and `downloadFile()` both return an operation containing a `Single` and an `Observable`. The `Single` can be used to obtain the result of the download, and the `Observable` can be used to monitor download/upload progress. + +```java +// Download +RxProgressAwareSingleOperation download = + RxAmplify.Storage.downloadFile(remoteKey, localFile); + +download + .observeProgress() + .subscribe( + progress -> Log.i("RxAmplifyDemo", "Download progress = " + progress.toString()) + ); + +download + .observeResult() + .subscribe( + result -> Log.i("RxAmplifyDemo", "Download finished! " + result.getFile().getPath()), + failure -> Log.e("RxAmplifyDemo", "Download failed", failure) + ); + +// Upload +RxProgressAwareSingleOperation upload = + RxAmplify.Storage.uploadFile(remoteKey, localFile); + +upload + .observeProgress() + .subscribe( + progress -> Log.i("RxAmplifyDemo", "Upload progress = " + progress.toString()) + ); + +upload + .observeResult() + .subscribe( + result -> Log.i("RxAmplifyDemo", "Upload finished! " + result.getKey()), + failure -> Log.e("RxAmplifyDemo", "Upload failed", failure) + ); +``` diff --git a/src/fragments/lib-legacy/project-setup/android/use-existing-resources/use-existing-resources.mdx b/src/fragments/lib-legacy/project-setup/android/use-existing-resources/use-existing-resources.mdx new file mode 100644 index 00000000000..abe56227439 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/android/use-existing-resources/use-existing-resources.mdx @@ -0,0 +1,95 @@ +An application’s backend is built with cloud resources such as AWS AppSync GraphQL APIs, Amazon S3 storage, and Amazon Cognito authentication. The Amplify CLI simplifies the provisioning of new backend resources across these different categories. However, you can alternatively use the Amplify libraries to add or re-use existing AWS resources that you provisioned without the CLI. The Amplify libraries support configuration through the *amplifyconfiguration.json* file which defines all the regions and service endpoints for your backend AWS resources. + +## Add an existing AWS resource to an Android application + +Before you can add an existing AWS resource to an Android application, the application must have the Amplify libraries installed. For detailed instructions, see [Install Amplify Libraries](/lib/project-setup/create-application#n2-install-amplify-libraries). + +### 1. Manually create the Amplify configuration file for your Android project + +First, locate your project’s `res` folder. For example, if the name of your project is *MyAmplifyApp*, you can find the `res` folder at the following location, `MyAmplifyApp/app/src/main/res`: + +![GSA](/images/project-setup/2_useExistingResources.png) + +Next, in your project’s `res` folder, create a new folder named `raw`. + +Finally, in the `raw` folder, create a file named `amplifyconfiguration.json`. At this point the contents of your `amplifyconfiguration.json` file can be an empty object, `{}`. + +### 2. Initialize Amplify in your application +To initialize Amplify when your application is launched, you will need to create a new `Application` class and override its `onCreate()` method. + +First, locate your application’s namespace where you will create the new application class. For example, if your application is named *MyAmplifyApp*, navigate to either `MyAmplifyApp/app/src/main/java/com.example.MyAmplifyApp` or `MyAmplifyApp/app/src/main/kotlin/com.example.MyAmplifyApp` depending on the programming language you are using. + +From the Android Studio main menu, choose **File -> New** and select either **Java Class** or **Kotlin File/Class** depending your programming language. + +Select **Class**, and specify a name for your new class in the **Name** field. + +Paste the following code for the `onCreate()` method inside your new class: + + + + + +```java + public void onCreate() { + super.onCreate(); + + try { + Amplify.configure(getApplicationContext()); + + Log.i("MyAmplifyApp", "Initialized Amplify"); + } catch (AmplifyException e) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", e); + } +} +``` + + + + + +```kotlin +override fun onCreate() { + super.onCreate() + + try { + Amplify.configure(applicationContext) + Log.i("MyAmplifyApp", "Initialized Amplify") + } catch (error: AmplifyException) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error) + } +} +``` + + + + + +Next, configure your application to use your new custom `Application class`. Open the `AndroidManifest.xml` file located in your project directory at `app/src/main/AndroidManifest.xml`. + +Add the `android:name` attribute to the application node. For example, if the application name is *MyAmplifyApp* and the new class is named *MyAmplifyApplication*, the update to the `AndroidManifest.xml` file looks as follows: +```xml + + + + + + +``` + +### 3. Edit your configuration file to use an existing AWS resource + +Now you’re ready to customize your application’s `amplifyconfiguration.json` file to specify an existing AWS resource to use. + +Note that before you can add an AWS resource to your application, the application must have the Amplify libraries installed. If you need to perform this step, see [Install Amplify Libraries](/lib/project-setup/create-application#n2-install-amplify-libraries). + +Select a category from the following list to view an example `amplifyconfiguration.json` file you can use as a template to author your own `amplifyconfiguration.json` file: + +* See the [Analytics category](/lib/analytics/existing-resources) to use existing AWS Pinpoint resources. +* See the [API (GraphQL) category](/lib/graphqlapi/existing-resources) to use existing AWS AppSync resources. +* See the [API (REST) category](/lib/restapi/existing-resources) to use existing Amazon API Gateway and AWS Lambda resources. +* See the [Authentication category](/lib/auth/existing-resources) to use existing Amazon Cognito resources. +* See the [Storage category](/lib/storage/existing-resources) to use existing Amazon S3 resources. diff --git a/src/fragments/lib-legacy/project-setup/flutter/create-application/10_createProject.mdx b/src/fragments/lib-legacy/project-setup/flutter/create-application/10_createProject.mdx new file mode 100644 index 00000000000..5bcd2840144 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/flutter/create-application/10_createProject.mdx @@ -0,0 +1,7 @@ +Create a new project using Flutter CLI: + +```bash +flutter create +``` + +You now have an empty Flutter project into which you'll add Amplify in the next steps. diff --git a/src/fragments/lib-legacy/project-setup/flutter/create-application/20_pubspec.mdx b/src/fragments/lib-legacy/project-setup/flutter/create-application/20_pubspec.mdx new file mode 100644 index 00000000000..fcb3b81457d --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/flutter/create-application/20_pubspec.mdx @@ -0,0 +1,13 @@ +Amplify for Flutter is distributed via [pub.dev](https://pub.dev/packages/amplify_flutter). + +1. From your project root directory, find and modify `pubspec.yaml` and add required Amplify plugins to the project dependencies. E.g. + +import flutter0 from "/src/fragments/lib/project-setup/flutter/create-application/60_dependencies.mdx"; + + + +2. Install the dependencies by running the following command. Depending on your development environment, you may perform this step via your IDE (or it may even be performed for you automatically). + + ```bash + flutter pub get + ``` diff --git a/src/fragments/lib-legacy/project-setup/flutter/create-application/30_provisionBackend.mdx b/src/fragments/lib-legacy/project-setup/flutter/create-application/30_provisionBackend.mdx new file mode 100644 index 00000000000..7093bcefcb0 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/flutter/create-application/30_provisionBackend.mdx @@ -0,0 +1,52 @@ +To start provisioning resources in the backend, change directories to your project directory and run `amplify init`: + +```bash +# Make sure you have Amplify CLI v4.28 and above for Flutter support +amplify init +``` + +Amplify CLI then will suggest you an auto-generated options for you to speed up the process: + +```console +Project information +| Name: yourprojectname +| Environment: dev +| Default editor: Visual Studio Code +| App type: flutter +| Configuration file location: ./lib/ + +? Initialize the project with the above configuration? +``` + +If any of the following does not work for you, you can say no to the question that is prompted. + +After that, Amplify CLI will take you through the installation process. Enter the following when prompted: + +```console +? Enter a name for the environment + `dev` +? Choose your default editor: + `IntelliJ IDEA` +? Choose the type of app that you're building: + 'flutter' +? Where do you want to store your configuration file? + ./lib/ +``` + +After the project information is filled, you will be prompted to select the AWS profile for you to host your information. + +```console +? Select the authentication method you want to use: (Use arrow keys) +❯ AWS profile + AWS access keys + +# This is the profile you created with the `amplify configure` command in the introduction step. +Please choose the profile you want to use (Use arrow keys) +> default +``` +> Where possible the CLI will infer the proper configuration based on the type of project Amplify is being initialized in. In this case it knew we are using Create React App and provided the proper configuration for type of app, framework, source, distribution, build, and start options. + + +Upon successfully running `amplify init`, you will see a configuration file created in `./lib/` called `amplifyconfiguration.dart`. + +This file will be bundled into your application so that the Amplify libraries know how to reach your provisioned backend resources at runtime. diff --git a/src/fragments/lib-legacy/project-setup/flutter/create-application/40_verifyAmplifyLibraries.mdx b/src/fragments/lib-legacy/project-setup/flutter/create-application/40_verifyAmplifyLibraries.mdx new file mode 100644 index 00000000000..fb8d2248628 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/flutter/create-application/40_verifyAmplifyLibraries.mdx @@ -0,0 +1,49 @@ + +Before using any methods in the Amplify Flutter Library, it's important to add all necessary plugins and to call configure once in your app. The steps below will guide you through configuring Amplify Flutter at the root level of your flutter app. + +Add the necessary dart dependencies at the top of main.dart alongside to other imports: + +```dart +// Amplify Flutter Packages +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; + +// Generated in previous step +import 'amplifyconfiguration.dart'; +``` + +Add the following code to your application's root Stateful Widget, for the default Flutter app this should be the `class _MyHomePageState extends State`. + +```dart + +class _MyHomePageState extends State { + + @override + initState() { + super.initState(); + _configureAmplify(); + } + + Future _configureAmplify() async { + + // Add any Amplify plugins you want to use + final authPlugin = AmplifyAuthCognito(); + await Amplify.addPlugin(authPlugin); + + // You can use addPlugins if you are going to be adding multiple plugins + // await Amplify.addPlugins([authPlugin, analyticsPlugin]); + + // Once Plugins are added, configure Amplify + // Note: Amplify can only be configured once. + try { + await Amplify.configure(amplifyconfig); + } on AmplifyAlreadyConfiguredException { + safePrint("Tried to reconfigure Amplify; this can occur when your app restarts on Android."); + } + } + + // customize the rest of your Widget below as you wish... + +``` + +Note that all calls to `addPlugin()` or `addPlugins()` are made before `Amplify.configure()` is called. diff --git a/src/fragments/lib-legacy/project-setup/flutter/create-application/50_nextSteps.mdx b/src/fragments/lib-legacy/project-setup/flutter/create-application/50_nextSteps.mdx new file mode 100644 index 00000000000..5860ae22de2 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/flutter/create-application/50_nextSteps.mdx @@ -0,0 +1,8 @@ +Congratulations! You've created a skeleton app and are ready to start adding Amplify categories to your application. The following are some categories that you can start to build into your application: + +* [Analytics](/lib/analytics/getting-started) - for logging metrics and understanding your users +* [API (GraphQL)](/lib/graphqlapi/getting-started) - for adding a GraphQL endpoint to your app +* [API (REST)](/lib/restapi/getting-started) - for adding a REST endpoint to your app +* [Authentication](/lib/auth/getting-started) - for managing your users +* [DataStore](/lib/datastore/getting-started) - for making it easier to program for a distributed data store for offline and online scenarios +* [Storage](/lib/storage/getting-started) - store complex objects like pictures and videos to the cloud. diff --git a/src/fragments/lib-legacy/project-setup/flutter/create-application/60_dependencies.mdx b/src/fragments/lib-legacy/project-setup/flutter/create-application/60_dependencies.mdx new file mode 100644 index 00000000000..f7123d07f4a --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/flutter/create-application/60_dependencies.mdx @@ -0,0 +1,35 @@ + + + + + ```yaml + environment: + sdk: ">=2.15.0 <3.0.0" + + dependencies: + flutter: + sdk: flutter + + amplify_flutter: ^0.6.0 + amplify_auth_cognito: ^0.6.0 + ``` + + + + + + ```yaml + environment: + sdk: ">=2.17.0 <3.0.0" + + dependencies: + flutter: + sdk: flutter + + amplify_flutter: ^1.0.0-next.0 + amplify_auth_cognito: ^1.0.0-next.0 + ``` + + + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/project-setup/flutter/escape-hatch/escape-hatch.mdx b/src/fragments/lib-legacy/project-setup/flutter/escape-hatch/escape-hatch.mdx new file mode 100644 index 00000000000..4baad51d485 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/flutter/escape-hatch/escape-hatch.mdx @@ -0,0 +1,84 @@ +As an alternative to the Amplify client libraries, or in situations where the libraries do not provide the functionality you require, +the underlying AWS services can be communicated with directly using an HTTP client and the AWS Signature V4 (SigV4) [package](https://pub.dev/packages/aws_signature_v4). + +### Using the Signer + +To get started using the signer, add it as a dependency in your `pubspec.yaml` like the following: + +```yaml +dependencies: + aws_common: ^0.1.0 + aws_signature_v4: ^0.1.0 +``` + +After that create an instance of the signer in your project. + +```dart +import 'package:aws_signature_v4/aws_signature_v4.dart'; + +const signer = AWSSigV4Signer(); +``` + +AWS credentials are configured in the signer by overriding the `credentialsProvider` parameter of the constructor. By default, the signer +pulls credentials from your environment via the `AWSCredentialsProvider.environment()` provider. On mobile and web, this means using +the Dart environment which is configured by passing the `dart-define` flag to your flutter commands, like the following: + +```sh +$ flutter run --dart-define=AWS_ACCESS_KEY_ID=... --dart-define=AWS_SECRET_ACCESS_KEY=... +``` + +On Desktop, credentials are retrieved from the system's environment using [Platform.environment](https://api.dart.dev/stable/dart-io/Platform/environment.html). + +### Signing a Request + +The signer works by transforming HTTP requests using your credentials to create _signed_ HTTP requests which can be sent off in the +same way as normal HTTP requests. + +As an example, here's how you would sign a request to Cognito to gather information about a [User Pool](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html). + +```dart +import 'dart:convert'; + +import 'package:aws_common/aws_common.dart'; +import 'package:aws_signature_v4/aws_signature_v4.dart'; + +// Create the signer instance with credentials from the environment. +const AWSSigV4Signer signer = AWSSigV4Signer( + credentialsProvider: AWSCredentialsProvider.environment(), +); + +// Create the signing scope and HTTP request +const region = ''; + +Future main() async { + final scope = AWSCredentialScope( + region: region, + service: AWSService.cognitoIdentityProvider, + ); + + final request = AWSHttpRequest( + method: AWSHttpMethod.post, + uri: Uri.https('cognito-idp.$region.amazonaws.com', '/'), + headers: const { + AWSHeaders.target: 'AWSCognitoIdentityProviderService.DescribeUserPool', + AWSHeaders.contentType: 'application/x-amz-json-1.1', + }, + body: json.encode({ + 'UserPoolId': '', + }).codeUnits, + ); + + // Sign and send the HTTP request + final signedRequest = await signer.sign( + request, + credentialScope: scope, + ); + final resp = await signedRequest.send(); + final respBody = await resp.decodeBody(); + print(respBody); +} +``` + +For a full example, check out the [example](https://github.com/aws-amplify/amplify-flutter/tree/main/packages/aws_signature_v4/example) project in the GitHub repo. +And for specifics on the different AWS operations you can perform, check out the AWS API Reference docs for the service. For example, +[here](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_DescribeUserPool.html) are the docs for the `DescribeUserPool` API used above. diff --git a/src/fragments/lib-legacy/project-setup/flutter/null-safety/null-safety.mdx b/src/fragments/lib-legacy/project-setup/flutter/null-safety/null-safety.mdx new file mode 100644 index 00000000000..9eb003b360e --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/flutter/null-safety/null-safety.mdx @@ -0,0 +1,38 @@ +**Amplify Flutter and Null Safety** + + + +Amplify Flutter is planning to drop the support of non-null safe models in the near future. Please [migrate to Dart null safety](https://dart.dev/null-safety/migration-guide). + + + +The Amplify Flutter library supports [Dart null safety](https://dart.dev/null-safety) starting with version 0.2.0. + +| | amplify-flutter 0.1.x | amplify-flutter 0.2.x | +|------------------------------- |--------------------------------- |--------------------------------- | +| Null Safe App | Not Supported | Supported | +| Non Null Safe App w/ flutter v2 | Supported | Supported | +| Non Null Safe App w/ flutter v1 | Supported | Not Supported | + +**DataStore with Code Generated Models and Null Safety** + +The latest version of the Amplify CLI can generate Dart models with or without null safety using the `amplify codegen models` command. + +***Opting-in to Null Safety*** + +If you have a null safe app, or are migrating to null safety and your app uses generated models from amplify, you will need ensure the models are null safe as well. You should follow the [flutter null safety migration docs](https://dart.dev/null-safety/migration-guide) to migrate your application code, excluding the generated models. + +To migrate to null safe models, you can simply regenerate them following the instructions: +1. Make sure that the `pubspec.yaml` file at the root of your projects defines a Dart SDK version of 2.12.0 or greater. +2. Update your Amplify CLI to version 5.1.0 or higher. +```js +npm install -g @aws-amplify/cli +``` +4. Make sure that the `enableDartNullSafety` [feature flag](https://docs.amplify.aws/cli/reference/feature-flags) is set to "true" in your `amplify/cli.json` file. +5. Re-run `amplify codegen models` for your schema. + +***Opting-out of Null Safety*** + +If you wish to continue using non-null safe models: +1. Make sure that the `enableDartNullSafety` [feature flag](https://docs.amplify.aws/cli/reference/feature-flags) is set to "false". +2. Re-run `amplify codegen models` for your schema diff --git a/src/fragments/lib-legacy/project-setup/flutter/platform-setup/platform-setup.mdx b/src/fragments/lib-legacy/project-setup/flutter/platform-setup/platform-setup.mdx new file mode 100644 index 00000000000..32ff1981786 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/flutter/platform-setup/platform-setup.mdx @@ -0,0 +1,105 @@ + + + + +## iOS +From your project root, navigate to the `ios/` directory and modify the `Podfile` using a text editor of your choice and update the target iOS platform to 11.0 or higher. + +```bash +platform :ios, '11.0' +``` + + + +When preparing your application for deployment, you should also update your iOS Deployment Target to at least 11.0. See the [Flutter docs](https://docs.flutter.dev/deployment/ios) to learn more about building your iOS app for release. + + + +## Android +From your project root, navigate to the `android/app/` directory and modify `build.gradle` using a text editor of your choice and update the target Android SDK version to 21 or higher: + +```bash +minSdkVersion 21 +``` + +import flutter1 from '/src/fragments/start/getting-started/flutter/kotlin-version-warning.mdx'; + + + + + + + +## Web +No specific configuration steps are needed to begin using the Auth category in the browser. + +## iOS +From your project root, navigate to the `ios/` directory and modify the `Podfile` using a text editor of your choice and update the target iOS platform to 13.0 or higher. + +```bash +platform :ios, '13.0' +``` + +## Android + +From your project root, navigate to the `android/app/` directory and modify `build.gradle` using a text editor of your choice and update the target Android SDK version to 24 or higher: + +```bash +minSdkVersion 24 +``` + +## macOS +To run the Amplify-Flutter developer preview on macOS, you will need to enable signing as well as networking and keychain [entitlements](https://developer.apple.com/documentation/bundleresources/entitlements). + +### Enable network calls + +In order to run your application locally, you must enable the [Client Networking [entitlement](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_network_client). + +In Xcode, enable the App Sandbox capability and then select "Outgoing Connections (Client)" under "Network". + +![XCode Entitlements Screen](/images/project-setup/flutter/mac/xcode-entitlements.png) + +### Enable keychain + +Amplify Auth stores auth credentials in Keychain on macOS. The keychain option [kSecUseDataProtectionKeychain](https://developer.apple.com/documentation/security/ksecusedataprotectionkeychain?language=objc) is enabled by default, which makes it so that only your app has access to these credentials. This requires that your app be in at least one Keychain Access Group. + +To enable keychain sharing, open your project in Xcode and select the "Signing and Capabilities" section, then click the "+ icon". + +![XCode Enable Keychain Sharing](/images/project-setup/flutter/mac/enable-keychain-access.png) + +Search for "Keychain Sharing" in the subsequent modal, and add it. + +![XCode Keychain Sharing Search](/images/project-setup/flutter/mac/search-keychain-sharing.png) + +Finally, scroll down to "Keychain Sharing" in the "Signing and Capabilities" and click the "+" icon. By default, your bundle ID will be used. + +![XCode Adding Keychain Sharing Group](/images/project-setup/flutter/mac/adding-keychain-access-group.png) + +### Enable signing + +Add a development team in the "Signing and Capabilities" section and enable signing. + +![XCode Signing and Capabilities Screen](/images/project-setup/flutter/mac/enable-signing.png) + +## Windows +No specific configuration steps are needed to begin using the Auth category on Windows; however, depending on how your Windows configuration and the method you use to run your Flutter app, you may need to change the file path length limitation. + +## Linux +To run the Amplify-Flutter developer preview on Linux, you must install two libraries: + +* libsecret-1-dev +* libglib2.0-dev + +To install on Ubuntu, run: + +```terminal +sudo apt-get update +sudo apt-get install -y libsecret-1-dev +sudo apt-get install -y libglib2.0-dev +``` + +The command to install might vary slightly on other Linux distributions. + + + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/project-setup/flutter/prereq/cliInstall.mdx b/src/fragments/lib-legacy/project-setup/flutter/prereq/cliInstall.mdx new file mode 100644 index 00000000000..d6ecd6345b3 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/flutter/prereq/cliInstall.mdx @@ -0,0 +1,5 @@ + + +import all0 from "/src/fragments/cli-install-block.mdx"; + + diff --git a/src/fragments/lib-legacy/project-setup/flutter/prereq/prereq.mdx b/src/fragments/lib-legacy/project-setup/flutter/prereq/prereq.mdx new file mode 100644 index 00000000000..7b840b84a66 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/flutter/prereq/prereq.mdx @@ -0,0 +1,4 @@ +- Install a stable version of [Flutter](https://flutter.dev/docs/get-started/install). + - For the stable release, use version 2.10.0 or higher + - For the developer preview, use version 3.0.0 or higher +- Setup your [IDE](https://flutter.dev/docs/get-started/editor?tab=androidstudio) diff --git a/src/fragments/lib-legacy/project-setup/ios/combine/combine.mdx b/src/fragments/lib-legacy/project-setup/ios/combine/combine.mdx new file mode 100644 index 00000000000..8c6155c4459 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/ios/combine/combine.mdx @@ -0,0 +1,163 @@ + +The default Amplify library for iOS supports iOS 11 and higher, and ships with APIs that return results on `Result` callbacks, as in: + +```swift +Amplify.DataStore.save(Post(title: "My Post", content: "My content", ...), completion: { result in + switch result { + case .success: + print("Post saved") + case .failure(let dataStoreError): + print("An error occurred saving the post: \(dataStoreError)") + } +}) +``` + +If your project declares platform support of iOS 13 or higher, Amplify also provides APIs that expose [Combine](https://developer.apple.com/documentation/combine) Publishers, which allows you to use familiar Combine patterns, as in: + +```swift +Amplify.DataStore.save(Post(title: "My Post", content: "My content")) + .sink { completion in + if case .failure(let dataStoreError) = completion { + print("An error occurred saving the post: \(dataStoreError)") + } + } + receiveValue: { value in + print("Post saved: \(value)") + } +``` + +While this doesn't save much for a single invocation, it provides great readability benefits when chaining asynchronous calls, since you can use standard Combine operators and publishers to compose complex functionality into readable chunks: + +```swift +subscription = Publishers.Zip( + Amplify.DataStore.save(Person(name: "Rey")), + Amplify.DataStore.save(Person(name: "Kylo")) +).flatMap { hero, villain in + Amplify.DataStore.save(EpicBattle(hero: hero, villain: villain)) +}.flatMap { battle in + Publishers.Zip( + Amplify.DataStore.save( + Outcome(of: battle) + ), + Amplify.DataStore.save( + Checkpoint() + ) + ) +}.sink { completion in + if case .failure(let dataStoreError) = completion { + print("An error occurred in a preceding operation: \(dataStoreError)") + } +} +receiveValue: { _ in + print("Everything completed successfully") +} +``` + +Compared to nesting these dependent calls in callbacks, this provides a much more readable pattern. + +**NOTE**: Remember that Combine publishers do not retain `sink` subscriptions, so you must maintain a reference to the subscription in your code, such as in an instance variable of the enclosing type: + +```swift +struct MyAppCode { + var subscription AnyCancellable? + + ... + + func doSomething() { + // Subscription is retained by the `self.subscription` instance + // variable, so the `sink` code will be executed + subscription = Amplify.DataStore.save(Person(name: "Rey")) + .sink(...) + } +} +``` + +## Installation + +There is no additional work needed to enable Combine support. Projects that declare a deployment target of iOS 13.0 or higher will automatically see the appropriate method signatures and properties, depending on the Category and API you are calling. + +## API Comparison + +Amplify strives to provide an intuitive interface for APIs that expose Combine functionality by overloading the no-Combine API signature, minus the result callbacks. Thus, `Amplify.DataStore.save(_:where:completion:)` has an equivalent Combine-supporting API of `Amplify.DataStore.save(_:where:)`. In most cases, the Result callback `Success` and `Failure` types in standard Amplify APIs translate exactly to the `Output` and `Failure` types of publishers returned from Combine-supporting APIs. + +The way to get to a Combine publisher for a given API varies depending on whether the asynchronous work can be cancelled or not: + +- APIs that **do not** return an operation simply return an `AnyPublisher` directly from the API call: + ```swift + let publisher = Amplify.DataStore + .save(myPost) + ``` + +- Most APIs that **do** return an operation expose a `resultPublisher` property on the returned operation + ```swift + let publisher = Amplify.Predictions + .convert(textToSpeech: text, options: options) + .resultPublisher + ``` + +### Special cases + +Not all APIs map neatly to the `resultPublisher` pattern. While this asymmetry increases the mental overhead of learning to use Amplify with Combine, the ease of use at the call site should make up for the additional learning curve. In addition, Xcode will show the available publisher properties, making it easier to discover which publisher you need: + +![image](/images/combine-xcode.png) + +#### `API.subscribe()` + +The `API.subscribe()` method exposes a `subscriptionDataPublisher` for the stream of subscription data, and a `connectionStatePublisher` for the status of the underlying connection. Many apps will only need to use the `subscriptionDataPublisher`, since a closed GraphQL subscription will be reported as a completion on that publisher. The `connectionStatePublisher` exists for apps that need to inspect when the connection initially begins, even if data has not yet been received by that subscription. + +#### `Hub.publisher(for:)` + +The Amplify Hub category exposes only one Combine-related API: `Hub.publisher(for:)`, which returns a publisher for all events on a given channel. You can then apply the standard Combine [`filter`](https://developer.apple.com/documentation/combine/anypublisher/filter(_:)) operator to inspect only those events you care about. + +#### `Storage` upload & download operations + +Storage upload and download APIs report both completion and overall operation progress. In addition to the typical `resultPublisher` that reports the overall status of the operation, Storage upload and download APIs also have a `progressPublisher` that reports incremental progress when available. + +## Cancelling operations + +Most Amplify APIs return a use-case specific Operation that you may use to cancel an in-process operation. On iOS 13 and above, those Operations contain publishers to report values back to the app. + +Cancelling a subscription to a publisher simply releases that publisher, but does not affect the work in the underlying operation. For example, say you start a file upload on a view in your app: + +```swift +import Combine + +class MyView: UIView { + +// Declare instance properties to retain the operation and subscription cancellables +var uploadOperation: StorageUploadFileOperation? +var resultSink: AnyCancellable? +var progressSink: AnyCancellable? + +// Then when you start the operation, assign those instance properties +func uploadFile() { + uploadOperation = Amplify.Storage.uploadFile(key: fileNameKey, local: filename) + + resultSink = uploadOperation + .resultPublisher + .sink( + receiveCompletion: { completion in + if case .failure(let storageError) = completion { + handleUploadError(storageError) + } + }, receiveValue: { print("File successfully uploaded: \($0)") } + ) + + progressSink = uploadOperation + .progressPublisher + .sink{ print("\($0.fractionCompleted * 100)% completed") } +} +``` + +After you call `uploadFile()` as above, your containing class retains a reference to the operation that is actually performing the upload, as well as Combine `AnyCancellable`s that can be used to stop receiving result and progress events. + +To cancel the upload (for example, in response to the user pressing a **Cancel** button), you simply call `cancel()` on the upload operation: + +```swift +func cancelUpload() { + // Automatically sends a completion to `resultPublisher` and `progressPublisher` + uploadOperation.cancel() +} +``` + +If you navigate away from `MyView`, the `uploadOperation`, `resultSink`, and `progressSink` instance variables will be released, and you will no longer receive progress or result updates on those sinks, but Amplify will continue to process the upload operation. diff --git a/src/fragments/lib-legacy/project-setup/ios/create-application/10_createProject.mdx b/src/fragments/lib-legacy/project-setup/ios/create-application/10_createProject.mdx new file mode 100644 index 00000000000..aeac713850c --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/ios/create-application/10_createProject.mdx @@ -0,0 +1,18 @@ +**Open Xcode.** From the menu bar, select **"File -> New -> Project..."** + +Select **iOS** tab, choose Application type as **App** and then click on **Next**. + +![GSA](/images/project-setup/20_1_createProject.png) + +Fill in the following for your project: +* Product Name: **MyAmplifyApp** +* Interface: **SwiftUI** +* Language: **Swift** +* **Tap `Next`** + +![GSA](/images/project-setup/20_2_createProject.png) + +After tapping Next, **select where you would like to save your project**, then **tap Create**. In this example, we will choose: **~/Developer** +![GSA](/images/project-setup/20_3_createProject.png) + +You should now have an empty iOS project without Amplify. \ No newline at end of file diff --git a/src/fragments/lib-legacy/project-setup/ios/create-application/20_install_libraries.mdx b/src/fragments/lib-legacy/project-setup/ios/create-application/20_install_libraries.mdx new file mode 100644 index 00000000000..08c3d401a38 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/ios/create-application/20_install_libraries.mdx @@ -0,0 +1,85 @@ + + + + +To start adding the Amplify Libraries to your iOS project, open your project in Xcode and select **File > Add Packages...** + +![Add package dependency](/images/project-setup/20_4_add-package-dependency.png) + +Enter the Amplify iOS GitHub repo URL (`https://github.com/aws-amplify/amplify-ios`) into the search bar and hit **Enter**. Wait for the result to load. + +You'll see the Amplify iOS repository rules for which version of Amplify you want Swift Package Manager to install. Choose the dependency rule **Up to Next Major Version**, as it will use the latest compatible version of the dependency that can be detected from the `main` branch, then click **Add Package**. + +![Search for repo](/images/project-setup/20_5_search-amplify-repo.png) + +Lastly, choose which of the libraries you want added to your project. Always select the **Amplify** library. The "Plugin" to install depends on which categories you are using: + +- API: **AWSAPIPlugin** +- Analytics: **AWSPinpointAnalyticsPlugin** +- Auth: **AWSCognitoAuthPlugin** +- DataStore: **AWSDataStorePlugin** +- Geo (Developer Preview): **AWSLocationGeoPlugin** +- Storage: **AWSS3StoragePlugin** + + _Note: AWSPredictionsPlugin is not currently supported through Swift Package Manager due to different minimum iOS version requirements. Support for this will eventually be added._ + +![Select dependencies](/images/project-setup/20_7_select-dependencies.png) + +Select all that are appropriate, then click **Add Package**. + +You can always go back and modify which SPM packages are included in your project by opening the Package Dependencies tab for your project: Click on the Project file in the Xcode navigator, then click on your project's icon, then select the **Package Dependencies** tab. + + + +You must explicitly import plugins in your app code when using Swift Package Manager to install Amplify, as in: + +```swift +import Amplify +import AWSAPIPlugin +import AWSDataStorePlugin +``` + +This is a result of Swift Package Manager's importing only relevant pieces of the dependency being installed–in this case, the categories of Amplify. + + + + + + + +Before starting this step, please make sure you **close Xcode.** + +**Open a terminal** and **change directories to your project**. For example, if you created your project in the folder `~/Developer`, you can: +```bash +cd ~/Developer/MyAmplifyApp +``` + +In order to initialize your project with the CocoaPods package manager, **execute the command**: +```bash +pod init +``` + +After doing this, you should see a newly created file called `Podfile`. This file is used to describe what packages your project depends on. + +**Update the file** to include the `Amplify` pod: +``` +target 'MyAmplifyApp' do + use_frameworks! + pod 'Amplify' +end +``` + +To download and install the Amplify pod into your project, **execute the command**: +```bash +pod install --repo-update +``` + +After doing this, you should now see file called `MyAmplifyApp.xcworkspace`. You are required to use this file from now on instead of the .xcodeproj file. To open your workspace, **execute the command**: +```bash +xed . +``` +This should open the newly generated MyAmplifyApp.xcworkspace in Xcode. + + + + diff --git a/src/fragments/lib-legacy/project-setup/ios/create-application/30_provisionBackend.mdx b/src/fragments/lib-legacy/project-setup/ios/create-application/30_provisionBackend.mdx new file mode 100644 index 00000000000..e731362558f --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/ios/create-application/30_provisionBackend.mdx @@ -0,0 +1,23 @@ +To start provisioning resources in the backend, change directories to your project directory and run `amplify init`: +```bash +cd ~/Developer/MyAmplifyApp/ +amplify init +``` + +Enter the following when prompted: +```console +? Enter a name for the project + MyAmplifyApp +? Enter a name for the environment + dev +? Choose your default editor: + Visual Studio Code +? Choose the type of app that you're building + ios +? Do you want to use an AWS profile? + Yes +? Please choose the profile you want to use + default +``` + +Upon successfully running `amplify init`, you should see two new created files in your project directory: `amplifyconfiguration.json` and `awsconfiguration.json`. These two files must be manually added to your project so that they are bundled with your application. This is required so that Amplify libraries know how to reach your provisioned backend resources. diff --git a/src/fragments/lib-legacy/project-setup/ios/create-application/31_provisionBackend.mdx b/src/fragments/lib-legacy/project-setup/ios/create-application/31_provisionBackend.mdx new file mode 100644 index 00000000000..8c1b955b4ce --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/ios/create-application/31_provisionBackend.mdx @@ -0,0 +1,13 @@ +To add these configuration files to your project, **open finder within your project** and **drag both `amplifyconfiguration.json` and `awsconfiguration.json` to the Xcode window**, under your project's folder as seen in this screenshot: + +![GSA](/images/project-setup/50_1_dragDrop.png) + +* Enable **Copy items if needed** if not already enabled +* For “Added folders”, have **Create groups** selected. +* For “Add to targets”, make sure the app target (**MyAmplifyApp**) is checked. + +Click **Finish** to add these files to your project as shown in this screenshot: + +![GSA](/images/project-setup/50_2_addFiles.png) + +Now you can build (`Cmd+b`) and run (`Cmd+r`) your application. diff --git a/src/fragments/lib-legacy/project-setup/ios/create-application/40_verifyAmplifyLibraries.mdx b/src/fragments/lib-legacy/project-setup/ios/create-application/40_verifyAmplifyLibraries.mdx new file mode 100644 index 00000000000..f8beb74fa4a --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/ios/create-application/40_verifyAmplifyLibraries.mdx @@ -0,0 +1,57 @@ +**Open AppDelegate.swift** or **MyAmplifyApp.swift** and add `import Amplify` at the top of the file: +```swift +import Amplify +``` + +**Update the following function** to verify that Amplify can be compiled into your project: +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + do { + try Amplify.configure() + } catch { + print("An error occurred setting up Amplify: \(error)") + } + return true +} +``` +**Note:** If your app conforms to the `App` protocol, you can use your own `AppDelegate` class. Implement an `AppDelegate` and point Swift UI's `UIApplicationDelegateAdaptor` property wrapper to it, as below. + +```swift +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + do { + try Amplify.configure() + } catch { + print("An error occurred setting up Amplify: \(error)") + } + return true + } +} +@main +struct MyAmplifyApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} +``` + +Build your project (`Cmd+b`), then you have successfully added the Amplify library to your project and you should be able to run the application. + +Optionally, if you'd like to see additional log messages of what amplify is doing during configuration, you can turn on verbose logging before calling `Amplify.configure()`: +```swift +do { + Amplify.Logging.logLevel = .verbose + // Configure Amplify as usual... + try Amplify.configure() + // ... +``` + +Re-running the application with verbose logging on, you will see the following messages: +```console +[Amplify] Configuring +[Amplify] Configuration: nil +``` diff --git a/src/fragments/lib-legacy/project-setup/ios/prereq/prereq.mdx b/src/fragments/lib-legacy/project-setup/ios/prereq/prereq.mdx new file mode 100644 index 00000000000..f34418c42a7 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/ios/prereq/prereq.mdx @@ -0,0 +1,4 @@ +- [Install Xcode](https://developer.apple.com/xcode/downloads/) version 11.4 or later. +- (Optional) [Install CocoaPods](https://guides.cocoapods.org/) + + Amplify can be installed through the Swift Package Manager, which is integrated into Xcode, or you can install it through CocoaPods. \ No newline at end of file diff --git a/src/fragments/lib-legacy/project-setup/ios/use-existing-resources/use-existing-resources.mdx b/src/fragments/lib-legacy/project-setup/ios/use-existing-resources/use-existing-resources.mdx new file mode 100644 index 00000000000..eb6a611c293 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/ios/use-existing-resources/use-existing-resources.mdx @@ -0,0 +1,47 @@ +An application’s backend is built with cloud resources such as AWS AppSync GraphQL APIs, Amazon S3 storage, and Amazon Cognito authentication. The Amplify CLI simplifies the provisioning of new backend resources across these different categories. However, you can alternatively use the Amplify libraries to add or re-use existing AWS resources that you provisioned without the CLI. The Amplify libraries support configuration through the *amplifyconfiguration.json* file which defines all the regions and service endpoints for your backend AWS resources. + +## Add an existing AWS resource to an iOS application + +Before you can add an existing AWS resource to an iOS application, the application must have the Amplify libraries installed. For detailed instructions, see [Install Amplify Libraries](/lib/project-setup/create-application#n2-install-amplify-libraries). + +### 1. Manually create the Amplify configuration file for your iOS project + +Create a file named `amplifyconfiguration.json` in your project’s main directory. At this point the contents of your `amplifyconfiguration.json` file can be an empty object, `{}`. + +For example, if the name of your project is *MyAmplifyApp*, you will create the configuration file in your main application directory, `MyAmplifyApp/amplifyconfiguration.json`, as follows: + +![GSA](/images/project-setup/1_useExistingResources.png) + +### 2. Initialize Amplify in the application + +To initialize Amplify in your iOS application, open the `AppDelegate.swift` file and add `import Amplify` at the top of the file. +```swift +import Amplify +``` + +Update the `application` function in the `AppDelegate.swift` file to verify that Amplify can be compiled into your project. The `application` function’s code should be the following: + +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + do { + try Amplify.configure() + } catch { + print("An error occurred setting up Amplify: \(error)") + } + return true +} +``` +### 3. Edit the configuration file to use an existing AWS resource + +Now you’re ready to customize your application’s `amplifyconfiguration.json` file to specify an existing AWS resource to use. + +Note that before you can add an AWS resource to your application, the application must have the Amplify libraries installed. If you need to perform this step, see [Install Amplify Libraries](/lib/project-setup/create-application#n2-install-amplify-libraries). + +Select a category from the following list to view an example `amplifyconfiguration.json` file you can use as a template to author your own `amplifyconfiguration.json` file: + +* See the [Analytics category](/lib/analytics/existing-resources) to use existing AWS Pinpoint resources. +* See the [API (GraphQL) category](/lib/graphqlapi/existing-resources) to use existing AWS AppSync resources. +* See the [API (REST) category](/lib/restapi/existing-resources) to use existing Amazon API Gateway and AWS Lambda resources. +* See the [Authentication category](/lib/auth/existing-resources) to use existing Amazon Cognito resources. +* See the [Geo (Developer Preview) category](/lib/geo/existing-resources) to use existing Amazon Location Service resources. +* See the [Storage category](/lib/storage/existing-resources) to use existing Amazon S3 resources. \ No newline at end of file diff --git a/src/fragments/lib-legacy/project-setup/native_common/create-application/50_nextSteps.mdx b/src/fragments/lib-legacy/project-setup/native_common/create-application/50_nextSteps.mdx new file mode 100644 index 00000000000..5b21bc44026 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/native_common/create-application/50_nextSteps.mdx @@ -0,0 +1,10 @@ +Congratulations! You've created a skeleton app and are ready to start adding Amplify categories to your application. The following are some categories that you can start to build into your application: + +* [Analytics](/lib/analytics/getting-started) - for logging metrics and understanding your users +* [API (GraphQL)](/lib/graphqlapi/getting-started) - for adding a GraphQL endpoint to your app +* [API (REST)](/lib/restapi/getting-started) - for adding a REST endpoint to your app +* [Authentication](/lib/auth/getting-started) - for managing your users +* [DataStore](/lib/datastore/getting-started) - for making it easier to program for a distributed data store for offline and online scenarios +* [Geo](/lib/geo/getting-started) - to use location data and map UI components. +* [Predictions](/lib/predictions/getting-started) - to detect text, images, and more! +* [Storage](/lib/storage/getting-started) - store complex objects like pictures and videos to the cloud. diff --git a/src/fragments/lib-legacy/project-setup/native_common/create-application/common.mdx b/src/fragments/lib-legacy/project-setup/native_common/create-application/common.mdx new file mode 100644 index 00000000000..d6ab049fa73 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/native_common/create-application/common.mdx @@ -0,0 +1,79 @@ +## Goal + +Setup a skeleton project so that Amplify categories can be added to it + +## Project Setup + +### 1. Create a new project + +import ios0 from "/src/fragments/lib/project-setup/ios/create-application/10_createProject.mdx"; + + + +import android1 from "/src/fragments/lib/project-setup/android/create-application/10_createProject.mdx"; + + + +import flutter2 from "/src/fragments/lib/project-setup/flutter/create-application/10_createProject.mdx"; + + + +### 2. Install Amplify Libraries + +import ios3 from "/src/fragments/lib/project-setup/ios/create-application/20_install_libraries.mdx"; + + + +import android4 from "/src/fragments/lib/project-setup/android/create-application/20_gradle.mdx"; + + + +import flutter5 from "/src/fragments/lib/project-setup/flutter/create-application/20_pubspec.mdx"; + + + +### 3. Provision the backend with Amplify CLI + +import ios6 from "/src/fragments/lib/project-setup/ios/create-application/30_provisionBackend.mdx"; + + + +import ios7 from "/src/fragments/lib/project-setup/ios/create-application/31_provisionBackend.mdx"; + + + +import android8 from "/src/fragments/lib/project-setup/android/create-application/30_provisionBackend.mdx"; + + + +import flutter9 from "/src/fragments/lib/project-setup/flutter/create-application/30_provisionBackend.mdx"; + + + +### 4. Initialize Amplify in the application + +import ios10 from "/src/fragments/lib/project-setup/ios/create-application/40_verifyAmplifyLibraries.mdx"; + + + +import android11 from "/src/fragments/lib/project-setup/android/create-application/40_verifyAmplifyLibraries.mdx"; + + + +import flutter12 from "/src/fragments/lib/project-setup/flutter/create-application/40_verifyAmplifyLibraries.mdx"; + + + +### Next steps + +import ios13 from "/src/fragments/lib/project-setup/native_common/create-application/50_nextSteps.mdx"; + + + +import android14 from "/src/fragments/lib/project-setup/native_common/create-application/50_nextSteps.mdx"; + + + +import flutter15 from "/src/fragments/lib/project-setup/flutter/create-application/50_nextSteps.mdx"; + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/project-setup/native_common/prereq/cliInstall.mdx b/src/fragments/lib-legacy/project-setup/native_common/prereq/cliInstall.mdx new file mode 100644 index 00000000000..d6ecd6345b3 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/native_common/prereq/cliInstall.mdx @@ -0,0 +1,5 @@ + + +import all0 from "/src/fragments/cli-install-block.mdx"; + + diff --git a/src/fragments/lib-legacy/project-setup/native_common/prereq/common_body.mdx b/src/fragments/lib-legacy/project-setup/native_common/prereq/common_body.mdx new file mode 100644 index 00000000000..c0613a77e89 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/native_common/prereq/common_body.mdx @@ -0,0 +1,72 @@ +## Sign up for an AWS account + +If you don't already have an AWS account, you'll need to create one in order to follow the steps outlined in this tutorial. + +[Create AWS Account](https://portal.aws.amazon.com/billing/signup?redirect_url=https%3A%2F%2Faws.amazon.com%2Fregistration-confirmation#/start) + +> There are no upfront charges or any term commitments to create an AWS account and signing up gives you immediate access to the AWS Free Tier. + +## Install and configure the Amplify CLI + +The Amplify Command Line Interface (CLI) is a unified toolchain to create AWS cloud services for your app. Let's go ahead and install the Amplify CLI. + +### Option 1: Watch the video guide + +Watch the video below to learn how to install and configure the Amplify CLI or skip to the next section to follow the step-by-step instructions. + + + +### Option 2: Follow the instructions + +import ios0 from "/src/fragments/lib/project-setup/native_common/prereq/cliInstall.mdx"; + + + +import android1 from "/src/fragments/lib/project-setup/native_common/prereq/cliInstall.mdx"; + + + +import flutter2 from "/src/fragments/lib/project-setup/flutter/prereq/cliInstall.mdx"; + + + + +Because we're installing the Amplify CLI globally, you might need to run the command above with sudo. + + +Now it's time to setup the Amplify CLI. Configure Amplify by running the following command: + +```bash +amplify configure +``` + +`amplify configure` will ask you to sign into the AWS Console. + +Once you're signed in, Amplify CLI will ask you to create an IAM user. +> Amazon IAM (Identity and Access Management) enables you to manage users and user permissions in AWS. You can learn more about Amazon IAM [here](https://aws.amazon.com/iam/). + +```console +Specify the AWS Region +? region: # Your preferred region +Specify the username of the new IAM user: +? user name: # User name for Amplify IAM user +Complete the user creation using the AWS console +``` + +Create a user with `AdministratorAccess-Amplify` to your account to provision AWS resources for you like AppSync, Cognito etc. + +![image](/images/user-creation.gif) + +Once the user is created, Amplify CLI will ask you to provide the `accessKeyId` and the `secretAccessKey` to connect Amplify CLI with your newly created IAM user. + +```console +Enter the access key of the newly created user: +? accessKeyId: # YOUR_ACCESS_KEY_ID +? secretAccessKey: # YOUR_SECRET_ACCESS_KEY +This would update/create the AWS Profile in your local machine +? Profile Name: # (default) + +Successfully set up the new user. +``` + +Next, we'll set up the app and initialize Amplify! diff --git a/src/fragments/lib-legacy/project-setup/native_common/prereq/common_header.mdx b/src/fragments/lib-legacy/project-setup/native_common/prereq/common_header.mdx new file mode 100644 index 00000000000..05637361a6d --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/native_common/prereq/common_header.mdx @@ -0,0 +1,5 @@ +Before we begin, make sure you have the following installed: + +- [Node.js](https://nodejs.org/) v14.x or later +- [npm](https://www.npmjs.com/get-npm) v6.14.4 or later +- [git](https://git-scm.com/) v2.14.1 or later diff --git a/src/fragments/lib-legacy/project-setup/native_common/prereq/flutter_null_safety.mdx b/src/fragments/lib-legacy/project-setup/native_common/prereq/flutter_null_safety.mdx new file mode 100644 index 00000000000..4acf8b59450 --- /dev/null +++ b/src/fragments/lib-legacy/project-setup/native_common/prereq/flutter_null_safety.mdx @@ -0,0 +1,6 @@ + + + +Amplify Flutter now supports Dart null safety. See the [null safety documentation](/lib/project-setup/null-safety) for details. + + diff --git a/src/fragments/lib-legacy/pubsub/js/connection-states.mdx b/src/fragments/lib-legacy/pubsub/js/connection-states.mdx new file mode 100644 index 00000000000..acde4bc9155 --- /dev/null +++ b/src/fragments/lib-legacy/pubsub/js/connection-states.mdx @@ -0,0 +1,10 @@ +#### Connection states + +- **`Connected`** - Connected and working with no issues. +- **`ConnectedPendingDisconnect`** - The connection has no active subscriptions and is disconnecting. +- **`ConnectedPendingKeepAlive`** - The connection is open, but has missed expected keep alive messages. +- **`ConnectedPendingNetwork`** - The connection is open, but the network connection has been disrupted. When the network recovers, the connection will continue serving traffic. +- **`Connecting`** - Attempting to connect. +- **`ConnectionDisrupted`** - The connection is disrupted and the network is available. +- **`ConnectionDisruptedPendingNetwork`** - The connection is disrupted and the network connection is unavailable. +- **`Disconnected`** - Connection has no active subscriptions and is disconnecting. diff --git a/src/fragments/lib-legacy/pubsub/js/getting-started.mdx b/src/fragments/lib-legacy/pubsub/js/getting-started.mdx new file mode 100644 index 00000000000..ccada3076b1 --- /dev/null +++ b/src/fragments/lib-legacy/pubsub/js/getting-started.mdx @@ -0,0 +1,112 @@ +The AWS Amplify PubSub category provides connectivity with cloud-based message-oriented middleware. You can use PubSub to pass messages between your app instances and your app's backend creating real-time interactive experiences. + +PubSub is available with **AWS IoT** and **Generic MQTT Over WebSocket Providers**. + + +With AWS IoT, AWS Amplify's PubSub automatically signs your HTTP requests when sending your messages. + + +## AWS IoT + +When used with `AWSIoTProvider`, PubSub is capable of signing request according to [Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). + +To use in your app, import `AWSIoTProvider`: + +```javascript +import { Amplify, PubSub } from 'aws-amplify'; +import { AWSIoTProvider } from '@aws-amplify/pubsub'; +``` + +Define your endpoint and region in your configuration: + +```javascript +// Apply plugin with configuration +Amplify.addPluggable(new AWSIoTProvider({ + aws_pubsub_region: '', + aws_pubsub_endpoint: 'wss://xxxxxxxxxxxxx.iot..amazonaws.com/mqtt', + })); +``` + +Find your `aws_pubsub_endpoint` by logging onto your **AWS Console**, choose **IoT Core** from the list of services, then choose *Settings* from the left navigation pane. + +### Step 1: Create IAM policies for AWS IoT + +To use PubSub with AWS IoT, you will need to create the necessary IAM policies in the AWS IoT Console, and attach them to your Amazon Cognito Identity. + +Go to IoT Core and choose *Secure* from the left navigation pane, and then *Policies* from the dropdown menu. Next, click *Create*. The following `myIoTPolicy` policy will allow full access to all the topics. + +![Alt text](/images/create-iot-policy.png) + +### Step 2: Attach your policy to your Amazon Cognito Identity + +The next step is attaching the policy to your *Cognito Identity*. + +You can retrieve the `Cognito Identity Id` of a logged in user with Auth Module: +```javascript + Auth.currentCredentials().then((info) => { + const cognitoIdentityId = info.identityId; + }); +``` + +Then, you need to send your *Cognito Identity Id* to the AWS backend and attach `myIoTPolicy`. You can do this with the following [AWS CLI](https://aws.amazon.com/cli/) command: + +```bash +aws iot attach-policy --policy-name 'myIoTPolicy' --target '' +``` + +### Step 3: Allow the Amazon Cognito Authenticated Role to access IoT Services + +For your Cognito Authenticated Role to be able to interact with **AWS IoT** it may be necessary to update its permissions, if you haven't done this before. +One way of doing this is to log to your **AWS Console**, select **CloudFormation** from the available services. Locate the parent stack of your solution: it is usually named `-`. +Select the **Resources** tab and tap on `AuthRole` **Physical ID**. +The IAM console will be opened in a new tab. Once there, tap on the button **Attach Policies**, then search `AWSIoTDataAccess` and `AWSIoTConfigAccess`, select them and tap on **Attach policy**. + +If you are using Cognito Groups the IAM role associated with that group also need the `AWSIoTDataAccess` and `AWSIoTConfigAccess` policies attached to it. + +> Failing to grant IoT related permissions to the Cognito Authenticated Role will result in errors similar to the following in your browser console: `errorCode: 8, errorMessage: AMQJS0008I Socket closed.` + +## Third Party MQTT Providers + +Import PubSub module and related service provider plugin to your app: + +```javascript +import { PubSub } from 'aws-amplify'; +import { MqttOverWSProvider } from "@aws-amplify/pubsub/lib/Providers"; +``` + +To configure your service provider with a service endpoint, add following code: +```javascript +// Apply plugin with configuration +Amplify.addPluggable(new MqttOverWSProvider({ + aws_pubsub_endpoint: 'wss://iot.eclipse.org:443/mqtt', +})); +``` + +You can integrate any MQTT Over WebSocket provider with your app. Click [here](https://docs.aws.amazon.com/iot/latest/developerguide/protocols.html#mqtt-ws) to learn more about MQTT Over WebSocket. + +## How to reconfigure PubSub providers during runtime + +Sometimes you need to reconfigure your PubSub provider when working with multiple concurrent PubSub providers, reconfiguring authentication states, or changing the IoT connection region. To reconfigure the PubSub provider, remove the existing provider using `removePluggable` and add an updated PubSub provider using `addPluggable`. + +```javascript +import { Amplify, PubSub } from 'aws-amplify'; +import { AWSIoTProvider } from '@aws-amplify/pubsub'; + +const pubsub = new PubSub({}); + +// Apply plugin with configuration +pubsub.addPluggable(new AWSIoTProvider({ + aws_pubsub_region: '', + aws_pubsub_endpoint: 'wss://xxxxxxxxxxxxx.iot..amazonaws.com/mqtt', +})); + +// Remove plugin using the provider name +pubsub.removePluggable('AWSIoTProvider'); + +// Apply plugin with new configuration +pubsub.addPluggable(new AWSIoTProvider({ + aws_pubsub_region: '', + aws_pubsub_endpoint: 'wss://xxxxxxxxxxxxx.iot..amazonaws.com/mqtt', +})); + +``` diff --git a/src/fragments/lib-legacy/pubsub/js/publish.mdx b/src/fragments/lib-legacy/pubsub/js/publish.mdx new file mode 100644 index 00000000000..f794fa51dbf --- /dev/null +++ b/src/fragments/lib-legacy/pubsub/js/publish.mdx @@ -0,0 +1,20 @@ +To send a message to a topic, use `publish()` method with your topic name and the message: +```javascript +await PubSub.publish('myTopic1', { msg: 'Hello to all subscribers!' }); +``` + +If multiple providers are defined in your app you can pass the message to a specific provider: +```javascript +await PubSub.publish('myTopic1', { msg: 'Hello to all subscribers!' }, { provider: 'AWSIoTProvider' }); +``` + +You can also publish a message to multiple topics: +```javascript +await PubSub.publish(['myTopic1','myTopic2'], { msg: 'Hello to all subscribers!' }); +``` + + + +**Note:** If you do not include a specific provider it will publish a message to all of the configured PubSub providers in your app. + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/pubsub/js/subunsub.mdx b/src/fragments/lib-legacy/pubsub/js/subunsub.mdx new file mode 100644 index 00000000000..620d15e480b --- /dev/null +++ b/src/fragments/lib-legacy/pubsub/js/subunsub.mdx @@ -0,0 +1,80 @@ +## Subscribe +### Subscribe to a topic + +In order to start receiving messages from your provider, you need to subscribe to a topic as follows; +```javascript +PubSub.subscribe('myTopic').subscribe({ + next: data => console.log('Message received', data), + error: error => console.error(error), + complete: () => console.log('Done'), +}); +``` + +If multiple providers are defined in your app you can include the specific provider you would like to subscribe to: +```javascript +PubSub.subscribe('myTopic', { provider: 'AWSIoTProvider' }).subscribe({ + //... +}); + +PubSub.subscribe('myTopic', { provider: 'MqttOverWSProvider' }).subscribe({ + //... +}); +``` + + + +**Note:** If you do not include a specific provider it will subscribe to all of the configured PubSub providers in your app. + + + +Following events will be triggered with `subscribe()` + +| Event | Description | +| :--------: | --------------------------------------------------------------------- | +| `next` | Triggered every time a message is successfully received for the topic | +| `error` | Triggered when subscription attempt fails | +| `complete` | Triggered when you unsubscribe from the topic | + +### Subscribe to multiple topics + +To subscribe for multiple topics, just pass a String array including the topic names: +```javascript +PubSub.subscribe(['myTopic1','myTopic1']).subscribe({ + //... +}); +``` + +## Unsubscribe + +To stop receiving messages from a topic, you can use `unsubscribe()` method: +```javascript +const sub1 = PubSub.subscribe('myTopicA').subscribe({ + next: data => console.log('Message received', data), + error: error => console.error(error), + complete: () => console.log('Done'), +}); + +sub1.unsubscribe(); +// You will no longer get messages for 'myTopicA' +``` + +## Subscription connection status updates + +Now that your application is setup and using pubsub subscriptions, you may want to know when the subscription is finally established, or reflect to your users when the subscription isn't healthy. You can monitor the connection state for changes via Hub. + +```typescript +import { CONNECTION_STATE_CHANGE, ConnectionState } from '@aws-amplify/pubsub'; +import { Hub } from 'aws-amplify'; + +Hub.listen('pubsub', (data: any) => { + const { payload } = data; + if (payload.event === CONNECTION_STATE_CHANGE) { + const connectionState = payload.data.connectionState as ConnectionState; + console.log(connectionState); + } +}); +``` + +import jsConnectionStates from '/src/fragments/lib/pubsub/js/connection-states.mdx'; + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/push-notifications/js/getting-started.mdx b/src/fragments/lib-legacy/push-notifications/js/getting-started.mdx new file mode 100644 index 00000000000..5f629657c26 --- /dev/null +++ b/src/fragments/lib-legacy/push-notifications/js/getting-started.mdx @@ -0,0 +1,240 @@ +Setup instructions are provided for Android and iOS, and configuration for both platforms can be included on the same React Native project. + +## Requirements +1. In order to use Amazon Pinpoint you need to setup credentials (keys or certificates) for your targeted mobile platform, e.g. Android and/or iOS. +2. Testing Push Notifications requires a physical device, because simulators or emulators won't be able to handle push notifications. +3. Push Notifications category is integrated with [AWS Amplify Analytics category](/lib/analytics/getting-started) to be able to track notifications. Make sure that you have configured the Analytics category in your app before configuring the Push Notifications category. + +## Setup for Android + +1. Make sure you have a [Firebase Project](https://console.firebase.google.com) and app setup. + +2. Get your push messaging credentials for Android in Firebase console. [Click here for instructions](/sdk/push-notifications/setup-push-service). + +3. Install dependencies: + + ```bash + npm install aws-amplify @aws-amplify/pushnotification + ``` + +4. Add your push messaging credentials (Server key) with Amplify CLI by using the following commands: + + ```bash + amplify add notifications + ``` + + Choose *FCM* when promoted: + + ```console + ? Choose the push notification channel to enable. + APNS + ❯ FCM + Email + SMS + ``` + + The CLI will prompt for your *Firebase credentials*, enter them respectively. + + + You can find said *Firebase credentials* by visiting https://console.firebase.google.com/project/[your-project-id]/settings/cloudmessaging or by navigating to (gear-next-to-project-name) > Project Settings > Cloud Messaging on the Firebase Console. + + Alternatively, you can set up Android push notifications in Amazon Pinpoint Console. [Click here for instructions](https://docs.aws.amazon.com/pinpoint/latest/developerguide/mobile-push-android.html). + +5. Enable your app in Firebase. To do that, follow those steps: + + + If you don't have an existing Firebase project, you need to create one in order to continue. + + + * Visit the [Firebase console](https://console.firebase.google.com), and click the Gear icon next to **Project Overview** and click **Project Settings**. + * Click **Add App**, if you have an existing app you can skip this step + * Choose **Add Firebase to your Android App** + * Add your package name i.e. **com.myProjectName** and click **Register App** + * Download *google-services.json* file and copy it under your `android/app` project folder. + + + + Make sure you have *google-services.json* file in place or you won't pass the build process. + + + +6. Open *android/build.gradle* file and perform following edits: + + - Add `classpath("com.google.gms:google-services:4.3.3")` in the `dependencies` under `buildscript`: + + ```gradle + buildscript { + ... + dependencies { + ... + classpath("com.google.gms:google-services:4.3.3") + ... + } + } + ``` + +7. Open *android/app/build.gradle* and perform following edits: + + - Add firebase libs to the `dependencies` section: + + ```gradle + dependencies { + ... + implementation "com.google.firebase:firebase-core:15.0.2" + implementation "com.google.firebase:firebase-messaging:15.0.2" + ... + } + ``` + - Add following configuration to the bottom of the file: + + ```gradle + apply plugin: "com.google.gms.google-services" + ``` + +8. Open *android/gradle/wrapper/gradle-wrapper.properties* update the Gradle `distributionUrl`: + + ```properties + distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip + ``` + +9. Open *android/app/src/main/AndroidManifest.xml* file and add the following configuration into `application` element. + + ```xml + + + + + + + + + + + + + + + + + + + + + + + + + ``` + +10. Configure Push Notifications category for your app as shown in [Configure your App](#configure-your-app) section. + +11. Run your app: + + ```bash + npx react-native run-android + ``` + +## Setup for iOS + +1. For setting up iOS push notifications, you need to download and install Xcode from [Apple Developer Center](https://developer.apple.com/xcode/). + +2. Setup iOS Push Notifications and create a p12 certificate as instructed here in [Amazon Pinpoint Developer Guide](https://docs.aws.amazon.com/pinpoint/latest/developerguide/apns-setup.html). + +3. Install dependencies and CocoaPods: + + ```bash + npm install aws-amplify @aws-amplify/pushnotification @react-native-community/push-notification-ios + ``` + ```bash + npx pod-install + ``` + +4. Enable notifications and add your p12 certificate with Amplify CLI by using the following commands: + + ```bash + amplify add notifications + ``` + + Choose *APNS* when promoted: + + ```console + ? Choose the push notification channel to enable. + > APNS + FCM + Email + SMS + ``` + + Choose *Certificate* when promoted: + + ```console + ? Choose authentication method used for APNs + > Certificate + Key + ``` + + The CLI will prompt for your *p12 certificate path*, enter it respectively. + +5. Open project in Xcode and make updates for `@react-native-community/push-notification-ios`: + - [Add Capabilities : Background Mode - Remote Notifications](https://github.com/react-native-community/push-notification-ios#add-capabilities--background-mode---remote-notifications) + - [Augment `AppDelegate`](https://github.com/react-native-community/push-notification-ios#augment-appdelegate) + +6. Update General App settings: + + - Set bundle identifier (with the one you create on your Apple Developer Account) + - Make sure you have logged in with your Apple Developer account on Xcode and have a Team selected for the target. + +7. Configure Push Notification module for your app as shown in [Configure your App](#configure-your-app) section. + +8. Run your app: + + ```bash + npx react-native run-ios --device + ``` + +## Configure your App + +The Push Notifications category is integrated with `Analytics` module to be able to track notifications. Make sure that you have configured the Analytics module in your app before configuring Push Notification module. + + + +If you don't have Analytics already enabled, see our [Analytics Developer Guide](/lib/analytics/getting-started) to add Analytics to your app. + + + +```javascript +import { Amplify } from 'aws-amplify'; +import PushNotification from '@aws-amplify/pushnotification'; +import PushNotificationIOS from '@react-native-community/push-notification-ios'; +import awsconfig from './aws-exports'; + +Amplify.configure(awsconfig); +``` + +### Configuration Options + +- `requestIOSPermissions` is an optional boolean flag which specifies whether or not to automatically request push notifications permissions in iOS when calling `PushNotification.configure` for the first time. If not provided, it defaults to `true`. When set to `false`, you may later call the method `PushNotification.requestIOSPermissions` at the explicit point in your application flow when you want to prompt the user for permissions. +- `appId` is optional and *only* needed if `aws-exports` doesn't contain `aws_mobile_analytics_app_id` or you are manually configuring each category inside `Amplify.configure()`. + +```javascript +Amplify.configure({ + ...awsconfig, + PushNotification: { + requestIOSPermissions: false + } +}); +``` +```javascript +Amplify.configure({ + Auth: { /* ... */ }, + Analytics: { /* ... */ }, + PushNotification: { + appId: 'XXXXXXXXXXabcdefghij1234567890ab', + requestIOSPermissions: false + } +}); +``` diff --git a/src/fragments/lib-legacy/push-notifications/js/overview.mdx b/src/fragments/lib-legacy/push-notifications/js/overview.mdx new file mode 100644 index 00000000000..95bdc1c0f6d --- /dev/null +++ b/src/fragments/lib-legacy/push-notifications/js/overview.mdx @@ -0,0 +1,6 @@ +The Push Notifications category allows you to integrate push notifications in your app with Amazon Pinpoint targeting, campaign, and journey management support. + +This guide provides step-by-step instructions to start working with push notifications in React Native with Amazon Pinpoint. Amazon Pinpoint helps you to monitor your app's usage, create messaging campaigns and journeys targeted to specific user segments or demographics, and collect interaction metrics with push notifications. + +> Ensure you have [installed and configured the Amplify CLI and library](/cli/start/install). + diff --git a/src/fragments/lib-legacy/push-notifications/js/reactnative.mdx b/src/fragments/lib-legacy/push-notifications/js/reactnative.mdx new file mode 100644 index 00000000000..8dafee77010 --- /dev/null +++ b/src/fragments/lib-legacy/push-notifications/js/reactnative.mdx @@ -0,0 +1,5 @@ + + +Push Notifications are currently supported only for **React Native**. For handling Web Push Notifications with Service Workers, visit our [Service Workers Guide](/lib/utilities/serviceworker#handling-a-push-notification). + + diff --git a/src/fragments/lib-legacy/push-notifications/js/testing.mdx b/src/fragments/lib-legacy/push-notifications/js/testing.mdx new file mode 100644 index 00000000000..0968d594ed0 --- /dev/null +++ b/src/fragments/lib-legacy/push-notifications/js/testing.mdx @@ -0,0 +1 @@ +You can create messaging campaigns and send push notifications to your app with Amazon Pinpoint! Just follow these instructions on [Amazon Pinpoint Developer Guide](https://docs.aws.amazon.com/pinpoint/latest/developerguide/getting-started-sampletest.html) for the next steps. \ No newline at end of file diff --git a/src/fragments/lib-legacy/push-notifications/js/working-with-api.mdx b/src/fragments/lib-legacy/push-notifications/js/working-with-api.mdx new file mode 100644 index 00000000000..d56bc7c4d80 --- /dev/null +++ b/src/fragments/lib-legacy/push-notifications/js/working-with-api.mdx @@ -0,0 +1,43 @@ +You can use `onNotification`, `onRegister` and `onNotificationOpened` event handlers to work with push notifications in your app: + +```javascript +// get the notification data when notification is received +PushNotification.onNotification((notification) => { + // Note that the notification object structure is different from Android and IOS + console.log('in app notification', notification); + + // required on iOS only (see fetchCompletionHandler docs: https://github.com/react-native-community/push-notification-ios#finish) + notification.finish(PushNotificationIOS.FetchResult.NoData); +}); + +// get the registration token +// This will only be triggered when the token is generated or updated. +PushNotification.onRegister((token) => { + console.log('in app registration', token); +}); + +// get the notification data when notification is opened +PushNotification.onNotificationOpened((notification) => { + console.log('the notification is opened', notification); +}); +``` + + + +The `onRegister` handler will only be triggered once when the token is generated or updated by the push provider i.e. Apple/Google (and when the app opened the first time). + + + +If you have configured your application not to automatically request iOS push notification permissions, you can use the `requestIOSPermissions` method to request them explicitly: + +```javascript +// request iOS push notification permissions +PushNotification.requestIOSPermissions(); + +// request a subset of iOS push notification permissions +PushNotification.requestIOSPermissions({ + alert: true, + badge: true, + sound: false, +}); +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/restapi/android/authz/22_none_headers.mdx b/src/fragments/lib-legacy/restapi/android/authz/22_none_headers.mdx new file mode 100644 index 00000000000..cc264894494 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/android/authz/22_none_headers.mdx @@ -0,0 +1,45 @@ +If you would like to add request headers, you can add it directly to the request + + + + +This implementation uses `CompletableFuture`, which requires `minSdkVersion >= 24`. + +```java +RestOptions options = RestOptions.builder() + .addHeaders(Collections.singletonMap("key", "value")) + .build(); +``` + + + + +This implementation uses `CompletableFuture`, which requires `minSdkVersion >= 24`. + +```kotlin +val options = RestOptions.builder() + .addHeaders(mapOf("key" to "value")) + .build() +``` + + + + +This implementation uses `CompletableFuture`, which requires `minSdkVersion >= 24`. + +```kotlin +val request = RestOptions.builder() + .addHeaders(mapOf("key" to "value")) + .build() +``` + + + + +``` +RestOptions options = RestOptions.builder() + .addHeaders(Collections.singletonMap("key", "value")) + .build(); +``` + + diff --git a/src/fragments/lib-legacy/restapi/android/delete.mdx b/src/fragments/lib-legacy/restapi/android/delete.mdx new file mode 100644 index 00000000000..e9b2f24ec84 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/android/delete.mdx @@ -0,0 +1,62 @@ +## DELETE requests + + + + +```java +RestOptions options = RestOptions.builder() + .addPath("/todo/1") + .build(); + +Amplify.API.delete(options, + response -> Log.i("MyAmplifyApp", "DELETE succeeded: " + response), + error -> Log.e("MyAmplifyApp", "DELETE failed.", error) +); +``` + + + + +```kotlin +val options = RestOptions.builder() + .addPath("/todo/1") + .build() + +Amplify.API.delete(options, + { Log.i("MyAmplifyApp", "DELETE succeeded: $it") }, + { Log.e("MyAmplifyApp", "DELETE failed.", it) } +) +``` + + + + +```kotlin +val request = RestOptions.builder() + .addPath("/todo/1") + .build() +try { + val response = Amplify.API.delete(request) + Log.i("MyAmplifyApp", "DELETE succeeded: $response") +} catch (error: ApiException) { + Log.e("MyAmplifyApp", "DELETE failed", error) +} +``` + + + + +```java +RestOptions options = RestOptions.builder() + .addPath("/todo/1") + .build(); + +RxAmplify.API.delete(options) + .subscribe( + response -> Log.i("MyAmplifyApp", "DELETE succeeded: " + response), + error -> Log.e("MyAmplifyApp", "DELETE failed.", error) + ); +``` + + + diff --git a/src/fragments/lib-legacy/restapi/android/fetch.mdx b/src/fragments/lib-legacy/restapi/android/fetch.mdx new file mode 100644 index 00000000000..2fcb9142ea2 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/android/fetch.mdx @@ -0,0 +1,177 @@ +## GET requests + +To make a GET request, first build a RestOptions object and then use the Amplify.API.get api to issue the request: + + + + +```java +void getTodo() { + RestOptions options = RestOptions.builder() + .addPath("/todo") + .build(); + + Amplify.API.get(options, + restResponse -> Log.i("MyAmplifyApp", "GET succeeded: " + restResponse), + apiFailure -> Log.e("MyAmplifyApp", "GET failed.", apiFailure) + ); +} +``` + + + + +```kotlin +private fun getTodo() { + val request = RestOptions.builder() + .addPath("/todo") + .build() + + Amplify.API.get(request, + { Log.i("MyAmplifyApp", "GET succeeded: $it") }, + { Log.e("MyAmplifyApp", "GET failed.", it) } + ) +} +``` + + + + +```kotlin +private suspend fun getTodo() { + val request = RestOptions.builder() + .addPath("/todo") + .build() + + try { + val response = Amplify.API.get(request) + Log.i("MyAmplifyApp", "GET succeeded: ${response.data}") + } catch (error: ApiException) { + Log.e("MyAmplifyApp", "GET failed", error) + } +} +``` + + + + +```java +void getTodo() { + RestOptions options = RestOptions.builder() + .addPath("/todo") + .build(); + + RxAmplify.API.get(options) + .subscribe( + restResponse -> Log.i("MyAmplifyApp", "GET succeeded: " + restResponse), + apiFailure -> Log.e("MyAmplifyApp", "GET failed.", apiFailure) + ); +} +``` + + + + +## Accessing query parameters & body in Lambda proxy function + +> To learn more about Lambda Proxy Integration, please visit [Amazon API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html). + +If you are using a REST API which is generated with Amplify CLI, your backend is created with Lambda Proxy Integration, and you can access your query parameters & body within your Lambda function via the *event* object: + +```javascript +exports.handler = function(event, context, callback) { + console.log(event.queryStringParameters); + console.log('body: ', event.body); +} +``` + +Alternatively, you can update your backend file which is located at `amplify/backend/function/[your-lambda-function]/app.js` with the middleware: + +```javascript +const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware'); +app.use(awsServerlessExpressMiddleware.eventContext()); +``` + +Accessing Query Parameters with Serverless Express + +In your request handler use `req.apiGateway.event` or `req.query`: + +```javascript +app.get('/todo', function(req, res) { + const query = req.query; + // or + // const query = req.apiGateway.event.queryStringParameters + res.json({ + event: req.apiGateway.event, // to view all event data + query: query + }); +}); +``` + +Then you can use query parameters in your path as follows: + + + + +```java +RestOptions options = RestOptions.builder() + .addPath("/todo") + .addQueryParameters(Collections.singletonMap("q", "test")) + .build(); + +Amplify.API.get(options, + response -> Log.i("MyAmplifyApp", "GET succeeded: " + response), + error -> Log.e("MyAmplifyApp", "GET failed.", error) +); +``` + + + + +```kotlin +val request = RestOptions.builder() + .addPath("/todo") + .addQueryParameters(mapOf("q" to "test")) + .build() + +Amplify.API.get(request, + { Log.i("MyAmplifyApp", "GET succeeded: $it") }, + { Log.e("MyAmplifyApp", "GET failed", it) } +) +``` + + + + +```kotlin +val request = RestOptions.builder() + .addPath("/todo") + .addQueryParameters(mapOf("q" to "test")) + .build() + +try { + val response = Amplify.API.get(request) + Log.i("MyAmplifyApp", "GET succeeded: $response.") +} catch (error: ApiException) { + Log.e("MyAmplifyApp", "GET failed", error) +} +``` + + + + +```java +RestOptions options = RestOptions.builder() + .addPath("/todo") + .addQueryParameters(Collections.singletonMap("q", "test")) + .build(); + +RxAmplify.API.get(options) + .subscribe( + response -> Log.i("MyAmplifyApp", "GET succeeded: " + response), + error -> Log.e("MyAmplifyApp", "GET failed.", error) + ); +``` + + + diff --git a/src/fragments/lib-legacy/restapi/android/getting-started.mdx b/src/fragments/lib-legacy/restapi/android/getting-started.mdx new file mode 100644 index 00000000000..43adabaf7f0 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/android/getting-started.mdx @@ -0,0 +1,100 @@ +The Amplify CLI deploys REST APIs and handlers using [Amazon API Gateway](http://docs.aws.amazon.com/apigateway/latest/developerguide/) and [AWS Lambda](http://docs.aws.amazon.com/lambda/latest/dg/). + +The API category will perform SDK code generation which when used with the `AWSMobileClient` can be used for creating signed requests for Amazon API Gateway when the service Authorization is set to `AWS_IAM` or when using a [Cognito User Pools Authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html). + +See the authentication section for more details for using the `AWSMobileClient` in your application. + +## Create new REST API + +In a terminal window, navigate to your project folder (the folder that contains your app `.Android Studioproj` file), and add the SDK to your app. + +```bash +cd ./YOUR_PROJECT_FOLDER +amplify add api +``` + +When prompted select the following options: + +```console +$ > REST +$ > Create a new Lambda function +$ > Serverless express function +$ > Restrict API access? Yes +$ > Who should have access? Authenticated and Guest users +``` + +When configuration of your API is complete, the CLI displays a message confirming that you have configured local CLI metadata for this category. You can confirm this by running `amplify status`. Finally deploy your changes to the cloud: + +```bash +amplify push +``` + +## Working with the API + +Next make a call using one of the HTTP verbs under `Amplify.API` such as a GET: + + + + +```java +RestOptions request = RestOptions.builder() + .addPath("/items") + .addQueryParameters(Collections.singletonMap("lang", "en_US")) + .build(); + +Amplify.API.get("myAPI", request, + response -> Log.i("ApiQuickStart", "GET succeeded: " + response.toString()), + failure -> Log.e("ApiQuickStart", "GET failed", failure) +); +``` + + + + +```kotlin +val request = RestOptions.builder() + .addPath("/items") + .addQueryParameters(mapOf("lang" to "en_US")) + .build() + +Amplify.API.get("myAPI", request, + { Log.i("ApiQuickStart", "GET succeeded: $it") }, + { Log.e("ApiQuickStart", "GET failed", it) } +) +``` + + + + +```kotlin +val request = RestOptions.builder() + .addPath("/items") + .addQueryParameters(mapOf("lang" to "en_US")) + .build() + +try { + val response = Amplify.API.get("myAPI", request) + Log.i("ApiQuickStart", "GET succeeded: $response") +} catch (error: ApiException) { + Log.e("ApiQuickStart", "GET failed", error) +} +``` + + + + +```java +RestOptions request = RestOptions.builder() + .addPath("/items") + .addQueryParameters(Collections.singletonMap("lang", "en_US")) + .build(); + +RxAmplify.API.get("myAPI", request) + .subscribe( + response -> Log.i("ApiQuickStart", "GET succeeded: " + response.toString()), + failure -> Log.e("ApiQuickStart", "GET failed", failure) + ); +``` + + + diff --git a/src/fragments/lib-legacy/restapi/android/getting-started/10_preReq.mdx b/src/fragments/lib-legacy/restapi/android/getting-started/10_preReq.mdx new file mode 100644 index 00000000000..a9c991e58da --- /dev/null +++ b/src/fragments/lib-legacy/restapi/android/getting-started/10_preReq.mdx @@ -0,0 +1,2 @@ +* An Android application targeting Android API level 16 (Android 4.1) or above + * For a full example of creating Android project, please follow the [project setup walkthrough](/lib/project-setup/create-application) diff --git a/src/fragments/lib-legacy/restapi/android/getting-started/11_amplifyInit.mdx b/src/fragments/lib-legacy/restapi/android/getting-started/11_amplifyInit.mdx new file mode 100644 index 00000000000..ca15cac06be --- /dev/null +++ b/src/fragments/lib-legacy/restapi/android/getting-started/11_amplifyInit.mdx @@ -0,0 +1,52 @@ +To start provisioning api resources in the backend, go to your project directory and **execute the command**: + +```bash +amplify add api +``` + +Enter the following when prompted: +```console +? Please select from one of the below mentioned services: + `REST` +? Provide a friendly name for your resource to be used as a label for this category in the project: + `api` +? Provide a path (e.g., /book/{isbn}): + `/todo` +? Choose a Lambda source + `Create a new Lambda function` +? Provide an AWS Lambda function name: + `todo` +? Choose the runtime that you want to use: + `NodeJS` +? Choose the function template that you want to use: + `Serverless ExpressJS function (Integration with API Gateway)` +? Do you want to configure advanced settings? + `No` +? Do you want to edit the local lambda function now? + `No` +? Restrict API access + `Yes` +? Who should have access? + `Authenticated and Guest users` +? What kind of access do you want for Authenticated users? + `create, read, update, delete` +? What kind of access do you want for Guest users? + `create, read, update, delete` +Successfully added auth resource locally. +? Do you want to add another path? + `No` +``` + +To push your changes to the cloud, **execute the command**: + +```bash +amplify push +``` + +Upon completion, `amplifyconfiguration.json` should be updated to reference provisioned backend storage resources. Note that these files should already be a part of your project if you followed the [Project setup walkthrough](/lib/project-setup/create-application). + + + +The current version of the Amplify CLI will create classes intended for use with the AWS SDK for Android in your project. If you see compilation errors relating to `com.amazonaws.mobileconnectors.apigateway.annotation`, please delete the directory with the name of your API in Android Studio. + + diff --git a/src/fragments/lib-legacy/restapi/android/getting-started/20_installLib.mdx b/src/fragments/lib-legacy/restapi/android/getting-started/20_installLib.mdx new file mode 100644 index 00000000000..edcf9d7446c --- /dev/null +++ b/src/fragments/lib-legacy/restapi/android/getting-started/20_installLib.mdx @@ -0,0 +1,12 @@ +Expand **Gradle Scripts**, open **build.gradle (Module: app)**. You will already have configured Amplify by following the steps in the [Project Setup walkthrough](/lib/project-setup/create-application). + +Add API by adding these libraries into the `dependencies` block: +```groovy +dependencies { + implementation 'com.amplifyframework:aws-api:ANDROID_VERSION' + implementation 'com.amplifyframework:aws-auth-cognito:ANDROID_VERSION' + +} +``` + +Click **Sync Now**. diff --git a/src/fragments/lib-legacy/restapi/android/getting-started/30_initapi.mdx b/src/fragments/lib-legacy/restapi/android/getting-started/30_initapi.mdx new file mode 100644 index 00000000000..f03c9b89a49 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/android/getting-started/30_initapi.mdx @@ -0,0 +1,95 @@ +To initialize the Amplify Auth and API categories you call `Amplify.addPlugin()` method for each category. To complete initialization call `Amplify.configure()`. + +Add the following code to your `onCreate()` method in your application class: + + + + +```java +Amplify.addPlugin(new AWSApiPlugin()); +Amplify.addPlugin(new AWSCognitoAuthPlugin()); +``` + +Your class will look like this: + +```java +public class MyAmplifyApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + try { + // Add these lines to add the `AWSApiPlugin` and `AWSCognitoAuthPlugin` + Amplify.addPlugin(new AWSApiPlugin()); + Amplify.addPlugin(new AWSCognitoAuthPlugin()); + Amplify.configure(getApplicationContext()); + + Log.i("MyAmplifyApp", "Initialized Amplify."); + } catch (AmplifyException error) { + Log.e("MyAmplifyApp", "Could not initialize Amplify.", error); + } + } +} +``` + + + + +```kotlin +Amplify.addPlugin(AWSApiPlugin()) +Amplify.addPlugin(AWSCognitoAuthPlugin()) +``` + +Your class will look like this: + +```kotlin +class MyAmplifyApp : Application() { + override fun onCreate() { + super.onCreate() + + try { + // Add these lines to add the `AWSApiPlugin` and `AWSCognitoAuthPlugin` + Amplify.addPlugin(AWSApiPlugin()) + Amplify.addPlugin(AWSCognitoAuthPlugin()) + Amplify.configure(applicationContext) + + Log.i("MyAmplifyApp", "Initialized Amplify.") + } catch (error: AmplifyException) { + Log.e("MyAmplifyApp", "Could not initialize Amplify.", error) + } + } +} +``` + + + + +```java +RxAmplify.addPlugin(new AWSApiPlugin()); +RxAmplify.addPlugin(new AWSCognitoAuthPlugin()); +``` + +Your class will look like this: + +```java +public class MyAmplifyApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + try { + // Add these lines to add the `AWSApiPlugin` and `AWSCognitoAuthPlugin` + RxAmplify.addPlugin(new AWSApiPlugin()); + RxAmplify.addPlugin(new AWSCognitoAuthPlugin()); + RxAmplify.configure(getApplicationContext()); + + Log.i("MyAmplifyApp", "Initialized Amplify."); + } catch (AmplifyException error) { + Log.e("MyAmplifyApp", "Could not initialize Amplify.", error); + } + } +} +``` + + + diff --git a/src/fragments/lib-legacy/restapi/android/getting-started/40_postTodo.mdx b/src/fragments/lib-legacy/restapi/android/getting-started/40_postTodo.mdx new file mode 100644 index 00000000000..89cb1a6ac8c --- /dev/null +++ b/src/fragments/lib-legacy/restapi/android/getting-started/40_postTodo.mdx @@ -0,0 +1,64 @@ + + + +```java +RestOptions options = RestOptions.builder() + .addPath("/todo") + .addBody("{\"name\":\"Mow the lawn\"}".getBytes()) + .build(); + +Amplify.API.post(options, + response -> Log.i("MyAmplifyApp", "POST succeeded: " + response), + error -> Log.e("MyAmplifyApp", "POST failed.", error) +); +``` + + + + +```kotlin +val options = RestOptions.builder() + .addPath("/todo") + .addBody("{\"name\":\"Mow the lawn\"}".getBytes()) + .build() + +Amplify.API.post(options, + { Log.i("MyAmplifyApp", "POST succeeded: $it") }, + { Log.e("MyAmplifyApp", "POST failed", it) } +) +``` + + + + +```kotlin +val request = RestOptions.builder() + .addPath("/todo") + .addBody("{\"name\":\"Mow the lawn\"}".toByteArray()) + .build() +try { + val response = Amplify.API.post(request) + Log.i("MyAmplifyApp", "POST succeeded: $response") +} catch (error: ApiException) { + Log.e("MyAmplifyApp", "POST failed", error) +} +``` + + + + +```java +RestOptions options = RestOptions.builder() + .addPath("/todo") + .addBody("{\"name\":\"Mow the lawn\"}".getBytes()) + .build(); + +RxAmplify.API.post(options) + .subscribe( + response -> Log.i("MyAmplifyApp", "POST succeeded: " + response), + error -> Log.e("MyAmplifyApp", "POST failed.", error) + ); +``` + + + diff --git a/src/fragments/lib-legacy/restapi/android/update.mdx b/src/fragments/lib-legacy/restapi/android/update.mdx new file mode 100644 index 00000000000..dbd5f722471 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/android/update.mdx @@ -0,0 +1,71 @@ +## PUT requests + +To update an item via the API endpoint: + + + + +```java +RestOptions options = RestOptions.builder() + .addPath("/todo/1") + .addBody("{\"name\":\"Mow the lawn\"}".getBytes()) + .build(); + +Amplify.API.put(options, + response -> Log.i("MyAmplifyApp", "PUT succeeded: " + response), + error -> Log.e("MyAmplifyApp", "PUT failed.", error) +); +``` + + + + +```kotlin +val request = RestOptions.builder() + .addPath("/todo/1") + .addBody("{\"name\":\"Mow the lawn\"}".getBytes()) + .build() + +Amplify.API.put(request, + { Log.i("MyAmplifyApp", "PUT succeeded: $it") }, + { Log.e("MyAmplifyApp", "PUT failed", it) } +) +``` + + + + +```kotlin +val request = RestOptions.builder() + .addPath("/todo/1") + .addBody(JSONObject() + .put("name", "Mow the lawn") + .toString() + .toByteArray()) + .build() +try { + val response = Amplify.API.put(request) + Log.i("MyAmplifyApp", "PUT succeeded: $response") +} catch (error: ApiException) { + Log.e("MyAmplifyApp", "PUT failed", it) +} +``` + + + + +```java +RestOptions options = RestOptions.builder() + .addPath("/todo/1") + .addBody("{\"name\":\"Mow the lawn\"}".getBytes()) + .build(); + +RxAmplify.API.put(options) + .subscribe( + response -> Log.i("MyAmplifyApp", "PUT succeeded: " + response), + error -> Log.e("MyAmplifyApp", "PUT failed.", error) + ); +``` + + + diff --git a/src/fragments/lib-legacy/restapi/existing-resources.mdx b/src/fragments/lib-legacy/restapi/existing-resources.mdx new file mode 100644 index 00000000000..cd4409b011f --- /dev/null +++ b/src/fragments/lib-legacy/restapi/existing-resources.mdx @@ -0,0 +1,27 @@ +Existing Amazon API Gateway resources can be used with the Amplify Libraries by referencing your API Gateway **endpoint** and configuring authorization in your `amplifyconfiguration.json` file. + +```json +{ + "api": { + "plugins": { + "awsAPIPlugin": { + "[API NAME]": { + "endpointType": "REST", + "endpoint": "[API GATEWAY ENDPOINT]", + "region": "[REGION]", + "authorizationType": "[AUTHORIZATION TYPE]", + ... + } + } + } + } +} +``` + +- **API NAME**: Friendly name for the API (e.g., *api*) + - **endpoint**: The HTTPS endpoint of the API (e.g. *https://aaaaaaaaaa.execute-api.us-east-1.amazonaws.com/api*) + - **region**: AWS Region where the resources are provisioned (e.g. *us-east-1*) + - **authorizationType**: Authorization mode for accessing the API. This can be one of: `NONE`, `AWS_IAM`, `AMAZON_COGNITO_USER_POOLS`, or `API_KEY`. Each mode with the exception of `NONE` requires additional configuration parameters. See [Configure authorization modes](/lib/restapi/authz) for details. + +Note that before you can add an AWS resource to your application, the application must have the Amplify libraries installed. If you need to perform this step, see [Install Amplify Libraries](/lib/project-setup/create-application#n2-install-amplify-libraries). + diff --git a/src/fragments/lib-legacy/restapi/flutter/authz.mdx b/src/fragments/lib-legacy/restapi/flutter/authz.mdx new file mode 100644 index 00000000000..1aa7ae8d406 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/flutter/authz.mdx @@ -0,0 +1,110 @@ +When determining the authorization mode for your REST endpoint, there are a few built in options and customizations you can use. + +## IAM Authorization + +By default, the API will be using IAM authorization and the requests will be signed for you automatically. IAM authorization has two modes: one using an **unauthenticated** role, and one using an **authenticated** role. When the user has not signed in through `Amplify.Auth.signIn`, the unauthenticated role is used by default. Once the user has signed in, the authenticate role is used, instead. + +When you created your REST API with the Amplify CLI, you were asked if you wanted to restrict access. If you selected **no**, then the unauthenticated role will have access to the API. If you selected **yes**, you would have configured more fine grain access to your API. + +### Unauthenticated Requests + +You can use the API category to access API Gateway endpoints that don't require authentication. In this case, you need to allow unauthenticated identities in your Amazon Cognito Identity Pool settings. For more information, please see the [Amazon Cognito developer documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/identity-pools.html#enable-or-disable-unauthenticated-identities). + +When your API is configured to use IAM as the authorization type, your requests will automatically have IAM credentials added to the headers of outgoing requests. You can verify that IAM is being used as the authorization type by inspecting the `authorizationType` associated with your API in `amplifyconfiguration.dart`: + +```json +{ + "awsAPIPlugin": { + "": { + "endpointType": "REST", + "endpoint": "YOUR-REST-ENDPOINT", + "region": "us-west-2", + "authorizationType": "AWS_IAM" + } + } +} +``` + +## API Key + +If you want to configure a public REST API, you can set an API key in Amazon API Gateway. Then, update your `amplifyconfiguration.dart` to reference it. The value associated to the `"authorizationType"` key should be `"API_KEY"`. Also include an `"API_KEY"` as a key, and set its value to whatever your configured in API Gateway. + +```json +{ + "awsAPIPlugin": { + "": { + "endpointType": "REST", + "endpoint": "YOUR-REST-ENDPOINT", + "region": "us-west-2", + "authorizationType": "API_KEY", + "apiKey": "YOUR_API_KEY" + } + } +} +``` + +## Cognito User pool authorization + +If you set up the API Gateway with a custom authorization with a Cognito User pool, then you can set the `amplifyconfiguration.dart` to use `AMAZON_COGNITO_USER_POOLS `. + +```json +{ + "awsAPIPlugin": { + "": { + "endpointType": "REST", + "endpoint": "YOUR-REST-ENDPOINT", + "region": "us-west-2", + "authorizationType": "AMAZON_COGNITO_USER_POOLS" + } + } +} +``` + +Your `amplifyconfiguration.dart` should contain Cognito configuration in the `awsCognitoAuthPlugin` block, including details about your Cognito user pool: +```json +{ + "CognitoUserPool": { + "Default": { + "PoolId": "YOUR-POOL-ID", + "AppClientId": "YOUR-APP-CLIENT-ID", + "AppClientSecret": "YOUR-APP-CLIENT-SECRET", + "Region": "us-east-1" + } + }, + "CredentialsProvider": { + "CognitoIdentity": { + "Default": { + "PoolId": "YOUR-COGNITO-IDENTITY-POOLID", + "Region": "us-east-1" + } + } + } +} +``` + +With this configuration, your access token will automatically be included in outbound requests to your API, as an `Authorization` header. For more details on how to configure the API Gateway with the custom authorization, see [this](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html) + +## OIDC + + + +OIDC is not yet supported in REST on Android, but is available in GraphQL and DataStore. Please follow [the Github issue](https://github.com/aws-amplify/amplify-flutter/issues/978) for the latest updates. + + + +import flutter0 from "/src/fragments/lib/graphqlapi/flutter/authz/20_oidc.mdx"; + + + +## Customizing HTTP request headers + +To use custom headers on your HTTP request, you need to add these to Amazon API Gateway first. For more info about configuring headers, please visit [Amazon API Gateway Developer Guide](http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html) + +If you used the Amplify CLI to create your API, you can enable custom headers by following these steps: + +1. Visit [Amazon API Gateway console](https://aws.amazon.com/api-gateway/). +3. In the Amazon API Gateway console, click on the path you want to configure (e.g. /{proxy+}) +4. Click the *Actions* dropdown menu and select **Enable CORS** +5. Add your custom header (e.g. my-custom-header) on the text field Access-Control-Allow-Headers, separated by commas, like: 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,my-custom-header'. +6. Click on 'Enable CORS and replace existing CORS headers' and confirm. +7. Finally, similar to step 3, click the Actions drop-down menu and then select **Deploy API**. Select **Development** on deployment stage and then **Deploy**. (Deployment could take a couple of minutes). diff --git a/src/fragments/lib-legacy/restapi/flutter/delete.mdx b/src/fragments/lib-legacy/restapi/flutter/delete.mdx new file mode 100644 index 00000000000..9761f07e821 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/flutter/delete.mdx @@ -0,0 +1,16 @@ +## DELETE requests + +```dart +Future deleteTodo() async { + try { + const options = RestOptions(path: '/todo'); + final restOperation = Amplify.API.delete(restOptions: options); + final response = await restOperation.response; + print('DELETE call succeeded'); + print(String.fromCharCodes(response.data)); + } on ApiException catch (e) { + print('DELETE call failed: $e'); + } +} +``` + diff --git a/src/fragments/lib-legacy/restapi/flutter/fetch.mdx b/src/fragments/lib-legacy/restapi/flutter/fetch.mdx new file mode 100644 index 00000000000..87afb14ea79 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/flutter/fetch.mdx @@ -0,0 +1,71 @@ +## GET requests + +To make a GET request, first build a `RestOptions` object and then use the `Amplify.API.get` API to issue the request: + +```dart +Future getTodo() async { + try { + const options = RestOptions(path: '/todo'); + final restOperation = Amplify.API.get(restOptions: options); + final response = await restOperation.response; + print('GET call succeeded: ${response.data}'); + } on ApiException catch (e) { + print('GET call failed: $e'); + } +} +``` + +## Accessing query parameters & body in Lambda proxy function + +> To learn more about Lambda Proxy Integration, please visit [Amazon API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html). + +If you are using a REST API which is generated with Amplify CLI, your backend is created with Lambda Proxy Integration, and you can access your query parameters & body within your Lambda function via the *event* object: + +```javascript +exports.handler = function(event, context, callback) { + console.log(event.queryStringParameters); + console.log('body: ', event.body); +} +``` + +In case you do not have it already, alternatively, you can update your backend file which is located at `amplify/backend/function/[your-lambda-function]/src/app.js` with the middleware: + +```javascript +const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware'); +app.use(awsServerlessExpressMiddleware.eventContext()); +``` + +Accessing Query Parameters with Serverless Express + +In your request handler use `req.apiGateway.event` or `req.query`: + +```javascript +app.get('/todo', function(req, res) { + const query = req.query; + // or + // const query = req.apiGateway.event.queryStringParameters + res.json({ + event: req.apiGateway.event, // to view all event data + query: query + }); +}); +``` + +Then you can use query parameters in your path as follows: + +```dart +Future getTodo() async { + try { + const options = RestOptions( + path: '/todo', + queryParameters: {'q' : 'test'}, + ); + final restOperation = Amplify.API.get(restOptions: options); + final response = await restOperation.response; + print('GET call succeeded'); + print(String.fromCharCodes(response.data)); + } on ApiException catch (e) { + print('GET call failed: $e'); + } +} +``` diff --git a/src/fragments/lib-legacy/restapi/flutter/getting-started/10_preReq.mdx b/src/fragments/lib-legacy/restapi/flutter/getting-started/10_preReq.mdx new file mode 100644 index 00000000000..7829d1f7a1a --- /dev/null +++ b/src/fragments/lib-legacy/restapi/flutter/getting-started/10_preReq.mdx @@ -0,0 +1,5 @@ +* [Install and configure Amplify CLI](https://docs.amplify.aws/cli/start/install) +* A Flutter application targeting Flutter SDK >= 2.10.0 (stable version) with Amplify libraries integrated + * An iOS configuration targeting at least iOS 11.0 + * An Android configuration targeting at least Android API level 21 (Android 5.0) or above + * For a full example please follow the [project setup walkthrough](/lib/project-setup/create-application) diff --git a/src/fragments/lib-legacy/restapi/flutter/getting-started/11_amplifyInit.mdx b/src/fragments/lib-legacy/restapi/flutter/getting-started/11_amplifyInit.mdx new file mode 100644 index 00000000000..bc76e204d4f --- /dev/null +++ b/src/fragments/lib-legacy/restapi/flutter/getting-started/11_amplifyInit.mdx @@ -0,0 +1,43 @@ +To start provisioning API resources in the backend, go to your project directory and **execute the command**: + +```bash +amplify add api +``` + +Enter the following when prompted: +```console +? Please select from one of the below mentioned services: + `REST` +? Provide a friendly name for your resource to be used as a label for this category in the project: + `api` +? Provide a path (e.g., /book/{isbn}): + `/todo` +? Choose a Lambda source + `Create a new Lambda function` +? Provide a friendly name for your resource to be used as a label for this category in the project: + `todo` +? Provide the AWS Lambda function name: + `todo` +? Choose the function runtime that you want to use: + `NodeJS` +? Choose the function template that you want to use: + `Serverless ExpressJS function (Integration with API Gateway)` +? Do you want to access other resources created in this project from your Lambda function? + `No` +? Do you want to invoke this function on a recurring schedule? + `No` +? Do you want to edit the local lambda function now? + `No` +? Restrict API access + `No` +? Do you want to add another path? + `No` +``` + +To push your changes to the cloud, **execute the command**: + +```bash +amplify push +``` + +Upon completion, `amplifyconfiguration.dart` should be updated to reference provisioned backend storage resources. Note that this file should already be a part of your project if you followed the [Project setup walkthrough](/lib/project-setup/create-application). diff --git a/src/fragments/lib-legacy/restapi/flutter/getting-started/20_installLib.mdx b/src/fragments/lib-legacy/restapi/flutter/getting-started/20_installLib.mdx new file mode 100644 index 00000000000..1aabdf9b300 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/flutter/getting-started/20_installLib.mdx @@ -0,0 +1,13 @@ +Add the following dependencies to your `pubspec.yaml` file and install dependencies when asked. Please keep in mind that Auth plugin is needed for IAM authorization mode, which is default for REST API: + +```yaml +environment: + sdk: ">=2.15.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + amplify_flutter: ^0.6.0 + amplify_api: ^0.6.0 + amplify_auth_cognito: ^0.6.0 +``` diff --git a/src/fragments/lib-legacy/restapi/flutter/getting-started/30_initapi.mdx b/src/fragments/lib-legacy/restapi/flutter/getting-started/30_initapi.mdx new file mode 100644 index 00000000000..b651022a208 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/flutter/getting-started/30_initapi.mdx @@ -0,0 +1,64 @@ +To initialize the Amplify API category you call `Amplify.addPlugin()` method. To complete initialization call `Amplify.configure()`. + +Your code should look like this: + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:flutter/material.dart'; + +import 'amplifyconfiguration.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void initState() { + super.initState(); + _configureAmplify(); + } + + Future _configureAmplify() async { + // Add the following line to add API plugin to your app. + // Auth plugin needed for IAM authorization mode, which is default for REST API. + final auth = AmplifyAuthCognito(); + final api = AmplifyAPI(); + await Amplify.addPlugins([api, auth]); + + try { + await Amplify.configure(amplifyconfig); + } on AmplifyAlreadyConfiguredException { + safePrint( + 'Tried to reconfigure Amplify; this can occur when your app restarts on Android.'); + } + } + + Future onTestApi() async { + // Edit this function with next steps. + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Padding( + padding: const EdgeInsets.all(20.0), + child: ElevatedButton( + onPressed: onTestApi, + child: const Text('Rest API'), + ), + ), + ), + ); + } +} +``` + diff --git a/src/fragments/lib-legacy/restapi/flutter/getting-started/40_postTodo.mdx b/src/fragments/lib-legacy/restapi/flutter/getting-started/40_postTodo.mdx new file mode 100644 index 00000000000..1f189a2aee6 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/flutter/getting-started/40_postTodo.mdx @@ -0,0 +1,20 @@ +```dart +import 'dart:typed_data'; + +Future onTestApi() async { + try { + final options = RestOptions( + path: '/todo', + body: Uint8List.fromList('{\'name\':\'Mow the lawn\'}'.codeUnits) + ); + final restOperation = Amplify.API.post( + restOptions: options + ); + final response = await restOperation.response; + print('POST call succeeded'); + print(String.fromCharCodes(response.data)); + } on ApiException catch (e) { + print('POST call failed: $e'); + } +} +``` diff --git a/src/fragments/lib-legacy/restapi/flutter/update.mdx b/src/fragments/lib-legacy/restapi/flutter/update.mdx new file mode 100644 index 00000000000..00a9b0d7564 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/flutter/update.mdx @@ -0,0 +1,20 @@ +## PUT requests + +To update an item via the API endpoint: + +```dart +Future updateTodo() async { + try { + final options = RestOptions( + path: '/todo', + body: Uint8List.fromList('{\'name\':\'Mow the lawn\'}'.codeUnits), + ); + final restOperation = Amplify.API.put(restOptions: options); + final response = await restOperation.response; + print('PUT call succeeded'); + print(String.fromCharCodes(response.data)); + } on ApiException catch (e) { + print('PUT call failed: $e'); + } +} +``` diff --git a/src/fragments/lib-legacy/restapi/ios/authz/22_none_headers.mdx b/src/fragments/lib-legacy/restapi/ios/authz/22_none_headers.mdx new file mode 100644 index 00000000000..59e4ccc9f57 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/ios/authz/22_none_headers.mdx @@ -0,0 +1,5 @@ +If you would like to add request headers, you can add it directly to the request +```swift +let headers: [String: String] = ["headerField": "headerValue"] +let request = RESTRequest(headers: headers) +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/restapi/ios/delete.mdx b/src/fragments/lib-legacy/restapi/ios/delete.mdx new file mode 100644 index 00000000000..d5b79bc13cc --- /dev/null +++ b/src/fragments/lib-legacy/restapi/ios/delete.mdx @@ -0,0 +1,46 @@ +## DELETE data + + + + + +```swift +func deleteTodo() { + let request = RESTRequest(path: "/todo") + Amplify.API.delete(request: request) { result in + switch result { + case .success(let data): + let str = String(decoding: data, as: UTF8.self) + print("Success \(str)") + case .failure(let apiError): + print("Failed", apiError) + } + } +} +``` + + + + + +```swift +func deleteTodo() -> AnyCancellable { + let request = RESTRequest(path: "/todo") + let sink = Amplify.API.delete(request: request) + .resultPublisher + .sink { + if case let .failure(apiError) = $0 { + print("Failed", apiError) + } + } + receiveValue: { data in + let str = String(decoding: data, as: UTF8.self) + print("Success \(str)") + } + return sink +} +``` + + + + diff --git a/src/fragments/lib-legacy/restapi/ios/fetch.mdx b/src/fragments/lib-legacy/restapi/ios/fetch.mdx new file mode 100644 index 00000000000..2961121d4cb --- /dev/null +++ b/src/fragments/lib-legacy/restapi/ios/fetch.mdx @@ -0,0 +1,146 @@ +## GET requests + +To make a GET request, first create a RESTRequest object and then use the Amplify.API.get api to issue the request: + + + + + +```swift +func getTodo() { + let request = RESTRequest(path: "/todo") + Amplify.API.get(request: request) { result in + switch result { + case .success(let data): + let str = String(decoding: data, as: UTF8.self) + print("Success \(str)") + case .failure(let apiError): + print("Failed", apiError) + } + } +} +``` + + + + + +```swift +func getTodo() -> AnyCancellable { + let request = RESTRequest(path: "/todo") + let sink = Amplify.API.get(request: request) + .resultPublisher + .sink { + if case let .failure(apiError) = $0 { + print("Failed", apiError) + } + } + receiveValue: { data in + let str = String(decoding: data, as: UTF8.self) + print("Success \(str)") + } + return sink +} +``` + + + + + +## Handling non-2xx HTTP responses + +When your service returns a non-2xx HTTP status code in the response, the API call will result in a failure that you can handle in your app. The response body can be accessed from the `body: Data?` property. For example, when the `APIError` is an `.httpStatusError(StatusCode, HTTPURLResponse)`, then access the response body by type casting the response to an `AWSHTTPURLResponse` and retrieve the `body` field. + +```swift +if case let .httpStatusError(statusCode, response) = error, + let awsResponse = response as? AWSHTTPURLResponse, + let responseBody = awsResponse.body +{ + print("Response contains a \(responseBody.count) byte long response body") +} +``` + +## Accessing query parameters & body in Lambda proxy function + +> To learn more about Lambda Proxy Integration, please visit [Amazon API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html). + +If you are using a REST API which is generated with Amplify CLI, your backend is created with Lambda Proxy Integration, and you can access your query parameters & body within your Lambda function via the *event* object: + +```javascript +exports.handler = function(event, context, callback) { + console.log(event.queryStringParameters); + console.log('body: ', event.body); +} +``` + +Alternatively, you can update your backend file which is located at `amplify/backend/function/[your-lambda-function]/app.js` with the middleware: + +```javascript +const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware'); +app.use(awsServerlessExpressMiddleware.eventContext()); +``` + +## Accessing query parameters with Serverless Express + +In your request handler use `req.apiGateway.event` or `req.query`: + +```javascript +app.get('/todo', function(req, res) { + const query = req.query; + // or + // const query = req.apiGateway.event.queryStringParameters + res.json({ + event: req.apiGateway.event, // to view all event data + query: query + }); +}); +``` + +Then you can use query parameters in your path as follows: + + + + + +```swift +func getTodo() { + let queryParameters = ["q":"test"] + let request = RESTRequest(path: "/todo", queryParameters: queryParameters) + Amplify.API.get(request: request) { result in + switch result { + case .success(let data): + let str = String(decoding: data, as: UTF8.self) + print("Success \(str)") + case .failure(let apiError): + print("Failed", apiError) + } + } +} +``` + + + + + +```swift +func getTodo() -> AnyCancellable { + let queryParameters = ["q":"test"] + let request = RESTRequest(path: "/todo", queryParameters: queryParameters) + let sink = Amplify.API.get(request: request) + .resultPublisher + .sink { + if case let .failure(apiError) = $0 { + print("Failed", apiError) + } + } + receiveValue: { data in + let str = String(decoding: data, as: UTF8.self) + print("Success \(str)") + } + return sink +} +``` + + + + diff --git a/src/fragments/lib-legacy/restapi/ios/getting-started.mdx b/src/fragments/lib-legacy/restapi/ios/getting-started.mdx new file mode 100644 index 00000000000..0f8df4009aa --- /dev/null +++ b/src/fragments/lib-legacy/restapi/ios/getting-started.mdx @@ -0,0 +1,78 @@ +The Amplify CLI deploys REST APIs and handlers using [Amazon API Gateway](http://docs.aws.amazon.com/apigateway/latest/developerguide/) and [AWS Lambda](http://docs.aws.amazon.com/lambda/latest/dg/). + +The API category will perform SDK code generation which, when used with the `AWSMobileClient` can be used for creating signed requests for Amazon API Gateway when the service Authorization is set to `AWS_IAM` or when using a [Cognito User Pools Authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html). + +## Create new REST API + +In a terminal window, navigate to your project folder (the folder that contains your app `.xcodeproj` file), and add the SDK to your app. + +```console +cd ./YOUR_PROJECT_FOLDER +amplify add api +``` + +When prompted select the following options: + +```console +> REST +> Create a new Lambda function +> Serverless express function +> Restrict API access? Yes +> Who should have access? Authenticated and Guest users +``` + +When configuration of your API is complete, the CLI displays a message confirming that you have configured local CLI metadata for this category. You can confirm this by running `amplify status`. Finally deploy your changes to the cloud: + +```bash +amplify push +``` + +## Connect to Your Backend + +Add `AWSAPIPlugin` to your Podfile: + +```ruby +target :'YOUR-APP-NAME' do + use_frameworks! + + pod 'Amplify' + pod 'AWSPluginsCore' + pod 'AmplifyPlugins/AWSAPIPlugin' +end +``` + +Run `pod install --repo-update` and then add `awsconfiguration.json` and `amplifyconfiguration.json` file to your project **(File->Add Files to ..->Add)** and then build your project, ensuring there are no issues. + +Add the following code to your app: + +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + AWSMobileClient.default().initialize { (userState, error) in + guard error == nil else { + print("Error initializing AWSMobileClient. Error: \(error!.localizedDescription)") + return + } + guard let userState = userState else { + print("userState is unexpectedly empty initializing AWSMobileClient") + return + } + + print("AWSMobileClient initialized, userstate: \(userState)") + } + + // Amplify section + let apiPlugin = AWSAPIPlugin() + try! Amplify.add(plugin: apiPlugin) + try! Amplify.configure() + print("Amplify initialized") + + return true +} +``` + +## IAM authorization + +To invoke an API Gateway endpoint from your application, For AWS IAM authorization use the `AWSMobileClient` as outlined in the authentication section. + +**Update this to have authorization mode included here rather than link** diff --git a/src/fragments/lib-legacy/restapi/ios/getting-started/10_preReq.mdx b/src/fragments/lib-legacy/restapi/ios/getting-started/10_preReq.mdx new file mode 100644 index 00000000000..370fc743236 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/ios/getting-started/10_preReq.mdx @@ -0,0 +1,3 @@ +* An iOS application targeting at least iOS 11.0 with Amplify libraries integrated + * For a full example of please follow the [project setup walkthrough](/lib/project-setup/create-application) + diff --git a/src/fragments/lib-legacy/restapi/ios/getting-started/11_amplifyInit.mdx b/src/fragments/lib-legacy/restapi/ios/getting-started/11_amplifyInit.mdx new file mode 100644 index 00000000000..6eae11f8f77 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/ios/getting-started/11_amplifyInit.mdx @@ -0,0 +1,50 @@ +To start provisioning api resources in the backend, go to your project directory and **execute the command**: + +```bash +amplify add api +``` + +Enter the following when prompted: +```console +? Please select from one of the below mentioned services: + `REST` +? Provide a friendly name for your resource to be used as a label for this category in the project: + `api1f12345` +? Provide a path (e.g., /book/{isbn}): + `/todo` +? Choose a Lambda source + `Create a new Lambda function` +? Provide a friendly name for your resource to be used as a label for this category in the project: + `restTodo123` +? Provide the AWS Lambda function name: + `restTodoLambda123` +? Choose the function runtime that you want to use: + `NodeJS` +? Choose the function template that you want to use: + `Serverless ExpressJS function (Integration with API Gateway)` +? Do you want to access other resources created in this project from your Lambda function? + `No` +? Do you want to invoke this function on a recurring schedule? + `No` +? Do you want to edit the local lambda function now? `No` +Successfully added the Lambda function locally +? Restrict API access + `Yes` +? Who should have access? + `Authenticated and Guest users` +? What kind of access do you want for Authenticated users? + `create, read, update, delete` +? What kind of access do you want for Guest users? + `create, read, update, delete` +Successfully added auth resource locally. +? Do you want to add another path? + `No` +``` + +To push your changes to the cloud, **execute the command**: + +```bash +amplify push +``` + +Upon completion, `amplifyconfiguration.json` should be updated to reference provisioned backend storage resources. Note that these files should already be a part of your project if you followed the [Project setup walkthrough](/lib/project-setup/create-application). diff --git a/src/fragments/lib-legacy/restapi/ios/getting-started/20_installLib.mdx b/src/fragments/lib-legacy/restapi/ios/getting-started/20_installLib.mdx new file mode 100644 index 00000000000..dfd042208e3 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/ios/getting-started/20_installLib.mdx @@ -0,0 +1,40 @@ + + + + +import ios0 from "/src/fragments/lib/ios-spm.mdx"; + + + +3. Lastly, choose **AWSAPIPlugin**, **AWSCognitoAuthPlugin** and **Amplify**. Then click **Add Package**. + + + + + +To install the Amplify API and Authentication to your application, **add both `AmplifyPlugins/AWSAPIPlugin` and `AmplifyPlugins/AWSCognitoAuthPlugin` to your `Podfile`**. Your `Podfile` should look similar to: + +```ruby +target 'MyAmplifyApp' do + use_frameworks! + pod 'Amplify' + pod 'AmplifyPlugins/AWSCognitoAuthPlugin' + pod 'AmplifyPlugins/AWSAPIPlugin' +end +``` + +To install, download and resolve these pods, **execute the command**: + +```bash +pod install --repo-update +``` + +Now you can **open your project** by opening the `.xcworkspace` file using the following command: + +```bash +xed . +``` + + + + \ No newline at end of file diff --git a/src/fragments/lib-legacy/restapi/ios/getting-started/30_initapi.mdx b/src/fragments/lib-legacy/restapi/ios/getting-started/30_initapi.mdx new file mode 100644 index 00000000000..f2fe2fd13ad --- /dev/null +++ b/src/fragments/lib-legacy/restapi/ios/getting-started/30_initapi.mdx @@ -0,0 +1,96 @@ +To initialize the Amplify API and Authentication categories, we are required to use the `Amplify.addPlugin()` method for each category we want. When we are done calling `addPlugin()` on each category, we finish configuring Amplify by calling `Amplify.configure()`. + +**Add the following imports** to the top of your `AppDelegate.swift` file: + + + + + +```swift +import Amplify +import AWSAPIPlugin +``` + + + + + +```swift +import Amplify +import AmplifyPlugins +``` + + + + + +**Add the following code** + + + + + +Add to your AppDelegate's `application:didFinishLaunchingWithOptions` method + +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + do { + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.add(plugin: AWSAPIPlugin()) + try Amplify.configure() + print("Amplify configured with API and Auth plugin") + } catch { + print("Failed to initialize Amplify with \(error)") + } + + return true +} +``` + + + + + +Create a custom `AppDelegate`, and add to your `application:didFinishLaunchingWithOptions` method +```swift +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + do { + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.add(plugin: AWSAPIPlugin()) + try Amplify.configure() + print("Amplify configured with API and Auth plugin") + } catch { + print("Failed to initialize Amplify with \(error)") + } + + return true + } +} +``` + +Then in the `App` scene, use `UIApplicationDelegateAdaptor` property wrapper to use your custom `AppDelegate` +```swift +@main +struct MyAmplifyApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} +``` + + + + + +Upon building and running this application you should see the following in your console window: + +```console +Amplify configured with API and Auth plugin +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/restapi/ios/getting-started/40_postTodo.mdx b/src/fragments/lib-legacy/restapi/ios/getting-started/40_postTodo.mdx new file mode 100644 index 00000000000..3eafd799e5e --- /dev/null +++ b/src/fragments/lib-legacy/restapi/ios/getting-started/40_postTodo.mdx @@ -0,0 +1,46 @@ + + + + +```swift +func postTodo() { + let message = #"{"message": "my new Todo"}"# + let request = RESTRequest(path: "/todo", body: message.data(using: .utf8)) + Amplify.API.post(request: request) { result in + switch result { + case .success(let data): + let str = String(decoding: data, as: UTF8.self) + print("Success \(str)") + case .failure(let apiError): + print("Failed", apiError) + } + } +} +``` + + + + + +```swift +func postTodo() -> AnyCancellable { + let message = #"{"message": "my new Todo"}"# + let request = RESTRequest(path: "/todo", body: message.data(using: .utf8)) + let sink = Amplify.API.post(request: request) + .resultPublisher + .sink { + if case let .failure(apiError) = $0 { + print("Failed", apiError) + } + } + receiveValue: { data in + let str = String(decoding: data, as: UTF8.self) + print("Success \(str)") + } + return sink +} +``` + + + + diff --git a/src/fragments/lib-legacy/restapi/ios/update.mdx b/src/fragments/lib-legacy/restapi/ios/update.mdx new file mode 100644 index 00000000000..525f67cd1cc --- /dev/null +++ b/src/fragments/lib-legacy/restapi/ios/update.mdx @@ -0,0 +1,50 @@ +## Update data + +Put data to the API endpoint: + + + + + +```swift +func putTodo() { + let updatedMessage = #"{"message": "my updated Todo"}"# + let request = RESTRequest(path: "/todo", body: updatedMessage.data(using: .utf8)) + Amplify.API.put(request: request) { result in + switch result { + case .success(let data): + let str = String(decoding: data, as: UTF8.self) + print("Success \(str)") + case .failure(let apiError): + print("Failed", apiError) + } + } +} +``` + + + + + +```swift +func putTodo() -> AnyCancellable { + let updatedMessage = #"{"message": "my updated Todo"}"# + let request = RESTRequest(path: "/todo", body: updatedMessage.data(using: .utf8)) + let sink = Amplify.API.put(request: request) + .resultPublisher + .sink { + if case let .failure(apiError) = $0 { + print("Failed", apiError) + } + } + receiveValue: { data in + let str = String(decoding: data, as: UTF8.self) + print("Success \(str)") + } + return sink +} +``` + + + + diff --git a/src/fragments/lib-legacy/restapi/js/authz.mdx b/src/fragments/lib-legacy/restapi/js/authz.mdx new file mode 100644 index 00000000000..3c6503d5d0c --- /dev/null +++ b/src/fragments/lib-legacy/restapi/js/authz.mdx @@ -0,0 +1,75 @@ +## Request headers + +When working with a REST endpoint, you may need to set request headers for authorization purposes. This is done by passing a `custom_header` function into the configuration: + +```javascript +Amplify.configure({ + API: { + endpoints: [ + { + name: "sampleCloudApi", + endpoint: "https://xyz.execute-api.us-east-1.amazonaws.com/Development", + custom_header: async () => { + return { Authorization : 'token' } + // Alternatively, with Cognito User Pools use this: + // return { Authorization: `Bearer ${(await Auth.currentSession()).getAccessToken().getJwtToken()}` } + // return { Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}` } + } + } + ] + } +}); +``` + +## Note related to use Access Token or ID Token + +The ID Token contains claims about the identity of the authenticated user such as name, email, and phone_number. It could have custom claims as well, for example using [Amplify CLI](https://docs.amplify.aws/cli/usage/lambda-triggers#override-id-token-claims). On the Amplify Authentication category you can retrieve the Id Token using: + +```javascript +(await Auth.currentSession()).getIdToken().getJwtToken(); +``` + +The Access Token contains scopes and groups and is used to grant access to authorized resources. [This is a tutorial for enabling custom scopes.](https://aws.amazon.com/premiumsupport/knowledge-center/cognito-custom-scopes-api-gateway/). You can retrieve the Access Token using + +```javascript +(await Auth.currentSession()).getAccessToken().getJwtToken(); +``` + +## Customizing HTTP request headers + +To use custom headers on your HTTP request, you need to add these to Amazon API Gateway first. For more info about configuring headers, please visit [Amazon API Gateway Developer Guide](http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html) + +If you have used Amplify CLI to create your API, you can enable custom headers by following above steps: + +1. Visit [Amazon API Gateway console](https://aws.amazon.com/api-gateway/). +3. On Amazon API Gateway console, click on the path you want to configure (e.g. /{proxy+}) +4. Then click the *Actions* dropdown menu and select **Enable CORS** +5. Add your custom header (e.g. my-custom-header) on the text field Access-Control-Allow-Headers, separated by commas, like: 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,my-custom-header'. +6. Click on 'Enable CORS and replace existing CORS headers' and confirm. +7. Finally, similar to step 3, click the Actions drop-down menu and then select **Deploy API**. Select **Development** on deployment stage and then **Deploy**. (Deployment could take a couple of minutes). + +## Unauthenticated Requests + +You can use the API category to access API Gateway endpoints that don't require authentication. In this case, you need to allow unauthenticated identities in your Amazon Cognito Identity Pool settings. For more information, please visit [Amazon Cognito Developer Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/identity-pools.html#enable-or-disable-unauthenticated-identities). + +## Cognito User Pools Authorization + +You can use the JWT token provided by the Authentication API to authenticate against API Gateway directly when using a custom authorizer. + +```javascript +async function postData() { + const apiName = 'MyApiName'; + const path = '/path'; + const myInit = { + headers: { + Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}`, + }, + }; + + return await API.post(apiName, path, myInit); +} + +postData(); +``` + +> Note that the header name, in the above example 'Authorization', is dependent on what you choose during your API Gateway configuration. diff --git a/src/fragments/lib-legacy/restapi/js/cancel.mdx b/src/fragments/lib-legacy/restapi/js/cancel.mdx new file mode 100644 index 00000000000..6c06bb40d84 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/js/cancel.mdx @@ -0,0 +1,35 @@ +## CANCEL API requests + +You may cancel any request made through API category by keeping a reference to the promise returned. + +```javascript +const promise = API.get(myApiName, myPath, myInit); + +try { + await promise; +} catch (error) { + console.log(error); + // If the error is because the request was cancelled we can confirm here. + if(API.isCancel(error)) { + console.log(error.message); // "my message for cancellation" + // handle user cancellation logic + } +} + +... + +// To cancel the above request +API.cancel(promise, "my message for cancellation"); +``` + +You need to ensure that the promise returned from `API.get()` or any other API calls has not been modified. Typically async functions wrap the promise being returned into another promise. For example, the following will not work + +```javascript +async function makeAPICall() { + return API.get(myApiName, myPath, myInit); +} +const promise = makeAPICall(); + +// The following will NOT cancel the request. +API.cancel(promise, "my error message"); +``` diff --git a/src/fragments/lib-legacy/restapi/js/delete.mdx b/src/fragments/lib-legacy/restapi/js/delete.mdx new file mode 100644 index 00000000000..74f8243c244 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/js/delete.mdx @@ -0,0 +1,47 @@ +## DELETE data + +```javascript +const apiName = 'MyApiName'; // replace this with your api name. +const path = '/path'; //replace this with the path you have configured on your API +const myInit = { // OPTIONAL + headers: {}, // OPTIONAL +}; + +API + .del(apiName, path, myInit) + .then(response => { + // Add your code here + }) + .catch(error => { + console.log(error.response); + }); +``` + +Example with async/await + +```javascript +async function deleteData() { + const apiName = 'MyApiName'; + const path = '/path'; + const myInit = { // OPTIONAL + headers: {} // OPTIONAL + } + return await API.del(apiName, path, myInit); +} + +deleteData(); +``` + +Access body in the Lambda function + +```javascript +// using a basic lambda handler +exports.handler = (event, context) => { + console.log('body: ', event.body); +} + +// using serverless express +app.delete('/myendpoint', function(req, res) { + console.log('body: ', req.body); +}); +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/restapi/js/fetch.mdx b/src/fragments/lib-legacy/restapi/js/fetch.mdx new file mode 100644 index 00000000000..8a408b9ffd4 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/js/fetch.mdx @@ -0,0 +1,142 @@ +To invoke an endpoint, you need to set `apiName`, `path` and `headers` parameters, and each method returns a Promise. Under the hood the API category utilizes [Axios](https://github.com/axios/axios) to execute the HTTP requests. API status code response > 299 are thrown as an exception. If you need to handle errors managed by your API, work with the `error.response` object. + +## GET requests + +```javascript +const apiName = 'MyApiName'; +const path = '/path'; +const myInit = { // OPTIONAL + headers: {}, // OPTIONAL + response: true, // OPTIONAL (return the entire Axios response object instead of only response.data) + queryStringParameters: { // OPTIONAL + name: 'param', + }, +}; + +API + .get(apiName, path, myInit) + .then(response => { + // Add your code here + }) + .catch(error => { + console.log(error.response); + }); +``` + +Example with async/await + +```javascript +function getData() { + const apiName = 'MyApiName'; + const path = '/path'; + const myInit = { // OPTIONAL + headers: {}, // OPTIONAL + }; + + return API.get(apiName, path, myInit); +} + +(async function () { + const response = await getData(); +})(); +``` + +## GET requests with query parameters + +To use query parameters with `get` method, you can pass them in `queryStringParameters` parameter in your method call: + +```javascript +const items = await API.get('myCloudApi', '/items', { + 'queryStringParameters': { + 'order': 'byPrice' + } +}); +``` + +## HEAD + +```javascript +const apiName = 'MyApiName'; // replace this with your api name. +const path = '/path'; //replace this with the path you have configured on your API +const myInit = { // OPTIONAL + headers: {}, // OPTIONAL +}; + +API + .head(apiName, path, myInit) + .then(response => { + // Add your code here + }); +``` + +Example with async/await: + +```javascript +function head() { + const apiName = 'MyApiName'; + const path = '/path'; + const myInit = { // OPTIONAL + headers: {}, // OPTIONAL + }; + + return API.head(apiName, path, myInit); +} + +(async function () { + const response = await head(); +})(); +``` + +## Accessing query parameters & body in Lambda proxy function + +> To learn more about Lambda Proxy Integration, please visit [Amazon API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html). + +If you are using a REST API which is generated with Amplify CLI, your backend is created with Lambda Proxy Integration, and you can access your query parameters & body within your Lambda function via the *event* object: + +```javascript +exports.handler = function(event, context, callback) { + console.log(event.queryStringParameters); + console.log('body: ', event.body); +} +``` + +Alternatively, you can update your backend file which is located at `amplify/backend/function/[your-lambda-function]/app.js` with the middleware: + +```javascript +const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware'); +app.use(awsServerlessExpressMiddleware.eventContext()); +``` + +Accessing Query Parameters with Serverless Express + +In your request handler use `req.apiGateway.event` or `req.query`: + +```javascript +app.get('/items', function(req, res) { + const query = req.query; + // or + // const query = req.apiGateway.event.queryStringParameters + res.json({ + event: req.apiGateway.event, // to view all event data + query: query + }); +}); +``` + +Then you can use query parameters in your path as follows: + +```javascript +API.get('sampleCloudApi', '/items?q=test'); +``` + +## Custom response types + +By default, calling an API with AWS Amplify parses a JSON response. If you have a REST API endpoint which returns, for example, a file in Blob format, you can specify a custom response type using the `responseType` parameter in your method call: + +```javascript +let file = await API.get('myCloudApi', '/items', { + 'responseType': 'blob', +}); +``` + +Allowed values for `responseType` are "arraybuffer", "blob", "document", "json" or "text"; and it defaults to "json" if not specified. See the [documentation](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType) for more information. diff --git a/src/fragments/lib-legacy/restapi/js/getting-started.mdx b/src/fragments/lib-legacy/restapi/js/getting-started.mdx new file mode 100644 index 00000000000..2355a1f0d4f --- /dev/null +++ b/src/fragments/lib-legacy/restapi/js/getting-started.mdx @@ -0,0 +1,111 @@ +The API category provides a solution for making HTTP requests to REST and GraphQL endpoints. For building GraphQL APIs please visit the [GraphQL Getting Started](/lib/graphqlapi/getting-started) section of our documentation. + +The REST API category can be used for creating signed requests against Amazon API Gateway when the API Gateway Authorization is set to `AWS_IAM`. + +> Ensure you have [installed and configured the Amplify CLI and library](/cli/start/install). + +## Automated Setup: Create new REST API + +Run the following command in your project's root folder: + +```bash +amplify add api +``` + +Select `REST` as the service type. + +```console +? Please select from one of the below mentioned services + GraphQL +❯ REST +``` + +The CLI will prompt several options to create your resources. With the provided options you can create: +- REST endpoints that triggers Lambda functions +- REST endpoints which enables CRUD operations on an Amazon DynamoDB table + +During setup you can use existing Lambda functions and DynamoDB tables or create new ones by following the CLI prompts. After your resources have been created update your backend with the `push` command: + +```bash +amplify push +``` + +A configuration file called `aws-exports.js` will be copied to your configured source directory, for example `./src`. + +### Configure your application + +Import and load the configuration file in your app. It's recommended you add the Amplify configuration step to your app's root entry point. For example `App.js` in React or `main.ts` in Angular. + +```javascript +import { Amplify, API } from 'aws-amplify'; +import awsconfig from './aws-exports'; + +Amplify.configure(awsconfig); +``` + +## Manual Setup: Import existing REST API + +For manual configuration you need to provide your AWS Resource configuration and optionally configure authentication. + +```javascript +import { Amplify, API } from 'aws-amplify'; + +Amplify.configure({ + // OPTIONAL - if your API requires authentication + Auth: { + // REQUIRED - Amazon Cognito Identity Pool ID + identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab', + // REQUIRED - Amazon Cognito Region + region: 'XX-XXXX-X', + // OPTIONAL - Amazon Cognito User Pool ID + userPoolId: 'XX-XXXX-X_abcd1234', + // OPTIONAL - Amazon Cognito Web Client ID (26-char alphanumeric string) + userPoolWebClientId: 'a1b2c3d4e5f6g7h8i9j0k1l2m3', + }, + API: { + endpoints: [ + { + name: "MyAPIGatewayAPI", + endpoint: "https://1234567890-abcdefgh.amazonaws.com" + }, + { + name: "MyCustomCloudFrontApi", + endpoint: "https://api.my-custom-cloudfront-domain.com", + + } + ] + } +}); +``` + +### AWS Regional Endpoints + +You can utilize regional endpoints by passing in the *service* and *region* information to the configuration. See [AWS Regions and Endpoints](https://docs.aws.amazon.com/general/latest/gr/rande.html). The example below defines a [Lambda invocation](https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html) in the `us-east-1` region: + +```javascript +API: { + endpoints: [ + { + name: "MyCustomLambda", + endpoint: "https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/yourFuncName/invocations", + service: "lambda", + region: "us-east-1" + } + ] +} +``` + + +**THIS IS NOT A RECOMMENDED ARCHITECTURE** and we highly recommend you leverage AWS AppSync or API Gateway as the endpoint to invoke your Lambda functions. + + + + + + **Configuring Amazon Cognito Regional Endpoints:** To call regional service endpoints, your Amazon Cognito role needs to be configured with appropriate access for the related service. See [AWS Cognito Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/iam-roles.html) for more details. + + + +## API Reference + +For the complete API documentation for API module, visit our [API Reference](https://aws-amplify.github.io/amplify-js/api/classes/apiclass.html). diff --git a/src/fragments/lib-legacy/restapi/js/update.mdx b/src/fragments/lib-legacy/restapi/js/update.mdx new file mode 100644 index 00000000000..21c00bf7975 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/js/update.mdx @@ -0,0 +1,118 @@ +## POST data + +Posts data to the API endpoint: + +```javascript +const apiName = 'MyApiName'; // replace this with your api name. +const path = '/path'; //replace this with the path you have configured on your API +const myInit = { + body: {}, // replace this with attributes you need + headers: {}, // OPTIONAL +}; + +API + .post(apiName, path, myInit) + .then(response => { + // Add your code here + }) + .catch(error => { + console.log(error.response); + }); +``` + +Example with async/await + +```javascript +async function postData() { + const apiName = 'MyApiName'; + const path = '/path'; + const myInit = { // OPTIONAL + body: {}, // replace this with attributes you need + headers: {}, // OPTIONAL + }; + + return await API.post(apiName, path, myInit); +} + +postData(); +``` + +## PUT data + +When used together with a REST API, `put()` method can be used to create or update records. It updates the record if a matching record is found. Otherwise, a new record is created. + +```javascript +const apiName = 'MyApiName'; // replace this with your api name. +const path = '/path'; // replace this with the path you have configured on your API +const myInit = { + body: {}, // replace this with attributes you need + headers: {}, // OPTIONAL +}; + +API + .put(apiName, path, myInit) + .then(response => { + // Add your code here + }) + .catch(error => { + console.log(error.response); + }); +``` + +Example with async/await: + +```javascript +async function putData() { + const apiName = 'MyApiName'; + const path = '/path'; + const myInit = { // OPTIONAL + body: {}, // replace this with attributes you need + headers: {}, // OPTIONAL + }; + + return await API.put(apiName, path, myInit); +} + +putData(); +``` + +Access body in the Lambda function + +```javascript +// using a basic lambda handler +exports.handler = (event, context) => { + console.log('body: ', event.body); +} + +// using serverless express +app.put('/myendpoint', function(req, res) { + console.log('body: ', req.body); +}); +``` + +Update a record: + +```javascript +const params = { + body: { + itemId: '12345', + itemDesc: ' update description' + }, +}; + +const apiResponse = await API.put('MyTableCRUD', '/manage-items', params); +``` + +## Access body in Lambda proxy function + +```javascript +// using a basic lambda handler +exports.handler = (event, context) => { + console.log('body: ', event.body); +} + +// using serverless express +app.post('/myendpoint', function(req, res) { + console.log('body: ', req.body); +}); +``` \ No newline at end of file diff --git a/src/fragments/lib-legacy/restapi/native_common/authz/common.mdx b/src/fragments/lib-legacy/restapi/native_common/authz/common.mdx new file mode 100644 index 00000000000..7bedd43cf0b --- /dev/null +++ b/src/fragments/lib-legacy/restapi/native_common/authz/common.mdx @@ -0,0 +1,175 @@ +When determining the authorization mode for your REST endpoint, there are a few built in options and customizations you can do. + +## IAM Authorization + +By default, the API will be using IAM authorization and the requests will be signed for you automatically. IAM authorization has two modes: one using an **unauthenticated** role, and one using an **authenticated** role. When the user has not signed in through `Amplify.Auth.signIn`, the unauthenticated role is used by default. Once the user has signed in, the authenticate role is used, instead. + +When you created your REST API with the Amplify CLI, you were asked if you wanted to restrict access. If you selected **no**, then the unauthenticated role will have access to the API. If you selected **yes**, you would have configured more fine grain access to your API. + +### Unauthenticated Requests + +You can use the API category to access API Gateway endpoints that don't require authentication. In this case, you need to allow unauthenticated identities in your Amazon Cognito Identity Pool settings. For more information, please visit [Amazon Cognito Developer Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/identity-pools.html#enable-or-disable-unauthenticated-identities). + +When your API is configured to use IAM as the authorization type, your requests will automatically have IAM credentials added to the headers of outgoing requests. You can verify that IAM is being used as the authorization type by inspecting the `authorizationType` associated with your API in `amplifyconfiguration.json`: + +```json +{ + "awsAPIPlugin": { + "[YOUR-RESTENDPOINT-NAME]": { + "endpointType": "REST", + "endpoint": "YOUR-REST-ENDPOINT", + "region": "us-west-2", + "authorizationType": "AWS_IAM" + } + } +} +``` + +## API Key + +If you want to configure a public REST API, you can set an API key in Amazon API Gateway. Then, update your `amplifyconfiguration.json` to reference it. The value associated to the `"authorizationType"` key should be `"API_KEY"`. Also include a `"apiKey"` as a key, and set its value to whatever you configured in API Gateway. + +```json +{ + "awsAPIPlugin": { + "[YOUR-RESTENDPOINT-NAME]": { + "endpointType": "REST", + "endpoint": "YOUR-REST-ENDPOINT", + "region": "us-west-2", + "authorizationType": "API_KEY", + "apiKey": "YOUR_API_KEY" + } + } +} +``` + +## Cognito User pool authorization + +If you set up the API Gateway with a custom authorization with a Cognito User pool, then you can set the `amplifyconfiguration.json` to use `AMAZON_COGNITO_USER_POOLS `. + +```json +{ + "awsAPIPlugin": { + "[YOUR-RESTENDPOINT-NAME]": { + "endpointType": "REST", + "endpoint": "YOUR-REST-ENDPOINT", + "region": "us-west-2", + "authorizationType": "AMAZON_COGNITO_USER_POOLS" + } + } +} +``` + +Your `amplifyconfiguration.json` should contain Cognito configuration in the `awsCognitoAuthPlugin` block, including details about your Cognito user pool: +```json +{ + "CognitoUserPool": { + "Default": { + "PoolId": "YOUR-POOL-ID", + "AppClientId": "YOUR-APP-CLIENT-ID", + "AppClientSecret": "YOUR-APP-CLIENT-SECRET", + "Region": "us-east-1" + } + }, + "CredentialsProvider": { + "CognitoIdentity": { + "Default": { + "PoolId": "YOUR-COGNITO-IDENTITY-POOLID", + "Region": "us-east-1" + } + } + } +} +``` + +With this configuration, your access token will automatically be included in outbound requests to your API, as an `Authorization` header. For more details on how to configure the API Gateway with the custom authorization, see [this](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html) + +## OIDC + +If you are using a 3rd party OIDC provider you will need to configure it and manage the details of token refreshes yourself. Update the `amplifyconfiguration.json` file and code snippet as follows: + +```json +{ + ... + "awsAPIPlugin": { + "[YOUR-RESTENDPOINT-NAME]": { + "endpointType": "REST", + "endpoint": "[REST-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "OPENID_CONNECT", + } + } +} +``` + +import ios0 from "/src/fragments/lib/graphqlapi/ios/authz/20_oidc.mdx"; + + + +import android1 from "/src/fragments/lib/graphqlapi/android/authz/20_oidc.mdx"; + + + +### Note related to use Access Token or ID Token + +The Access Token contains scopes and groups and is used to grant access to authorized resources. [This is a tutorial for enabling custom scopes.](https://aws.amazon.com/premiumsupport/knowledge-center/cognito-custom-scopes-api-gateway/). + +The ID Token contains claims about the identity of the authenticated user such as name, email, and phone_number. It could have custom claims as well, for example using [Amplify CLI](https://docs.amplify.aws/cli/usage/lambda-triggers#override-id-token-claims). + +If you are using Cognito's user pool as the authorization type, this will by default retrieve and use the Access Token for your requests. If you would like to override this behavior and use the ID Token instead, you can treat Cognito user pool as your OIDC provider, set the authorization type to `OPENID_CONNECT` and use `Amplify.Auth` to retrieve the ID Token for your requests. + +import ios2 from "/src/fragments/lib/graphqlapi/ios/authz/21_oidc.mdx"; + + + +import android3 from "/src/fragments/lib/graphqlapi/android/authz/21_oidc.mdx"; + + + +## NONE +You can also set authorization mode to `NONE` so that the library will not provide any request interception logic. You can use this when your API does not require any authorization or when you want to manipulate the request yourself, such as adding header values or authorization data. + +```json +{ + ... + "awsAPIPlugin": { + "[yourApiName]": { + "endpointType": "REST", + "endpoint": "[REST-ENDPOINT]", + "region": "[REGION]", + "authorizationType": "NONE", + } + } +} +``` + +import ios4 from "/src/fragments/lib/restapi/ios/authz/22_none_headers.mdx"; + + + +import android5 from "/src/fragments/lib/restapi/android/authz/22_none_headers.mdx"; + + + +You can register your own request interceptor to intercept the request and perform an action or inject something into your request before it is performed. + +import ios6 from "/src/fragments/lib/graphqlapi/ios/advanced-workflows/50_interceptor.mdx"; + + + +import android7 from "/src/fragments/lib/graphqlapi/android/advanced-workflows/50_interceptor.mdx"; + + + +## Customizing HTTP request headers + +To use custom headers on your HTTP request, you need to add these to Amazon API Gateway first. For more info about configuring headers, please visit [Amazon API Gateway Developer Guide](http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html) + +If you used the Amplify CLI to create your API, you can enable custom headers by following these steps: + +1. Visit [Amazon API Gateway console](https://aws.amazon.com/api-gateway/). +3. On Amazon API Gateway console, click on the path you want to configure (e.g. /{proxy+}) +4. Then click the *Actions* dropdown menu and select **Enable CORS** +5. Add your custom header (e.g. my-custom-header) on the text field Access-Control-Allow-Headers, separated by commas, like: 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,my-custom-header'. +6. Click on 'Enable CORS and replace existing CORS headers' and confirm. +7. Finally, similar to step 3, click the Actions drop-down menu and then select **Deploy API**. Select **Development** on deployment stage and then **Deploy**. (Deployment could take a couple of minutes). diff --git a/src/fragments/lib-legacy/restapi/native_common/getting-started/common.mdx b/src/fragments/lib-legacy/restapi/native_common/getting-started/common.mdx new file mode 100644 index 00000000000..3d46c16f912 --- /dev/null +++ b/src/fragments/lib-legacy/restapi/native_common/getting-started/common.mdx @@ -0,0 +1,87 @@ +The Amplify API category provides an interface for making requests to your backend. The Amplify CLI deploys REST APIs and handlers using [Amazon API Gateway](http://docs.aws.amazon.com/apigateway/latest/developerguide/) and [AWS Lambda](http://docs.aws.amazon.com/lambda/latest/dg/). + +## Goal +To setup and configure your application with Amplify API to make requests to your API Gateway and trigger the lambda function using authorization provided by Amplify Auth. + +## Prerequisites + +import ios0 from "/src/fragments/lib/restapi/ios/getting-started/10_preReq.mdx"; + + + +import android1 from "/src/fragments/lib/restapi/android/getting-started/10_preReq.mdx"; + + + +import flutter2 from "/src/fragments/lib/restapi/flutter/getting-started/10_preReq.mdx"; + + + +## Configure API + +import ios3 from "/src/fragments/lib/restapi/ios/getting-started/11_amplifyInit.mdx"; + + + +import android4 from "/src/fragments/lib/restapi/android/getting-started/11_amplifyInit.mdx"; + + + +import flutter5 from "/src/fragments/lib/restapi/flutter/getting-started/11_amplifyInit.mdx"; + + + +## Install Amplify Libraries + +import ios6 from "/src/fragments/lib/restapi/ios/getting-started/20_installLib.mdx"; + + + +import android7 from "/src/fragments/lib/restapi/android/getting-started/20_installLib.mdx"; + + + +import flutter8 from "/src/fragments/lib/restapi/flutter/getting-started/20_installLib.mdx"; + + + +## Initialize Amplify API + +import ios9 from "/src/fragments/lib/restapi/ios/getting-started/30_initapi.mdx"; + + + +import android10 from "/src/fragments/lib/restapi/android/getting-started/30_initapi.mdx"; + + + +import flutter11 from "/src/fragments/lib/restapi/flutter/getting-started/30_initapi.mdx"; + + + +## Make a POST Request + +Copy and paste the following code in your application so that it runs only once when the app starts: + +import ios12 from "/src/fragments/lib/restapi/ios/getting-started/40_postTodo.mdx"; + + + +import android13 from "/src/fragments/lib/restapi/android/getting-started/40_postTodo.mdx"; + + + +import flutter14 from "/src/fragments/lib/restapi/flutter/getting-started/40_postTodo.mdx"; + + + +To navigate to your backend, go to the [API Gateway console](https://aws.amazon.com/apigateway) and select the API. The name of the API corresponds to the friendly name of the resource to be used as a label you specified earlier in the CLI steps. + +## Next steps + +Congratulations! You've made a call to your API Gateway and triggered your Lambda function. Check out the following links to see other Amplify API use cases: + +* [Fetching Data](/lib/restapi/fetch) +* [Updating Data](/lib/restapi/update) +* [Deleting Data](/lib/restapi/delete) +* [Define authorization rules](/lib/restapi/authz) diff --git a/src/fragments/lib-legacy/ssr/js/getting-started.mdx b/src/fragments/lib-legacy/ssr/js/getting-started.mdx new file mode 100644 index 00000000000..48608288b25 --- /dev/null +++ b/src/fragments/lib-legacy/ssr/js/getting-started.mdx @@ -0,0 +1,246 @@ +To work well with server-rendered pages, Amplify JS requires slight modifications from how you would use it in a client-only environment. + +## Amplify + +### Enabling SSR + +When using the Amplify CLI, the __aws-exports.js__ file gets created and updated automatically for you based upon the resources you have added and configured. + +For client-only apps, `Amplify.configure(awsExports)` is all you need. + +To enable SSR support, also provide `ssr: true`: + +```js +import { Amplify } from "aws-amplify"; +import awsExports from "../src/aws-exports"; + +Amplify.configure({ ...awsExports, ssr: true }); +``` + +By providing `ssr: true`, Amplify persists credentials on the client in cookies so that subsequent requests to the server have access to them. + +> **Note**: Once [vercel/next.js#16977](https://github.com/vercel/next.js/issues/16977) is resolved, you can hoist `Amplify.configure` into **pages/_app.js**. Until then, be sure that all **pages/*** run `Amplify.configure({ ...awsExports, ssr: true })`. + +### withSSRContext + +Once an application has been configured with `ssr: true`, client-side credentials are passed to the server via cookies. + +The `withSSRContext` utility creates an instance of `Amplify` scoped to a single request (`req`) using those cookie credentials. + +For client-side code rendered in the browser, your page should continue using top-level imports as usual: + +```js +import { Amplify, API } from "aws-amplify"; +import awsExports from "../src/aws-exports"; + +Amplify.configure({ ...awsExports, ssr: true }); + +export default function HomePage({ posts = [] }) { + const [posts, setPosts] = useState(posts); + + useEffect(() => { + // 👇 Notice how the client correctly uses the top-level `API` import + API.graphql({ query: listPosts }).then(({ data }) => setPosts(data.listPosts.items)); + }, []) + + return ( ... ); +} +``` + +When on the server, use `withSSRContext({ req?: ServerRequest })`: + +```js +import { Amplify, API, withSSRContext } from "aws-amplify"; + +export async function getServerSideProps({ req }) { + // 👇 Notice how the server uses `API` from `withSSRContext`, instead of the top-level `API`. + const SSR = withSSRContext({ req }) + const { data } = await SSR.API.graphql({ query: listPosts }); + + return { + props: { + posts: data.listPosts.items + } + } +} +``` + + + +Server-side functions that don't have a req object (e.g. Next.js' getStaticProps & getStaticPaths) should still use withSSRContext(). Please note that it's not possible to perform authenticated requests with Amplify when using these functions. + + + +## DataStore + +### Serializing + +For Next.js, returned `props` from the server have to be valid JSON. Because `DataStore.query(Model)` returns _instances_ of `Model`, we need the `serializeModel` helper to convert it to JSON instead: + +```js +import { serializeModel } from '@aws-amplify/datastore/ssr'; +import { Amplify, withSSRContext } from "aws-amplify"; + +... + +export async function getServerSideProps({ req }) { + const SSR = withSSRContext({ req }); + const posts = await SSR.DataStore.query(Post); + + return { + props: { + // 👇 This converts Post instances into serialized JSON for the client + posts: serializeModel(posts), + }, + }; +} +``` + +### Deserializing + +If your client-side code only reads from the server-side props and doesn't perform any updates to these models, then your client-side code won't need any changes. + +However, if you receive models from the server and need to `DataStore.delete(model)` or `DataStore.save(...)` changes to them, you'll need the `deserializeModel` utility to convert them from server-friendly JSON back into model _instances_: + +```js +import { deserializeModel } from '@aws-amplify/datastore/ssr'; +import { Amplify, withSSRContext } from "aws-amplify"; + +import { Post } from "../src/models"; + +export default function HomePage(initialData) { + // 👇 This converts the serialized JSON back into Post instances + const [posts, setPosts] = useState(deserializeModel(Post, initialData.posts)); + + ... +} +``` + +### Example: + +Using the following schema: + +```graphql +type Post + @model + @auth(rules: [{ allow: owner }, { allow: public, operations: [read] }]) { + id: ID! + title: String! + content: String! +} +``` + +Open **pages/index.js** and replace it with the following code: + +```jsx +// pages/index.js +import Head from "next/head"; +import styles from "../styles/Home.module.css"; +import { + Amplify, + Auth, + AuthModeStrategyType, + DataStore, + withSSRContext, +} from "aws-amplify"; +import { Authenticator } from "@aws-amplify/ui-react"; +import { Post } from "../src/models"; +import { serializeModel } from "@aws-amplify/datastore/ssr"; +import awsExports from "../src/aws-exports"; + +Amplify.configure({ + ...awsExports, + DataStore: { + authModeStrategyType: AuthModeStrategyType.MULTI_AUTH, + }, + ssr: true, +}); + +export async function getServerSideProps({ req }) { + const SSR = withSSRContext({ req }); + const posts = await SSR.DataStore.query(Post); + + return { + props: { + // 👇 This converts Post instances into serialized JSON for the client + posts: serializeModel(posts), + }, + }; +} + +async function handleCreatePost(event) { + event.preventDefault(); + + const form = new FormData(event.target); + + try { + const post = await DataStore.save( + new Post({ + title: form.get("title"), + content: form.get("content"), + }) + ); + + window.location.href = `/posts/${post.id}`; + } catch (error) { + console.log(error); + } +} + +export default function Home({ posts = [] }) { + return ( +
+ + Amplify DataStore + Next.js + + + +
+

Amplify DataStore + Next.js

+

+ {posts.length} + posts +

+ +
+ {posts.map((post) => ( + +

{post.title}

+

{post.content}

+
+ ))} + +
+

New Post

+ + +
+
+ Title + +
+ +
+ Content +