Amplify CLIで既存のAWSリソースに接続する
AWS Amplify CLI(コマンドラインインターフェース)は、認証、データベース、ストレージなどのクラウドリソースをコマンドラインでアプリケーション用にプロビジョニングするシンプルなワークフローを提供します。
このガイドでは、Amplify CLIを使用して既に作成したバックエンドリソースに新しいFlutterモバイルアプリを接続する方法を学びます。
モバイルアプリを既存のAWSリソースに接続する
このガイドでは、既存のFlutterアプリ用にAmplifyで作成されたAWSリソースに新しいFlutterアプリを接続する手順を説明します。既存のアプリがない場合は、このFlutterガイドに従って、Amplify AuthおよびAPIリソースを使用する予算トラッカーアプリを作成できます。
開始する前に、以下が必要です:
- 既存のFlutterアプリ
- AWSアカウント:アカウントがない場合は、AWSEnvironmentの設定チュートリアルに従って、簡単な概要を取得してください。
- インストールおよび構成されたAmplify CLI。
- インストールおよび構成されたFlutter。
- Flutterのコマンドラインツールと組み合わせたテキストエディタ。このガイドではVS Codeを使用しますが、好みのIDEを使用できます。
AWSバックエンド詳細を検索する
バックエンドリソースを新しいアプリに接続する前に、まず既存のアプリ用にプロビジョニングされたAWS環境の詳細を見つける必要があります。
ステップ1: 既存のアプリで、<Your-App>/amplify/team-provider-info.jsonファイルを開きます。

ステップ2: team-provider-info.jsonファイルで、以下を記録します:
- 使用する環境
- 必要な環境の
AmplifyAppId

Flutterアプリを作成する
必要なバックエンド詳細を収集したので、新しいFlutterアプリの構築を開始できます。
ステップ1: ターミナルで以下のコマンドを実行して、Flutterアプリを作成します。
flutter create amplify_connect_resourcesステップ2: ターミナルで以下のコマンドを実行して、新しく作成されたFlutterアプリをVS Codeで開きます。
cd amplify_connect_resources code . -rステップ3: アプリのルートフォルダに移動し、ターミナルで以下のコマンドを実行してAmplifyバックエンドをアプリにインポートします。
amplify pull --appId <The_App_ID> --envName <The_App_Env>ステップ4: AWS認証方法を選択します。この例では、AWSプロファイルを使用しています。選択したプロファイルがAWSリソースへのアクセスと変更に必要な権限を持っていることを確認してください。AWSプロファイルの設定の詳細については、Amplify CLIの構成を参照してください。
デフォルトエディタ、アプリタイプ、構成ファイルの保存場所に関するプロンプトのデフォルト値を受け入れます。次に、「modifying this backend」質問にはいと答えてください。Amplify CLIはバックエンドを初期化し、プロジェクトをクラウドに接続します。
? Select the authentication method you want to use: AWS profile
For more information on AWS Profiles, see:https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
? Please choose the profile you want to use AwsWest1Amplify AppID found: dorapq24trw9r. Amplify App name is:amplifyBudgetBackend environment dev found in Amplify Console app: amplifyBudget? Choose your default editor: Visual Studio Code✔ Choose the type of app that you're building · flutterPlease tell us about your project? Where do you want to store your configuration file? ./lib/? Do you plan on modifying this backend? Yes⠦ Fetching updates to backend environment: dev from the cloud.⠋ Building resource api/amplifyBudget⠹ Fetching updates to backend environment: dev from the cloud.✅ GraphQL schema compiled successfully.
Edit your schema at /Users/malakamm/development/amplify_connect_resources/amplify/backend/api/amplifyBudget/schema.graphql or place .graphql files in a directory at /Users/malakamm/development/amplify_connect_resources/amplify/backend/api/amplifyBudget/schema✔ Successfully pulled backend environment dev from the cloud.✅
✅ Successfully pulled backend environment dev from the cloud.Run 'amplify pull' to sync future upstream changes.Amplify CLIは、Amplifyプロジェクトとバックエンド詳細を含むamplifyという名前の新しいフォルダをアプリのルートフォルダに追加します。また、lib/フォルダにamplifyconfiguration.dartという新しいDartファイルも追加します。アプリはこのファイルをランタイムで使用して、プロビジョニングしたバックエンドリソースを見つけて接続します。

ステップ5: アプリルートディレクトリのpubspec.yamlファイルを更新して、必要なパッケージを追加します。この例では、このガイドで作成されたアプリと同じパッケージを使用します。これを行うには、次のようにpubspec.yamlを更新します。
name: budget_trackerdescription: A new Flutter project.publish_to: 'none' # Remove this line if you wish to publish to pub.devversion: 1.0.0+1
dependencies: amplify_api: ^2.0.0 amplify_auth_cognito: ^2.0.0 amplify_authenticator: ^2.0.0 amplify_flutter: ^2.0.0 flutter: sdk: flutter go_router: ^6.5.5 cupertino_icons: ^1.0.2
dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0
flutter: uses-material-design: trueステップ6:このコマンドを使用して、GraphQLスキーマへのタイプセーフインタラクションを有効にするために必要なDartファイルを生成します。
amplify codegen modelsAmplify CLIは、lib/modelsフォルダにDartファイルを生成します。

ステップ7: main.dartファイルを以下のコードで更新して、Amplify Authenticatorを導入し、Amplify APIを統計アプリに統合して、BudgetEntry項目を作成、更新、クエリ、削除します。通常、このファイルをより小さなモジュールに分割しますが、このガイドでは1つのファイルとして保持しました。
import 'package:amplify_api/amplify_api.dart';import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';import 'package:amplify_authenticator/amplify_authenticator.dart';import 'package:amplify_flutter/amplify_flutter.dart';import 'package:flutter/material.dart';import 'package:go_router/go_router.dart';import 'amplifyconfiguration.dart';import 'models/ModelProvider.dart';
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await _configureAmplify(); runApp(const MyApp());}
Future<void> _configureAmplify() async {
try {
final api = AmplifyAPI(modelProvider: ModelProvider.instance); final auth = AmplifyAuthCognito(); await Amplify.addPlugins([api, auth]); await Amplify.configure(amplifyconfig);
safePrint('Successfully configured'); } on Exception catch (e) { safePrint('Error configuring Amplify: $e'); }}
class MyApp extends StatelessWidget { const MyApp({super.key});
// GoRouter configuration static final _router = GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), ), GoRoute( path: '/manage-budget-entry', name: 'manage', builder: (context, state) => ManageBudgetEntryScreen( budgetEntry: state.extra as BudgetEntry?, ), ), ], );
@override Widget build(BuildContext context) { return Authenticator( child: MaterialApp.router( routerConfig: _router, debugShowCheckedModeBanner: false, builder: Authenticator.builder(), ), ); }}
class LoadingScreen extends StatelessWidget { const LoadingScreen({super.key});
@override Widget build(BuildContext context) { return const Scaffold( body: Center( child: CircularProgressIndicator(), ), ); }}
class HomeScreen extends StatefulWidget { const HomeScreen({super.key});
@override State<HomeScreen> createState() => _HomeScreenState();}
class _HomeScreenState extends State<HomeScreen> { var _budgetEntries = <BudgetEntry>[];
@override void initState() { super.initState(); _refreshBudgetEntries(); }
Future<void> _refreshBudgetEntries() async { try { final request = ModelQueries.list(BudgetEntry.classType); final response = await Amplify.API.query(request: request).response;
final todos = response.data?.items; if (response.hasErrors) { safePrint('errors: ${response.errors}'); return; } setState(() { _budgetEntries = todos!.whereType<BudgetEntry>().toList(); }); } on ApiException catch (e) { safePrint('Query failed: $e'); } }
Future<void> _deleteBudgetEntry(BudgetEntry budgetEntry) async { final request = ModelMutations.delete<BudgetEntry>(budgetEntry); final response = await Amplify.API.mutate(request: request).response; safePrint('Delete response: $response'); await _refreshBudgetEntries(); }
Future<void> _navigateToBudgetEntry({BudgetEntry? budgetEntry}) async { await context.pushNamed('manage', extra: budgetEntry); // Refresh the entries when returning from the // budget entry screen. await _refreshBudgetEntries(); }
double _calculateTotalBudget(List<BudgetEntry?> items) { var totalAmount = 0.0; for (final item in items) { totalAmount += item?.amount ?? 0; } return totalAmount; }
Widget _buildRow({ required String title, required String description, required String amount, TextStyle? style, }) { return Row( children: [ Expanded( child: Text( title, textAlign: TextAlign.center, style: style, ), ), Expanded( child: Text( description, textAlign: TextAlign.center, style: style, ), ), Expanded( child: Text( amount, textAlign: TextAlign.center, style: style, ), ), ], ); }
@override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( // Navigate to the page to create new budget entries onPressed: _navigateToBudgetEntry, child: const Icon(Icons.add), ), appBar: AppBar( title: const Text('Budget Tracker'), ), body: Center( child: Padding( padding: const EdgeInsets.only(top: 25), child: RefreshIndicator( onRefresh: _refreshBudgetEntries, child: Column( children: [ if (_budgetEntries.isEmpty) const Text('Use the \u002b sign to add new budget entries') else Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // Show total budget from the list of all BudgetEntries Text( 'Total Budget: \$ ${_calculateTotalBudget(_budgetEntries).toStringAsFixed(2)}', style: const TextStyle(fontSize: 24), ) ], ), const SizedBox(height: 30), _buildRow( title: 'Title', description: 'Description', amount: 'Amount', style: Theme.of(context).textTheme.titleMedium, ), const Divider(), Expanded( child: ListView.builder( itemCount: _budgetEntries.length, itemBuilder: (context, index) { final budgetEntry = _budgetEntries[index]; return Dismissible( key: ValueKey(budgetEntry), background: const ColoredBox( color: Colors.red, child: Padding( padding: EdgeInsets.only(right: 10), child: Align( alignment: Alignment.centerRight, child: Icon(Icons.delete, color: Colors.white), ), ), ), onDismissed: (_) => _deleteBudgetEntry(budgetEntry), child: ListTile( onTap: () => _navigateToBudgetEntry( budgetEntry: budgetEntry, ), title: _buildRow( title: budgetEntry.title, description: budgetEntry.description ?? '', amount: '\$ ${budgetEntry.amount.toStringAsFixed(2)}', ), ), ); }, ), ), ], ), ), ), ), ); }}
class ManageBudgetEntryScreen extends StatefulWidget { const ManageBudgetEntryScreen({ required this.budgetEntry, super.key, });
final BudgetEntry? budgetEntry;
@override State<ManageBudgetEntryScreen> createState() => _ManageBudgetEntryScreenState();}
class _ManageBudgetEntryScreenState extends State<ManageBudgetEntryScreen> { final _formKey = GlobalKey<FormState>(); final TextEditingController _titleController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); final TextEditingController _amountController = TextEditingController();
late final String _titleText;
bool get _isCreate => _budgetEntry == null; BudgetEntry? get _budgetEntry => widget.budgetEntry;
@override void initState() { super.initState();
final budgetEntry = _budgetEntry; if (budgetEntry != null) { _titleController.text = budgetEntry.title; _descriptionController.text = budgetEntry.description ?? ''; _amountController.text = budgetEntry.amount.toStringAsFixed(2); _titleText = 'Update budget entry'; } else { _titleText = 'Create budget entry'; } }
@override void dispose() { _titleController.dispose(); _descriptionController.dispose(); _amountController.dispose(); super.dispose(); }
Future<void> submitForm() async { if (!_formKey.currentState!.validate()) { return; }
// If the form is valid, submit the data final title = _titleController.text; final description = _descriptionController.text; final amount = double.parse(_amountController.text);
if (_isCreate) { // Create a new budget entry final newEntry = BudgetEntry( title: title, description: description.isNotEmpty ? description : null, amount: amount, ); final request = ModelMutations.create(newEntry); final response = await Amplify.API.mutate(request: request).response; safePrint('Create result: $response'); } else { // Update budgetEntry instead final updateBudgetEntry = _budgetEntry!.copyWith( title: title, description: description.isNotEmpty ? description : null, amount: amount, ); final request = ModelMutations.update(updateBudgetEntry); final response = await Amplify.API.mutate(request: request).response; safePrint('Update result: $response'); }
// Navigate back to homepage after create/update executes if (mounted) { context.pop(); } }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_titleText), ), body: Align( alignment: Alignment.topCenter, child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 800), child: Padding( padding: const EdgeInsets.all(16), child: SingleChildScrollView( child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextFormField( controller: _titleController, decoration: const InputDecoration( labelText: 'Title (required)', ), validator: (value) { if (value == null || value.isEmpty) { return 'Please enter a title'; } return null; }, ), TextFormField( controller: _descriptionController, decoration: const InputDecoration( labelText: 'Description', ), ), TextFormField( controller: _amountController, keyboardType: const TextInputType.numberWithOptions( signed: false, decimal: true, ), decoration: const InputDecoration( labelText: 'Amount (required)', ), validator: (value) { if (value == null || value.isEmpty) { return 'Please enter an amount'; } final amount = double.tryParse(value); if (amount == null || amount <= 0) { return 'Please enter a valid amount'; } return null; }, ), const SizedBox(height: 20), ElevatedButton( onPressed: submitForm, child: Text(_titleText), ), ], ), ), ), ), ), ), ); }}ステップ8: 以下のコマンドを使用してアプリを実行し、認証フローを使用してユーザーを作成します。次に、いくつかの予算項目を作成します。
flutter run結論
おめでとうございます!新しいFlutterアプリは、AWS Amplifyを通じて別のアプリからのAWSリソースに接続されました。この統合により、アプリはユーザー管理用の認証リソースと、Amazon DynamoDBで支援されたスケーラブルなGraphQL APIにアクセスできます。
リソースをクリーンアップする
このデモアプリの実験が終わったら、予期しない費用の発生を避けるためにバックエンドリソースを削除することをお勧めします。これは、アプリのルートフォルダで以下のコマンドを実行することで行えます。
amplify deleteこのデモアプリを本番対応アプリに拡張したい場合は、認可やストレージなどの追加リソースを追加する必要がある場合があります。Build&connectバックエンドセクションを参照して、他のバックエンドリソースを追加および接続する方法に関するガイドを確認してください。