Flutter file_picker and Uploading files to Amazon S3 with AWS Amplify
#17DaysOfFlutter
File picking is one of the most important features that your application can have. You can use it for image or any kind of file uploading mechanism.
For uploading files, there are many alternatives. One can use their own service as well as other helpful services like AWS Amplify. In this video you will learn about using native file picker and uploading those files to cloud by using AWS Amplify.
Requirements:
Flutter 3.0 or higher
Amplify CLI with an AWS account
In case you have not created an AWS account before, you can follow this guide that I created.
Be sure to also do any platform specific setup for AWS Amplify from the visual guide here.
Using file_picker
If your application is only relying on picking images, you can use image_picker. However, the image picker only supports iOS, Android and Web. But there is another alternative, you can use. It is a library called file_picker. A package that allows you to use the native file explorer to pick single or multiple files, with extensions filtering support.
Creating a new Flutter Project
You will create a new project with the flutter create
command.
flutter create file_upload_example --org dev.salih
After Flutter 3.0, this will create a new project with all platforms.
Now that you created a project, open the project with your preferred IDE.
Adding file_picker to the project
After file_picker
4.0, the library made our lives way more easier. For adding the file_picking capabilities, you need to add the following to pubspec.yaml file.
dependencies:
file_picker: ^5.2.5
After that run flutter pub get
.
Adding file picking mechanism
Go to lib/main.dart and remove everything in the code. Afterwards, add the following.
import 'package:flutter/material.dart';
void main() {
runApp(const FilePickingApp());
}
class FilePickingApp extends StatelessWidget {
const FilePickingApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('File Picking')),
body: const FilePickingView(),
),
);
}
}
class FilePickingView extends StatefulWidget {
const FilePickingView({Key? key}) : super(key: key);
@override
State<FilePickingView> createState() => _FilePickingViewState();
}
class _FilePickingViewState extends State<FilePickingView> {
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
Now it is time to add the UI to FilePickingView
. Update the UI with the following:
class _FilePickingViewState extends State<FilePickingView> {
bool _isFileSelected = false;
void _selectFile() {
setState(() {
_isFileSelected = true;
});
}
void _deselectFile() {
setState(() {
_isFileSelected = false;
});
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_isFileSelected
? 'You selected a file. Click the button below to remove file'
: 'Click the button below to select a file.',
),
ElevatedButton(
onPressed: _isFileSelected ? _deselectFile : _selectFile,
child: Text(isFileSelected ? 'Deselect File' : 'Select File'),
)
],
),
);
}
}
Now if you run the application, it should look like the following:
Now that you have the basic app structure, let's continue with file picking mechanism.
Go to the _selectFile
function and update it as follows:
Future<void> _selectFile() async {
final result = await FilePicker.platform.pickFiles();
if (result != null) {
final file = result.files.single;
setState(() {
_isFileSelected = true;
});
}
}
If you go to and run the application. You can see that the application is opening a native file picker on all the platforms.
Alright, now it is time to add AWS Amplify to your project for cloud upload mechanism.
Adding AWS Amplify for Uploading Files to AWS S3
AWS Amplify is the set of tools that AWS provides you to make your life easier by helping Frontend Devs to become full-stack devs. AWS Amplify supports many platforms and one of them is Flutter. In case you are wondering about AWS Amplify, you can check out the video that I created a few days back.
Initializing AWS Amplify with Flutter Application
Open up your terminal and direct yourself to the root directory of your application. Initialize AWS Amplify in your Flutter application by writng amplify init
:
(1)
msalihg@ file_upload_example % amplify init
Note: It is recommended to run this command from the root of your app directory
(2)
? Enter a name for the project fileuploadexample
The following configuration will be applied:
(3)
Project information
| Name: fileuploadexample
| Environment: dev
| Default editor: Visual Studio Code
| App type: flutter
| Configuration file location: ./lib/
? Initialize the project with the above configuration? Yes
Using default provider awscloudformation
(4)
? 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 salihgueler-frankfurt
Adding backend environment dev to AWS Amplify app: <project-id>
Let's check out the flow in detail:
(1):
amplify init
is called to initialize the AWS Amplify in our app.(2): You can either accept the default value that is shown in parenthesis.
(3): Amplify defines your project structure, you can accept themor configure them.
(4): Select AWS profile and pick the AWS account that you created earlier.
Now let's add the file upload mechanism with AWS Amplify.
Be sure to add any platform specific setup to your project from here --> https://docs.amplify.aws/lib/project-setup/platform-setup/q/platform/flutter/
Add AWS Amplify Storage for Uploading Files
For adding Amplify storage, you need to write amplify add storage
to your terminal:
(1)
msalihg file_upload_example % amplify add storage
(2)
? Select from one of the below mentioned services: Content (Images, audio, video, etc.)
(3)
✔ You need to add auth (Amazon Cognito) to your project in order to add storage for user files. Do you want to add auth now? (Y/n) · yes
Using service: Cognito, provided by: awscloudformation
The current configured provider is Amazon Cognito.
(4)
Do you want to use the default authentication and security configuration? Default configuration
Warning: you will not be able to edit these selections.
How do you want users to be able to sign in? Username
Do you want to configure advanced settings? No, I am done.
✅ Successfully added auth resource <project-id> locally
(5)
✔ Provide a friendly name for your resource that will be used to label this category in the project: · <resouce-id>
(6)
✔ Provide bucket name: · <bucket-name>
(7)
✔ Who should have access: · Auth users only
(8)
✔ What kind of access do you want for Authenticated users? · create/update, read, delete
(9)
✔ Do you want to add a Lambda Trigger for your S3 Bucket? (y/N) · no
Now let's examine the steps that we have taken above:
(1): Write
amplify add storage
to start the process.(2): Be sure to select Content (Images, audio, video, etc.).
(3): For using S3 buckets, you need to do an auth either with regular accounts or regular + guest accounts.
(4): Pick the default auth configuration with username login.
(5): Once you add the authentication add a default name for your resources or select the default one.
(6): Select a bucket name or select the default one.
(7): Select the people who can upload files. It can be either authenticated users or auth +guest users. I picked the Auth users
(8): Select create/update, read, delete by using arrows and space
(9): No need for a Lambda trigger for now.
Lastly, write amplify push
to create these resources on the cloud. Next stage is adding authentication flow.
Adding Authenticator UI
Amplify Authenticator UI is the UI library that AWS provides you to make your authentication flow implementation better.
For using it add the following to pubspec.yaml:
dependencies:
amplify_flutter: ^1.0.0-0
amplify_auth_cognito: ^1.0.0-0
amplify_authenticator: ^1.0.0-0
Afterwards, update the main function like the following by also adding _configureAmplify
function:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await _configureAmplify();
runApp(const FilePickingApp());
}
Future<void> _configureAmplify() async {
try {
await Amplify.addPlugin(AmplifyAuthCognito());
await Amplify.configure(amplifyconfig);
safePrint('Successfully configured');
} on Exception catch (e) {
safePrint('Error configuring Amplify: $e');
}
}
This way, you have ensured that Amplify library that you will be using is configured before the app started.
Lastly update the build
function in the FilePickingApp
class:
@override
Widget build(BuildContext context) {
return Authenticator(
child: MaterialApp(
builder: Authenticator.builder(),
home: Scaffold(
appBar: AppBar(title: const Text('File Picking')),
body: const FilePickingView(),
),
),
);
}
Now you are ready to implement file upload mechanism.
Uploading files
Before you upload the first file, let's update the class a bit:
class _FilePickingViewState extends State<FilePickingView> {
PlatformFile? _selectedFile;
Future<void> _selectFile() async {
final result = await FilePicker.platform.pickFiles();
if (result != null) {
setState(() {
_selectedFile = result.files.single;
});
}
}
void _deselectFile() {
setState(() {
_selectedFile = null;
});
}
@override
Widget build(BuildContext context) {
final isFileSelected = _selectedFile != null;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
isFileSelected
? 'You selected a file at path ${_selectedFile?.path}'
: 'Click the button below to select a file.',
),
ElevatedButton(
onPressed: isFileSelected ? _deselectFile : _selectFile,
child: Text(isFileSelected ? 'Select File' : 'Upload File'),
)
],
),
);
}
}
This has a reference to the file to be uploaded now. Let's update the file. First add the libraries to the pubspec.yaml file:
dependencies:
amplify_storage_s3: ^1.0.0-0
Afterwards, update the _configureAmplify
function from the main.dart:
Future<void> _configureAmplify() async {
try {
await Amplify.addPlugin(AmplifyStorageS3());
await Amplify.addPlugin(AmplifyAuthCognito());
await Amplify.configure(amplifyconfig);
safePrint('Successfully configured');
} on Exception catch (e) {
safePrint('Error configuring Amplify: $e');
}
}
Lastly, update the _selectFile
and add the _uploadExampleData
function to upload the file and add a value representation to the uploaded file in _FilePickingViewState
class:
// (1)
double _uploadedPercentage = 0;
Future<void> _selectFile() async {
final result = await FilePicker.platform.pickFiles();
if (result != null) {
// (2)
uploadExampleData(result.files.first);
}
}
Future<void> uploadExampleData(PlatformFile file) async {
try {
setState(() {
_selectedFile = file;
});
final result = await Amplify.Storage.uploadFile(
// (3)
localFile: AWSFile.fromPath(
file.path!,
),
key: file.name,
onProgress: (progress) {
// (4)
setState(() {
_uploadedPercentage = progress.fractionCompleted;
});
},
).result;
safePrint('Uploaded data to location: ${result.uploadedItem.key}');
_deselectFile();
} on StorageException catch (e) {
safePrint(e.message);
}
}
(1): This is the value to keep the upload percentage
(2): If the file is selected, the upload function will be called
(3): AWSFile is a special file format to create from the path
(4): Each percentage change is shown with the fraction completed
and update the build
function:
@override
Widget build(BuildContext context) {
final isFileSelected = _selectedFile != null;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
isFileSelected
? 'You selected a file at path ${_selectedFile?.path}'
: 'Click the button below to select a file.',
),
if (isFileSelected)
CircularProgressIndicator(
value: _uploadedPercentage,
),
ElevatedButton(
onPressed: isFileSelected ? _deselectFile : _selectFile,
child: Text(isFileSelected ? 'Deselect File' : 'Select File'),
)
],
),
);
}
Now if you run your application, you can see that the app will show a login flow and uploading mechanism for your files.
BONUS: Listing Files
We can have all the uploads on the world, if we do not show it, that won’t matter. In this section, you will add a new toolbar icon to list the uploaded files.
First upload the FilePickingApp
class with new icon buttons:
class FilePickingApp extends StatelessWidget {
const FilePickingApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Authenticator(
child: MaterialApp(
debugShowCheckedModeBanner: false,
builder: Authenticator.builder(),
home: Builder(builder: (context) {
return Scaffold(
appBar: AppBar(
title: const Text('File Picking'),
actions: [
IconButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return const ListPreviousFilesView();
},
),
);
},
icon: const Icon(Icons.list_alt),
),
IconButton(
onPressed: () {
Amplify.Auth.signOut();
},
icon: const Icon(Icons.exit_to_app),
),
],
),
body: const FilePickingView(),
);
}),
),
);
}
}
Now you will see ListPreviousFilesView
is giving you an error. The reason is that you have not added that UI yet. Add the ListPreviousFilesView
widget as below:
class ListPreviousFilesView extends StatefulWidget {
const ListPreviousFilesView({Key? key}) : super(key: key);
@override
State<ListPreviousFilesView> createState() => _ListPreviousFilesViewState();
}
class _ListPreviousFilesViewState extends State<ListPreviousFilesView> {
final items = <StorageItem>[];
Future<void> listAlbum() async {
try {
// (1)
final result = await Amplify.Storage.list().result;
// (2)
items
..clear()
..addAll(result.items);
safePrint('Listed items: ${result.items}');
} on StorageException catch (e) {
safePrint(e.message);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Previous Uploads')),
// (3)
body: FutureBuilder<void>(
future: listAlbum(),
builder: ((context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else {
// (4)
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(title: Text(items[index].key));
},
);
}
}),
),
);
}
}
(1): You are listing all the files. You can see how you can reach the guest files as well with the guide here.
(2): Retrieved items are added to the list by removing the previous ones.
(3): Calls the async function with the FutureBuilder
(4): Fetched files are shown as list items
Wrapping up
If you run the application right now, you can see the behavior in the video below:
You can use AWS Amplify to use AWS resources with the other technologies. You can see that it also makes your life easier and you get feedback on each file update with each fraction change.
If you have any questions or any feedback you can reach out to me over Twitter and see the project over GitHub.