AWS Amplify For Flutter Part 4: Amplify Storage

AWS Amplify For Flutter Part 4: Amplify Storage

This is the fourth instalment in a series about AWS Amplify for Flutter which gives you a clear picture about Amplify Storage

ยท

8 min read

In this series of AWS Amplify, we have seen Introduction and Setup of Amplify, Amplify DataStore and Amplify Authentication, In this article, we will see what Amplify Storage is, and how to implement it to our Flutter projects.

Amplify Storage service provides a way to store user and application-related data in public, private, and protected storage buckets. Amplify Storage has built-in support for AWS S3(Simple Storage Service), which means any file uploaded via Amplify Storage will be stored in S3 and managed manually via the S3 console.

In this article, we will see,

  • How to initialize and setup Amplify Storage in Flutter
  • How to upload and download a file from Storage
  • How to list all the files available in the Storage bucket
  • How to manage access control for files uploaded in Storage buckets

Amplify Storage core concepts

Amplify provides an abstraction layer for AWS S3. In general, S3 stores data in the form of objects in a bucket. These objects contains files and metadata related to that file. To store any file in S3, you must either upload it manually or upload it programmatically. When you upload a file, you can set permissions on the object and metadata manually. S3 buckets are containers for the objects. Each bucket can be configured for access control policies and regions where S3 will store data for quick access.

With Amplify Storage, it's crucial to understand access control. Amplify will only help you to abstract the storage process, but access control has to be configured by developers. You can apply access control policies at the time of storage setup or else you can update it at any time using Amplify CLI.

Initialise and setup Amplify Storage in Flutter

Prerequisites

  • Install Flutter SDK >= 1.20
  • iOS configured for at least iOS 11.0
  • Android configured for at least Android API level 21
  • Install and Configure Amplify CLI (Read the first article of this series.)

To add Storage to your Amplify project invoke the following CLI command:

amplify add storage

Enter the following when CLI asks:

? Please select from one of the below-mentioned services:
    `Content (Images, audio, video, etc.)`
? 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?
    `Yes`
? Do you want to use the default authentication and security configuration?
    `Default configuration`
? How do you want users to be able to sign in?
    `Username`
? Do you want to configure advanced settings?
    `No, I am done.`
? Please provide a friendly name for your resource that will be used to label this category in the project:
    `S3friendlyName`
? Please provide bucket name:
    `storagebucketname`
? Who should have access:
    `Auth and guest users`
? What kind of access do you want for Authenticated users?
    `create/update, read, delete`
? What kind of access do you want for Guest users?
    `create/update, read, delete`
? Do you want to add a Lambda Trigger for your S3 Bucket?
    `No`

Once the amplify add storage command is completed, run amplify push so that AWS will allocate a particular bucket for your project with provided configuration. Once completed, you can see amplifyconfiguration.dart has been modified with a newly created S3 bucket.

Install Amplify Storage package

Add the following package in your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  amplify_storage_s3: '<1.0.0'
  # reminder: amplify_auth_cognito should also be installed

After executing the previous command, initialize AmplifyStorageS3() and add it to the Amplify.addPlugins([]) list along with the configureAmplify() function, as with the other services of Amplify. Use the code given below to do this:

static configureAmplify() async {
    AmplifyAPI apiPlugin = AmplifyAPI();
    AmplifyStorageS3 storagePlugin = AmplifyStorageS3();

    Amplify.addPlugins([
      storagePlugin,
      apiPlugin,
    ]);

    // Once Plugins are added, configure Amplify
    // Note: Amplify can only be configured once.
    try {
      await Amplify.configure(amplifyconfig);
    } catch (e) {
      print(
          "Tried to reconfigure Amplify; this can occur when your app restarts on Android.");
    }
  }

Upload and download file from Storage

Amplify Storage has a different way of handling files. Unlike Firebase Storage which simply takes the file and returns a URL, Amplify Storage uses keys to upload and download a file. So when you upload a file, attach a unique key to that object and optionally store that key in your database according to your database scheme. Now when you want to download the file, simply fetch the key and pass it to the storage plugin function which will in return the file associated with that unique key.

This key mechanism for storing files allows Amplify to easily manage access control. Optionally you can also fetch the URL for the file, but it will not necessarily mean that you can access that file via URL because of access control policies. If you choose to make the file public, then you can access the file directly via URL.

In the backend, when you call the download function with the key, it will send an AWS Cognito token with that as well. Post this, S3 will verify if that user has access to this file and it will only return the file is it does.

Let's see the code for uploading a file:

`import 'dart:io'; // to add the File class

// ...

// // Option 1 - get the filepath yourself
File file = File('$path/filename.txt')

// // Option 2 - using file-picker
// import 'package:file_picker/file_picker.dart';
// ...
// File local = await FilePicker.getFile(type: FileType.image);
// local.existsSync();

final key = new DateTime.now().toString();
Map<String, String> metadata = <String, String>{};
metadata['name'] = 'filename';
metadata['desc'] = 'A test file';
S3UploadFileOptions options = S3UploadFileOptions(accessLevel: StorageAccessLevel.guest, metadata: metadata);
try {
  UploadFileResult result = await Amplify.Storage.uploadFile(
    key: key,
    local: file,
    options: options
  );
} on StorageException catch (e) {
  print(e.message);
}

Downloading the file

Once you have uploaded a file with key file-key, you can download that file with the downloadFile function from the Storage plugin. Use the following code to perform this function:

try {
  DownloadFileResult result = await Amplify.Storage.downloadFile(
    key: 'file-key',
    local: new File('$path/download.png')
  );
} on StorageException catch (e) {
  print(e.message);
}

Get URL of uploaded file

Once you have uploaded a file, you can retrieve the URL using the key for the file as shown below:


try {
  GetUrlResult result =
    await Amplify.Storage.getUrl(key: "file-key");
  print(result.url); 
} on StorageException catch (e) {
  print(e.message);
}

List all the files in Storage

You can simply call the list() function of the Storage plugin to get the list of all uploaded files in the bucket by using the following code:

try {
  ListResult res = await Amplify.Storage.list();
print(res);
} on StorageException catch (e) {
  print(e.message);
}

To list only protected or private files, you can use the options parameter. Using optional parameters you can list all the files uploaded by a specific user by providing the user ID in the option parameters as shown below:

try {
  S3ListOptions options = S3ListOptions(
    targetIdentityId: "otherUserID",
    accessLevel: StorageAccessLevel.protected
  );

  ListResult res = await Amplify.Storage.list(
    options: options
  );
} on StorageException catch (e) {
  print(e.message);
}

Managing access control for files

At the time of adding Storage via Amplify CLI, you also get the option to configure access control. You can also configure different roles for authenticated and anonymous users. Additionally, you can configure the role of each file before uploading a file; these roles can be guest, private or protected.

  • Guest - Files can be accessed by all the users of your application.
  • Protected - Files can be read by all the users, but the creator of that file can delete and update it.
  • Private - Files can only be read and written by the creator.

Note: Guest user does not mean your file has public access; Guest user has a Cognito session ID which will be used to identify that user as a Guest user. To learn more about the anonymous users or Amplify Auth refer to Part 3 of this series.

Now, let's see how to upload and download a protected file. To upload a protected file, you need to pass S3UploadFileOptions object in uploadFile function.

try {
  // use a file selection mechanism of your choice
  File file = await FilePicker.getFile(type: FileType.image);
  final key = new DateTime.now().toString();
  final local = file.absolute.path;
  S3UploadFileOptions options = S3UploadFileOptions(
    accessLevel: StorageAccessLevel.protected
  );
  UploadFileResult result = await Amplify.Storage.uploadFile(
    key: key,
    local: local,
    options: options
  );
} catch (e) {
  print(e.toString());
}

If you notice in S3, all the protected files will have /protected/[USERID]/{file} folder structure.

To read a protected file, you have to pass the USERID of the user that has created this file.

try {
  S3DownloadFileOptions options = S3DownloadFileOptions(
    targetIdentityId: "[USER_ID]",
    accessLevel: StorageAccessLevel.protected
  );
  DownloadFileResult result = await Amplify.Storage.downloadFile(
    key: key,
    local: new File('$path/download.png')
    options: options
  );
} catch (e) {
  print(e.toString());
}

Upload and download a private file

To make a file private, just choose StorageAccessLevel.private as the options when you upload the file and use the following code:

S3UploadFileOptions options = S3UploadFileOptions(accessLevel: StorageAccessLevel.private);

This will create a /private/[USERID] folder and store the file in that folder. To download/read that file, specify the same accessLevel as shown below:

S3DownloadFileOptions options = S3DownloadFileOptions(accessLevel: StorageAccessLevel.private);

Conclusion

That's all for AWS Amplify Storage. Thank you so much for reading ๐Ÿ˜. If you like this article, then please leave a ๐Ÿ‘ and come say hi on Twitter.

ย