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

Page updated May 16, 2024

Quickstart

Prerequisites

Before you get started, make sure you have the following installed:

You can follow the official documentation to install Flutter on your machine and check the editor documentation for setting up your editor.

Once you have installed Flutter, you can create a new Flutter project using the following command:

Terminal
flutter create my_amplify_app

Create Backend

The easiest way to get started with AWS Amplify is through npm with create-amplify command. You can run it from your base project directory.

Terminal
npm create amplify@latest
? Where should we create your project? (.) # press enter

Running this command will scaffold Amplify backend files in your current project with the following files added:

├── amplify/
│ ├── auth/
│ │ └── resource.ts
│ ├── data/
│ │ └── resource.ts
│ ├── backend.ts
│ └── package.json
├── node_modules/
├── .gitignore
├── package-lock.json
├── package.json
└── tsconfig.json

To deploy your backend use Amplify's per-developer cloud sandbox. This feature provides a separate backend environment for every developer on a team, ideal for local development and testing. To run your application with a sandbox environment, you can run the following command:

Terminal
npx ampx sandbox --outputs-format dart --outputs-out-dir lib

Adding Authentication

The initial scaffolding already has a pre-configured auth backend defined in the amplify/auth/resource.ts file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook.

The fastest way to get your login experience up and running is to use our Authenticator UI component available in the Amplify UI library.

To use the Authenticator, you need to add the following dependencies to your project:

pubspec.yaml
dependencies:
amplify_flutter: ^1.0.0
amplify_auth_cognito: ^1.0.0
amplify_authenticator: ^1.0.0

You will add:

  • amplify_flutter to connect your application with the Amplify resources.
  • amplify_auth_cognito to connect your application with the Amplify Cognito resources.
  • amplify_authenticator to use the Amplify UI components.

After adding the dependencies, you need to run the following command to install the dependencies:

Terminal
flutter pub get

Lastly update your main.dart file to use the Amplify UI components:

main.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 'amplifyconfiguration.dart';
Future<void> main() async {
try {
WidgetsFlutterBinding.ensureInitialized();
await _configureAmplify();
runApp(const MyApp());
} on AmplifyException catch (e) {
runApp(Text("Error configuring Amplify: ${e.message}"));
}
}
Future<void> _configureAmplify() async {
try {
await Amplify.addPlugin(AmplifyAuthCognito());
await Amplify.configure(outputs);
safePrint('Successfully configured');
} on Exception catch (e) {
safePrint('Error configuring Amplify: $e');
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return Authenticator(
child: MaterialApp(
builder: Authenticator.builder(),
home: const Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SignOutButton(),
Text('TODO Application'),
],
),
),
),
),
);
}
}

The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow.

Run your application in your local environment again. You should be presented with a login experience now.

Adding Data

The initial scaffolding already has a pre-configured data backend defined in the amplify/data/resource.ts file. The default example will create a Todo model with content field.

Let's modify this to add the following:

  • A boolean isDone field.
  • An authorization rules specifying owners, authenticated via your Auth resource can "create", "read", "update", and "delete" their own records.
  • Update the defaultAuthorizationMode to sign API requests with the user authentication token.
import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
const schema = a.schema({
Todo: a
.model({
content: a.string(),
isDone: a.boolean(),
})
.authorization(allow => [allow.owner()]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: "userPool",
},
});

Next, let's implement UI to create, list, and delete the to-do items.

Amplify can automatically generate code for interacting with the backend API. Run the command in the terminal to generate dart model classes from the Data schema under lib/models:

Terminal
npx ampx generate graphql-client-code --format modelgen --model-target dart --out lib/models

Once you are done, add the API dependencies to your project. You will add amplify_api to connect your application with the Amplify API.

pubspec.yaml
dependencies:
amplify_api: ^1.0.0

After adding the dependencies, update the _configureAmplify method in your main.dart file to use the Amplify API:

main.dart
Future<void> _configureAmplify() async {
try {
await Amplify.addPlugins(
[
AmplifyAuthCognito(),
AmplifyAPI(modelProvider: ModelProvider.instance),
],
);
await Amplify.configure(outputs);
safePrint('Successfully configured');
} on Exception catch (e) {
safePrint('Error configuring Amplify: $e');
}
}

Next create a new widget called TodoScreen and add the following code:

main.dart
class TodoScreen extends StatefulWidget {
const TodoScreen({super.key});
State<TodoScreen> createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton.extended(
label: const Text('Add Random Todo'),
onPressed: () async {
final newTodo = Todo(
id: uuid(),
content: "Random Todo ${DateTime.now().toIso8601String()}",
isDone: false,
createdAt: TemporalDateTime(DateTime.now()),
updatedAt: TemporalDateTime(DateTime.now()),
);
final request = ModelMutations.create(newTodo);
final response = await Amplify.API.mutate(request: request).response;
if (response.hasErrors) {
safePrint('Creating Todo failed.');
} else {
safePrint('Creating Todo successful.');
}
_refreshTodos();
},
),
body: const Placeholder(),
);
}
}

This will create a random Todo every time a user clicks on the floating action button. You can see the ModelMutations.create method is used to create a new Todo.

And update the MyApp widget in your main.dart file like the following:

main.dart
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return Authenticator(
child: MaterialApp(
builder: Authenticator.builder(),
home: const SafeArea(
child: Scaffold(
body: Column(
children: [
SignOutButton(),
Expanded(child: TodoScreen()),
],
),
),
),
),
);
}
}

Next add a _todos list in _TodoScreenState to add the results from the API and call the refresh function:

main.dart
List<Todo> _todos = [];
void initState() {
super.initState();
_refreshTodos();
}

and create a new function called _refreshTodos:

main.dart
Future<void> _refreshTodos() async {
try {
final request = ModelQueries.list(Todo.classType);
final response = await Amplify.API.query(request: request).response;
final todos = response.data?.items;
if (response.hasErrors) {
safePrint('errors: ${response.errors}');
return;
}
setState(() {
_todos = todos!.whereType<Todo>().toList();
});
} on ApiException catch (e) {
safePrint('Query failed: $e');
}
}

and update the body with the following code:

main.dart
body: _todos.isEmpty == true
? const Center(
child: Text(
"The list is empty.\nAdd some items by clicking the floating action button.",
textAlign: TextAlign.center,
),
)
: ListView.builder(
itemCount: _todos.length,
itemBuilder: (context, index) {
final todo = _todos[index];
return Dismissible(
key: UniqueKey(),
confirmDismiss: (direction) async {
return false;
},
child: CheckboxListTile.adaptive(
value: todo.isDone,
title: Text(todo.content!),
onChanged: (isChecked) async { },
),
);
},
),

Now let's add a update and delete functionality.

For update, add the following code to the onChanged method of the CheckboxListTile.adaptive widget:

main.dart
final request = ModelMutations.update(
todo.copyWith(isDone: isChecked!),
);
final response =
await Amplify.API.mutate(request: request).response;
if (response.hasErrors) {
safePrint('Updating Todo failed. ${response.errors}');
} else {
safePrint('Updating Todo successful.');
await _refreshTodos();
}

This will call the ModelMutations.update method to update the Todo with a copied/updated version of the todo item. So now the checkbox will get an update as well.

For delete functionality, add the following code to the confirmDismiss method of the Dismissible widget:

main.dart
if (direction == DismissDirection.endToStart) {
final request = ModelMutations.delete(todo);
final response =
await Amplify.API.mutate(request: request).response;
if (response.hasErrors) {
safePrint('Updating Todo failed. ${response.errors}');
} else {
safePrint('Updating Todo successful.');
await _refreshTodos();
return true;
}
}
return false;

This will delete the Todo item when the user swipes the item from right to left. Now if you run the application you should see the following flow.

You can terminate the sandbox environment now to clean up the project.

Publishing changes to cloud

For publishing the changes to cloud, you need to create a remote git repository. For a detailed guide, you can follow the link here.

🥳 Success

That's it! You have successfully built a fullstack app on AWS Amplify. If you want to learn more about how to work with Amplify, here's the conceptual guide for how Amplify works.