Page updated Mar 26, 2024

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.

Connecting a web app? We also offer a version of this guide for integrating existing backends with React using the Amplify CLI. Check out the React guide.

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.

The team-provider-info.json file within the file directory of the Amplify app.

Step 2: In the team-provider-info.json file, note the following:

  1. The environment you want to use
  2. The AmplifyAppId for the required environment
The environment and AmplifyAppId in team-provider-info.json file.

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

Open the created app using VS Code.

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 profile
2
3For more information on AWS Profiles, see:
4https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
5
6? Please choose the profile you want to use AwsWest1
7Amplify AppID found: dorapq24trw9r. Amplify App name is:amplifyBudget
8Backend environment dev found in Amplify Console app: amplifyBudget
9? Choose your default editor: Visual Studio Code
10Choose the type of app that you're building · flutter
11Please tell us about your project
12? Where do you want to store your configuration file? ./lib/
13? Do you plan on modifying this backend? Yes
14Fetching updates to backend environment: dev from the cloud. Building resource api/amplifyBudget
15Fetching 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/schema
18Successfully pulled backend environment dev from the cloud.
19
20
21Successfully 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.

The Amplify folder and amplifyconfiguration.dart file within the file directory of the Amplify app.

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.

pubspec.yaml
1name: budget_tracker
2description: A new Flutter project.
3publish_to: 'none' # Remove this line if you wish to publish to pub.dev
4version: 1.0.0+1
5
6environment:
7 sdk: '>=3.1.0 <4.0.0'
8dependencies:
9 amplify_api: ^1.0.0
10 amplify_auth_cognito: ^1.0.0
11 amplify_authenticator: ^1.0.0
12 amplify_flutter: ^1.0.0
13 flutter:
14 sdk: flutter
15 go_router: ^6.5.5
16 cupertino_icons: ^1.0.2
17
18dev_dependencies:
19 flutter_test:
20 sdk: flutter
21 flutter_lints: ^2.0.0
22
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.

The models folder within the file directory of the Amplify app.

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.

main.dart
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 configuration
35 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 @override
52 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 @override
67 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 @override
80 State<HomeScreen> createState() => _HomeScreenState();
81}
82
83class _HomeScreenState extends State<HomeScreen> {
84 var _budgetEntries = <BudgetEntry>[];
85
86 @override
87 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 the
120 // 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 @override
166 Widget build(BuildContext context) {
167 return Scaffold(
168 floatingActionButton: FloatingActionButton(
169 // Navigate to the page to create new budget entries
170 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 else
186 Row(
187 mainAxisAlignment: MainAxisAlignment.center,
188 children: [
189 // Show total budget from the list of all BudgetEntries
190 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 @override
255 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 @override
271 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 @override
286 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 data
299 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 entry
305 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 instead
315 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 executes
326 if (mounted) {
327 context.pop();
328 }
329 }
330
331 @override
332 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.