This is the first part of a three part series on using Flutter with AWS Amplify. In this series we will learn how to setup a Flutter project with Amplify, to make the most of the Amplify provided categories/features like auth, storage and API. In Part 1 we will go through how to scaffold a project and build out an initial UI, before creating our application’s data storage model and provisioning out the AWS cloud backend. In part two and part three of this series we will build on this application and add additional features available in AWS Amplify, such as Auth, cloud storage and analytics.
Flutter was introduced by Google as an open source cross platform development environment for IOS and Android that uses Dart as its programming language. Flutter itself is relatively new( v1.0 was released December 2018 ). Since that time is has expanded beyond native mobile into web and desktop applications. At the time of writing, Flutter is gaining greater popularity than React Native on both GitHub and Stack Overflow due in part to its fantastic developer experience.
First launched in late 2017, AWS Amplify is a package of tools and services designed to make it easy for developers to create and launch apps in AWS, including code libraries, ready-to-use components, and a built-in CLI.
For those who have not come across AWS Amplify before It is an opinionated framework that makes assumptions on the developer’s behalf, so less time on scaffolding boiler plate code and more time building features. In 2020 Amplify released a beta set of open source client librariesthat support Flutter, these subsequently went GA in Feb 2021.
In part 1 of this series we are going to build out an application that helps users to store the ‘humble’ shopping list. This shopping list application should store a list of items that a household would like to buy, and as members visit the shops they can tick off items from the list. The ultimate aim of ‘smart shopping list’ is that the chore of shopping can be shared ! . So lets look at the typical screen flow of our application.
- Mark Item as complete (purchased)
- Add New Item
- Add new Item to List
- View List
A pretty straight forward flow, so let’s get started …
Before we start building out the UI, we need to install and configure our development environment to run Flutter — this is beyond the scope of this blog, however just follow these steps to set up your local Flutter environment.
For my IDE of choice I will be using VSCode, however Android Studio is also supported with setup instructions here. Once you have configured the editor, test your configuration and then finally write a quick hello world application.
| Note: There are also some great tips and tricks for Android Developers in the flutter documentation here.
Once we are all setup lets jump in and build our UI. All the code for this demo you can find here https://github.com/deekob/flutter_droidcon — The first thing to do is create a new Flutter Project — you can either do this at the command line using ‘flutter create’ or by using your IDE of choice.
The entry point to any flutter project is main.dart — lets change that first to include our theme and styling.
This is nothing more than the entry point to the application, where we can setup the UI theme before then calling our first View — In this case I have called the first view ShoppingListItemsView — which I will define in a new class file.
This is the main part of our UI, its broken up into a number of Widgets, as everything in Flutter is a widget — In this case we are using a StatefulWidget to hold the state (Shopping List Item) that we are creating. The StatefulWidget has a state that has one @override method called build(BuildContext context) — and this is where we create our UI.
The UI itself we have a _navbar that simply holds the apps title ‘My Shopping List’, a _floatingActionButton that when pressed, will push a modal from the bottom containing the widget called _newItemView. Finally in this widget we have a TextEditingController that holds the value of our new item and a button to save the item. Notice that the onPressed() event in the ElevatedButton isnt doing anything yet. Before we can persist anything however we first need to define our application data model.
Now with the UI done we want to start thinking about persisting the application’s data. To do this we will use the Amplify Admin UI to model what the data objects will be, what fields and types they contain, which fields are optional and which are mandatory. So, lets start by opening the Admin UI sandbox web page found at AdminUI sandbox.
From here we get to choose what we are building, either:
Hosting a Web Site
Ultimately, we want to build out an application backend for this application, so we choose the first option ‘Create an app backend ’.
We can either start modelling the data layer, set up an auth layer or configure file storage for our application. In this case we are focusing on data, so lets choose ‘Data >>’
Next, we start to think about our data model, its relationships and how to test it locally. This can all be done when we select the ‘Data’ feature and then selecting the ‘Blank Schema’ option, as we want to start from scratch, finally hitting ‘Create New Schema’ to get going.
With the Admin UI data modelling feature we can do things like choose the names and types for our data. For individual types we can decide if they are simple types like ‘String’ or more complex scalar types like AWSDateTime. Along with model / field names and types we can also mark the fields as mandatory by ticking the ‘isRequired’ checkbox, or mark them as being a collection by ticking the ‘is array’ checkbox.
We can easily define relationships between model elements, currently I can define a 1:1, 1:M and M:M relationships inside the AdminUI.
The model we want to define is a ShoppingListItem must have a name ( itemName ) and a flag( isComplete ) to let the user know if the item has been bought. This model will look something like this
For more in depth instructions on how to get started with data modelling, check out Amplify Docs. Next we can test this locally to see if it fits our understanding and the existing front-end code. Selecting ‘Test this Locally’ brings up a set of steps we need to perform to integrate this new model into the application.
The steps on how to build and test our newly created schema are nicely laid out for us as a series of steps in the AdminUI, so let’s step through these. The first one looks like this :
Choose the type of App we are building — we can choose either a Web, IOS / Android or cross- platform and then make the appropriate framework/language selection based on our platform selection. For this example, we are choosing a cross platform ( even though we will be targeting Android ) with Flutter as the framework.
This step will contain the commands needed if you are creating an app from scratch. As we are using an existing frontend UI, we will skip this step.
The next step contains the commands needed to install the Amplify CLI, which we then use to pull down the data model we have created in the AdminUI Sandbox. This ‘amplify pull’ command will also in our case generate the correct types that we can use to access our data model in our Dart code.
This step shows us how to install the Amplify Flutter libraries that we can use to interact with the types pulled down and generated in Step 3. It also shows us how to configure our application to start implementing Amplify client libraries in our frontend code and finally how to configure the native projects for IOS and Android. In summary what we will do is:
Add the dependencies to pubspec.yaml :
Click Pub Get in the “Flutter commands” bar to install Amplify Libraries
(iOS only) Open ios/Podfile and update the platform target:
Go to main.dart and add the following lines to initialize the Amplify Libraries:
Finally, this step shows us how to build CRUD operations into our application by supplying code snippets for Amplify Datastore. Datastore is an Amplify category/feature that enables applications to be built with a client first programming model. The beauty of the datastore category is that once we deploy a backend for our app into an AWS account, the data persistence mechanisms we have defined in datastore will mean all application data will automatically be synced to all connected clients. This is due to the service that underpins datastore, AWS Appsync.
For a more in-depth view of what datastore can do -> check this out
So, for the ShoppingListItem model, if I want to persist items into our local datastore we would use the datastore API and code something like:
The datastore API is pretty elegant, in that with this line of code we are storing the result in your local sqllite DB. But before we check to see if this is working, lets explore how we would code the other CRUD operations. To do this we create a data repository class to abstract all of the data operations in our app. This class look like this
Note that the args for the updateListItem function have a bool? — this is because the isComplete field is non-mandatory and Dart is inherently null safe as are theAmplify client libs. — To find out more about Dart null safety
So now we have modelled our data in AdminUI, installed the Amplify CLI, pulled down the required libraries and model schema from the AdminUI sandbox, generated types in Dart for our model AND changed our application to call the datastore API for these types. We have done a lot in a short time, the last thing we need to do is test it all works. So we can simply run our application and excercise the creating of shopping list items. As mentioned previously all the items created will be stored only on the device
If we are not happy with our model, we can go back to our sandbox and re-model our data again. We can re-run the ‘amplify pull ’ command to retrieve any changes to our model and re-generate the types accordingly inside our codebase. This way we can quickly iterate on our model until it is fit for purpose.
When we are finally happy with the application and the data that it is persisting, we can move on to connecting our AWS account up and start to think about adding other features like datasync, authentication, content management and user management.
Deploying the app into a AWS account is the final step of the Sandbox Admin UI process and can be kicked off by selecting ‘Deploy to AWS’.
We are then prompted to either deploy into a new AWS account or deploy to an existing account. If we choose a new account, we will be taken to the account creation wizard and stepped through the process of creating a brand-new AWS account. However, in this case, lets assume we will use an existing AWS account and so we are subsequently prompted to login to that account in the browser.
Once successfully logged in on the browser, the next step takes us to the AWS console where we give our new backend a name.
Once the app has been given a name and region click on ‘ Confirm Deployment ’ will begin the deployment process of the backend into our AWS account. This may take a few minutes to execute…
Once the deployment has completed, the next step is to open the Admin UI page for our app and start adding additional features to the backend. To do this simply click on ‘Open Admin UI’
Once the AdminUI is open, you will see that it looks very different than it did as a Sandbox, this is because now with the addition of an AWS account there is a large array of features we can add that were not available in the sandbox like authorization and storage, that I will cover in later posts.
Before we can do anything, we need to connect our local development environment to our newly deployed backend. To do this we need to find the ‘amplify pull’ command to run locally. To find out what it is, click on the ‘Local Setup Instructions’ link as shown.
So now running:
amplify pull -appId [xxxxxxxx] -envName staging
will then kick off an authentication challenge, once that successfully completes we will then be prompted to set up our local amplify project so it is ‘in-sync’ with our backend. The main things it will synchronise are the cloudformation templates that describe the backend services and generate any types from the objects created in the data model.
Next thing we will do is to test that our data model is deployed correctly and that syncing updates to our app works as expected. To do this we can use a feature of AdminUI called ‘App Content Management’.
App Content Management allows us to create data in our back-end for testing but also it allows us to create markdown content. For our purposes we will create some test ShoppingListItems using the built-in editor that we will then use to test data sync. We will create two items for our Shopping List [Cheese , Crackers ]
Once the items are created, we can start up our app in the local dev environment and ❗ boom ❗ without any code changes at all we have both of these ShoppingListItems displaying in the app.
So that's it for Part 1 in this series on Amplify and Flutter on Android — In this post we have explored how to set up our initial project, we have built our UI using Flutter, installed the Amplify tools and data modeled and tested our application data using Amplify Admin UI Sandbox. We did all this without having an AWS account, as we used one of Amplify’s categories/features called Datastore which allows us to store application data locally on the device.
We then deployed our solution to an AWS account, this enabled us to test the persistence of the data in the cloud. With Amplify provisioning the backend of AWS Appsync and Amazon DynamoDB which was abstracted from the developer by the Amplify Datastore category. Another real advantage of using Datastore is that we were then able to sync data to the cloud and other connected clients without changing any of the api calls, as Amplify Datastore takes care of all of this heavy lifting for us.
As you can see using Amplify and Flutter together are a great way to accelerate your application development cycle on Android, in Part 2 of this series we will add login and signup to our application and start building the concept of a user profile.
See you then …