Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated May 6, 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.

cd amplify_connect_resources
code . -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.

? 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 AwsWest1
Amplify AppID found: dorapq24trw9r. Amplify App name is:amplifyBudget
Backend environment dev found in Amplify Console app: amplifyBudget
? Choose your default editor: Visual Studio Code
Choose the type of app that you're building · flutter
Please 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.

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
name: budget_tracker
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: '>=3.1.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
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
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());
}
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,
),
);
}
}
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),
),
],
),
),
),
),
),
),
);
}
}

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.