Connect to existing AWS resources with Amplify CLI
The AWS Amplify CLI (Command Line Interface) CLI provides a simple workflow for provisioning cloud resources like authentication, databases, and storage for apps through the command line.
In this guide, you will learn how to connect a new Flutter mobile app to backend resources you've already created using the Amplify CLI.
Connect mobile app to existing AWS resources
This guide will walk you through connecting a new Flutter app to AWS resources created with Amplify for an existing Flutter app. If you don't already have an existing app, you can follow this Flutter tutorial to create a budget tracker app that uses Amplify Auth and API resources.
Before you begin, you will need:
- An existing Flutter app
- An AWS account: If you don't already have an account, follow the Setting Up Your AWS Environment tutorial for a quick overview.
- The Amplify CLI installed and configured.
- Flutter installed and configured.
- A text editor combined with Flutter’s command-line tools. For this guide, we will use VS Code, but you can use your preferred IDE.
Find the AWS backend details
Before connecting the backend resources to our new app, we first need to locate the details of the AWS environment provisioned for the existing app.
Step 1: In your existing app, open the file <Your-App>/amplify/team-provider-info.json
.
Step 2: In the team-provider-info.json
file, note the following:
- The environment you want to use
- The
AmplifyAppId
for the required environment
Create the Flutter app
Now that we have gathered the necessary backend details, we can start building out the new Flutter app.
Step 1: Create a Flutter app by running the following command in your terminal.
flutter create amplify_connect_resources
Step 2: Open the newly created Flutter app using VS Code by running the following commands in your terminal.
1cd amplify_connect_resources 2code . -r
Step 3: Navigate to the app's root folder and import the Amplify backend for the app by running the following command in your terminal.
amplify pull --appId <The_App_ID> --envName <The_App_Env>
Step 4: Select the AWS Authentication method. In this example, we are using an AWS profile. Ensure that the selected profile has the necessary permissions to access and modify AWS resources. See Configure the Amplify CLI for more information on setting up your AWS profile.
Accept the default values for the prompts about the default editor, type of app, and storage location of the configuration file. Then answer Yes to the “modifying this backend” question. Amplify CLI will initialize the backend and connect the project to the cloud.
1? Select the authentication method you want to use: AWS profile2
3For more information on AWS Profiles, see:4https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html5
6? Please choose the profile you want to use AwsWest17Amplify AppID found: dorapq24trw9r. Amplify App name is:amplifyBudget8Backend environment dev found in Amplify Console app: amplifyBudget9? Choose your default editor: Visual Studio Code10✔ Choose the type of app that you're building · flutter11Please tell us about your project12? Where do you want to store your configuration file? ./lib/13? Do you plan on modifying this backend? Yes14⠦ Fetching updates to backend environment: dev from the cloud.⠋ Building resource api/amplifyBudget15⠹ Fetching updates to backend environment: dev from the cloud.✅ GraphQL schema compiled successfully.16
17Edit 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/schema18✔ Successfully pulled backend environment dev from the cloud.19✅ 20
21✅ Successfully pulled backend environment dev from the cloud.22Run 'amplify pull' to sync future upstream changes.
The Amplify CLI will add a new folder named amplify
to the app's root folder, which contains the Amplify project and backend details. It will also add a new Dart file, amplifyconfiguration.dart
, to the lib/
folder. The app will use this file at runtime to locate and connect to the backend resources you have provisioned.
Step 5: Update the file pubspec.yaml
in the app root directory to add the required packages. In this example, we will use the same packages as the app created in this guide. To do this, update the pubspec.yaml
as shown in the following.
1name: budget_tracker2description: A new Flutter project.3publish_to: 'none' # Remove this line if you wish to publish to pub.dev4version: 1.0.0+15
6environment:7 sdk: '>=3.1.0 <4.0.0'8dependencies:9 amplify_api: ^1.0.010 amplify_auth_cognito: ^1.0.011 amplify_authenticator: ^1.0.012 amplify_flutter: ^1.0.013 flutter:14 sdk: flutter15 go_router: ^6.5.516 cupertino_icons: ^1.0.217
18dev_dependencies:19 flutter_test:20 sdk: flutter21 flutter_lints: ^2.0.022
23flutter:24 uses-material-design: true
Step 6: To enable type-safe interaction with the GraphQL schema, use this command to generate the required Dart files.
amplify codegen models
The Amplify CLI will generate the Dart files in the lib/models
folder.
Step 7: Update the main.dart
file with the following code to introduce the Amplify Authenticator and integrate Amplify API with your app to create, update, query, and delete BudgetEntry
items. Typically, you would break this file up into smaller modules but we've kept it as a single file for this guide.
1import 'package:amplify_api/amplify_api.dart';2import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';3import 'package:amplify_authenticator/amplify_authenticator.dart';4import 'package:amplify_flutter/amplify_flutter.dart';5import 'package:flutter/material.dart';6import 'package:go_router/go_router.dart';7import 'amplifyconfiguration.dart';8import 'models/ModelProvider.dart';9
10Future<void> main() async {11 WidgetsFlutterBinding.ensureInitialized();12 await _configureAmplify();13 runApp(const MyApp());14}15
16Future<void> _configureAmplify() async {17
18 try {19
20 final api = AmplifyAPI(modelProvider: ModelProvider.instance);21 final auth = AmplifyAuthCognito();22 await Amplify.addPlugins([api, auth]);23 await Amplify.configure(amplifyconfig);24
25 safePrint('Successfully configured');26 } on Exception catch (e) {27 safePrint('Error configuring Amplify: $e');28 }29}30
31class MyApp extends StatelessWidget {32 const MyApp({super.key});33
34 // GoRouter configuration35 static final _router = GoRouter(36 routes: [37 GoRoute(38 path: '/',39 builder: (context, state) => const HomeScreen(),40 ),41 GoRoute(42 path: '/manage-budget-entry',43 name: 'manage',44 builder: (context, state) => ManageBudgetEntryScreen(45 budgetEntry: state.extra as BudgetEntry?,46 ),47 ),48 ],49 );50
51 @override52 Widget build(BuildContext context) {53 return Authenticator(54 child: MaterialApp.router(55 routerConfig: _router,56 debugShowCheckedModeBanner: false,57 builder: Authenticator.builder(),58 ),59 );60 }61}62
63class LoadingScreen extends StatelessWidget {64 const LoadingScreen({super.key});65
66 @override67 Widget build(BuildContext context) {68 return const Scaffold(69 body: Center(70 child: CircularProgressIndicator(),71 ),72 );73 }74}75
76class HomeScreen extends StatefulWidget {77 const HomeScreen({super.key});78
79 @override80 State<HomeScreen> createState() => _HomeScreenState();81}82
83class _HomeScreenState extends State<HomeScreen> {84 var _budgetEntries = <BudgetEntry>[];85
86 @override87 void initState() {88 super.initState();89 _refreshBudgetEntries();90 }91
92 Future<void> _refreshBudgetEntries() async {93 try {94 final request = ModelQueries.list(BudgetEntry.classType);95 final response = await Amplify.API.query(request: request).response;96
97 final todos = response.data?.items;98 if (response.hasErrors) {99 safePrint('errors: ${response.errors}');100 return;101 }102 setState(() {103 _budgetEntries = todos!.whereType<BudgetEntry>().toList();104 });105 } on ApiException catch (e) {106 safePrint('Query failed: $e');107 }108 }109
110 Future<void> _deleteBudgetEntry(BudgetEntry budgetEntry) async {111 final request = ModelMutations.delete<BudgetEntry>(budgetEntry);112 final response = await Amplify.API.mutate(request: request).response;113 safePrint('Delete response: $response');114 await _refreshBudgetEntries();115 }116
117 Future<void> _navigateToBudgetEntry({BudgetEntry? budgetEntry}) async {118 await context.pushNamed('manage', extra: budgetEntry);119 // Refresh the entries when returning from the120 // budget entry screen.121 await _refreshBudgetEntries();122 }123
124 double _calculateTotalBudget(List<BudgetEntry?> items) {125 var totalAmount = 0.0;126 for (final item in items) {127 totalAmount += item?.amount ?? 0;128 }129 return totalAmount;130 }131
132 Widget _buildRow({133 required String title,134 required String description,135 required String amount,136 TextStyle? style,137 }) {138 return Row(139 children: [140 Expanded(141 child: Text(142 title,143 textAlign: TextAlign.center,144 style: style,145 ),146 ),147 Expanded(148 child: Text(149 description,150 textAlign: TextAlign.center,151 style: style,152 ),153 ),154 Expanded(155 child: Text(156 amount,157 textAlign: TextAlign.center,158 style: style,159 ),160 ),161 ],162 );163 }164
165 @override166 Widget build(BuildContext context) {167 return Scaffold(168 floatingActionButton: FloatingActionButton(169 // Navigate to the page to create new budget entries170 onPressed: _navigateToBudgetEntry,171 child: const Icon(Icons.add),172 ),173 appBar: AppBar(174 title: const Text('Budget Tracker'),175 ),176 body: Center(177 child: Padding(178 padding: const EdgeInsets.only(top: 25),179 child: RefreshIndicator(180 onRefresh: _refreshBudgetEntries,181 child: Column(182 children: [183 if (_budgetEntries.isEmpty)184 const Text('Use the \u002b sign to add new budget entries')185 else186 Row(187 mainAxisAlignment: MainAxisAlignment.center,188 children: [189 // Show total budget from the list of all BudgetEntries190 Text(191 'Total Budget: \$ ${_calculateTotalBudget(_budgetEntries).toStringAsFixed(2)}',192 style: const TextStyle(fontSize: 24),193 )194 ],195 ),196 const SizedBox(height: 30),197 _buildRow(198 title: 'Title',199 description: 'Description',200 amount: 'Amount',201 style: Theme.of(context).textTheme.titleMedium,202 ),203 const Divider(),204 Expanded(205 child: ListView.builder(206 itemCount: _budgetEntries.length,207 itemBuilder: (context, index) {208 final budgetEntry = _budgetEntries[index];209 return Dismissible(210 key: ValueKey(budgetEntry),211 background: const ColoredBox(212 color: Colors.red,213 child: Padding(214 padding: EdgeInsets.only(right: 10),215 child: Align(216 alignment: Alignment.centerRight,217 child: Icon(Icons.delete, color: Colors.white),218 ),219 ),220 ),221 onDismissed: (_) => _deleteBudgetEntry(budgetEntry),222 child: ListTile(223 onTap: () => _navigateToBudgetEntry(224 budgetEntry: budgetEntry,225 ),226 title: _buildRow(227 title: budgetEntry.title,228 description: budgetEntry.description ?? '',229 amount:230 '\$ ${budgetEntry.amount.toStringAsFixed(2)}',231 ),232 ),233 );234 },235 ),236 ),237 ],238 ),239 ),240 ),241 ),242 );243 }244}245
246class ManageBudgetEntryScreen extends StatefulWidget {247 const ManageBudgetEntryScreen({248 required this.budgetEntry,249 super.key,250 });251
252 final BudgetEntry? budgetEntry;253
254 @override255 State<ManageBudgetEntryScreen> createState() =>256 _ManageBudgetEntryScreenState();257}258
259class _ManageBudgetEntryScreenState extends State<ManageBudgetEntryScreen> {260 final _formKey = GlobalKey<FormState>();261 final TextEditingController _titleController = TextEditingController();262 final TextEditingController _descriptionController = TextEditingController();263 final TextEditingController _amountController = TextEditingController();264
265 late final String _titleText;266
267 bool get _isCreate => _budgetEntry == null;268 BudgetEntry? get _budgetEntry => widget.budgetEntry;269
270 @override271 void initState() {272 super.initState();273
274 final budgetEntry = _budgetEntry;275 if (budgetEntry != null) {276 _titleController.text = budgetEntry.title;277 _descriptionController.text = budgetEntry.description ?? '';278 _amountController.text = budgetEntry.amount.toStringAsFixed(2);279 _titleText = 'Update budget entry';280 } else {281 _titleText = 'Create budget entry';282 }283 }284
285 @override286 void dispose() {287 _titleController.dispose();288 _descriptionController.dispose();289 _amountController.dispose();290 super.dispose();291 }292
293 Future<void> submitForm() async {294 if (!_formKey.currentState!.validate()) {295 return;296 }297
298 // If the form is valid, submit the data299 final title = _titleController.text;300 final description = _descriptionController.text;301 final amount = double.parse(_amountController.text);302
303 if (_isCreate) {304 // Create a new budget entry305 final newEntry = BudgetEntry(306 title: title,307 description: description.isNotEmpty ? description : null,308 amount: amount,309 );310 final request = ModelMutations.create(newEntry);311 final response = await Amplify.API.mutate(request: request).response;312 safePrint('Create result: $response');313 } else {314 // Update budgetEntry instead315 final updateBudgetEntry = _budgetEntry!.copyWith(316 title: title,317 description: description.isNotEmpty ? description : null,318 amount: amount,319 );320 final request = ModelMutations.update(updateBudgetEntry);321 final response = await Amplify.API.mutate(request: request).response;322 safePrint('Update result: $response');323 }324
325 // Navigate back to homepage after create/update executes326 if (mounted) {327 context.pop();328 }329 }330
331 @override332 Widget build(BuildContext context) {333 return Scaffold(334 appBar: AppBar(335 title: Text(_titleText),336 ),337 body: Align(338 alignment: Alignment.topCenter,339 child: ConstrainedBox(340 constraints: const BoxConstraints(maxWidth: 800),341 child: Padding(342 padding: const EdgeInsets.all(16),343 child: SingleChildScrollView(344 child: Form(345 key: _formKey,346 child: Column(347 crossAxisAlignment: CrossAxisAlignment.start,348 children: [349 TextFormField(350 controller: _titleController,351 decoration: const InputDecoration(352 labelText: 'Title (required)',353 ),354 validator: (value) {355 if (value == null || value.isEmpty) {356 return 'Please enter a title';357 }358 return null;359 },360 ),361 TextFormField(362 controller: _descriptionController,363 decoration: const InputDecoration(364 labelText: 'Description',365 ),366 ),367 TextFormField(368 controller: _amountController,369 keyboardType: const TextInputType.numberWithOptions(370 signed: false,371 decimal: true,372 ),373 decoration: const InputDecoration(374 labelText: 'Amount (required)',375 ),376 validator: (value) {377 if (value == null || value.isEmpty) {378 return 'Please enter an amount';379 }380 final amount = double.tryParse(value);381 if (amount == null || amount <= 0) {382 return 'Please enter a valid amount';383 }384 return null;385 },386 ),387 const SizedBox(height: 20),388 ElevatedButton(389 onPressed: submitForm,390 child: Text(_titleText),391 ),392 ],393 ),394 ),395 ),396 ),397 ),398 ),399 );400 }401}
Step 8: Run the app using the following command, and use the authentication flow to create a user. Then create a few budget items.
flutter run
Conclusion
Congratulations! Your new Flutter app is now connected to AWS resources from a different app through AWS Amplify. This integration grants your app access to authentication resources for user management and a scalable GraphQL API backed by Amazon DynamoDB.
Clean up resources
Once you're finished experimenting with this demo app, we recommend deleting the backend resources to avoid incurring unexpected costs. You can do this by running the following command in the root folder of the app.
amplify delete
If you would like to expand this demo app into a production-ready app, you may need to add additional resources, such as authorization and storage. Refer to the Build & connect backend section for guides on how to add and connect other backend resources.