Building a social media app with AWS Amplify and Flutter(PART 1)

Building a social media app with AWS Amplify and Flutter(PART 1)

Dear Reader,
when I began learning how to create mobile applications, all I ever wanted to build was the next Facebook. And deep down within, I 100% believed I could create the next big social media app that'll go head-on with Facebook😅.

The year was 2014
I actually did create one called tori (That's pidgin English for Story), and struggle tooth and nail to get users on it 🥲.

The backend was a REST API built with PHP slim Framework with a MySQL database and hosted on Digital Ocean.

Developing that app, made me understand the true meaning of struggle. Yeah! The struggle with building both backend and frontend, while getting users to use it was real
Needless to say, the head-on battle with Facebook didn't happen.

I wish serverless was ubiquitous back then as it is today.

Well, it's 2021, and we are in luck. Serverless is here, it's big, bold, matured, and serves the aws amplify framework for rapidly creating modern full stack mobile and web applications.

With aws amplify framework, you can develop offline first scalable and highly performant full-stack serverless Graphql apps with ease.
In this post series, our main focus would be on building each individual component(login and registration, profile, home page, posts....) for our social media application.

I've created a Github repo and separated each component into branches, with visual screenshots. I enjoy showing the outputs.

Now we know what we are about to build. So how do we go about building it?
Firstly, Our app has to be offline first. What do I mean by that?
The app is supposed to function even when there's no internet connection. Offline-first apps make it possible to move content off the server and onto the phone. If an app only has to go to the server when it needs to, rather than all the time, it will be faster and more reliable.
How's that supposed to happen?
Amplify's DataStore provides an on-device data storage service to read, write and observe data in an online as well as offline mode and sync that data to a cloud database (DynamoDB) seamlessly.
Data modeling in DataStore is done with GraphQL which creates models in the native language of your framework (Dart,JS, Java, Swift, etc.).
With DataStore, you can also create offline-only apps without an AWS account or without provisioning any backend service like AWS AppSync or AWS DynamoDB.
DataStore also includes Delta Sync and several auto conflict resolution strategies with GraphQL.

How DataStore Works

It is well elaborated in this beautiful article, written by the Amplify Team. How Datastore works
Secondly, we need to determine and map out all entities of our app.
For this app, we'll have just 4.

  • User
  • Post
  • Comments
  • Chats

Relationships between Entities

  • A User can create many Post. A Post can only belong to a single User. A Post can have many comments. A comment can only belong to a single Post
    So there's a n:m(one to many) relationship between User and Post, And a n:m(one to many) relationship between Post and Comments.

  • A User can have many Chats and a Chat can belong to many Users. Therefore User and Chats have a m:m(many to many) relationship.

We'll visually see this in the Data Modeling editor provided by aws amplify.

Here's how the complete schema design looks like

data_model_firends.png

Let's get started

Prerequisite

Install and Configure amplify

The Amplify Command Line Interface (CLI) is a unified toolchain to create AWS cloud services for your app. Let’s go ahead and install the Amplify CLI

npm install -g @aws-amplify/cli


When it's done installing, setup the Amplify CLI by running

amplify configure


You'll be asked to sign in to your AWS account and create a user with AdministrativeAccess to your account to provision AWS resources for you like AppSync, Cognito etc.. Just follow the instructions in the CLI to complete it.

Once the user is created, Amplify CLI will ask you to provide the accessKeyId and the secretAccessKey to connect Amplify CLI with your newly created IAM user.

Enter the access key of the newly created user:
? accessKeyId:  # YOUR_ACCESS_KEY_ID
? secretAccessKey:  # YOUR_SECRET_ACCESS_KEY
This would update/create the AWS Profile in your local machine
? Profile Name:  # (default)

Successfully set up the new user.

And that's it for the setup.

Setup a FullStack project

I'll be using Android studio as my main IDE. There's also vscode that is fully supported for flutter development.
Create a new flutter project and give it a name of your choice. I'll name mine friends

Screen Shot 2021-09-22 at 07.26.24.png

Screen Shot 2021-09-22 at 07.27.09.png

Add Amplify to your application

  • From your project root directory, find and modify pubspec.yaml and add the Amplify plugins, alongside all other plugins we'll need, to the project dependencies.
environment:
  sdk: ">=2.12.0 <3.0.0"

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter
  amplify_flutter: ^0.2.4
  amplify_datastore: ^0.2.4
  amplify_auth_cognito: ^0.2.4
  amplify_storage_s3: ^0.2.4
  amplify_api: ^0.2.4
  provider: ^5.0.0
  image_picker: ^0.8.2
  path_provider: ^2.0.2
  image_cropper: ^1.2.1
  cached_network_image: ^3.1.0

I'll explain the significance of each plugin as we move through building the application. For now, install the dependencies by running

flutter pub get

Update target iOS platform

From your project root, navigate to the ios/ directory and modify the Podfile using a text editor of your choice and update the target iOS platform to 13.0 or higher.

platform :ios, '13.0'

Update target Android SDK version

From your project root, navigate to the android/app/ directory and modify build.gradle using a text editor of your choice and update the target Android SDK version to 21 or higher:

minSdkVersion 21

We are now set.
From the amplify docs,
With the basic setup complete, you can now model the data your application will work with. <br> Amplify DataStore persists the modeled data on your local device and can even synchronize to a backend API without writing any additional code. <br> These models are specified as GraphQL schemas. <br> You can learn more about GraphQL schemas and data modeling. [HERE](https://phatrabbitapps.com/building-full-stack-serverless-application-with-amplify-flutter-graphql-aws-cdk-and-typescript) <br> For now, let’s start by generating some initial data models.

Create data model

Navigate to Data feature in AWS Amplify Admin UI and, with the todo-schema selected, click create new schema

amplify.png On the Data modeling screen, you should see your Todo data model. Go ahead and add the other entities, based on this image. data_model_firends.png Once you are done, click on next and follow the instructions on the next screen.

Screen Shot 2021-09-22 at 08.06.01.png

Screen Shot 2021-09-22 at 08.12.35.png If you followed all the instructions in the setup screen, Amplify should have generated several directories and configuration files in your project for you.

Screen Shot 2021-09-22 at 08.11.44.png

Connect to the Cloud

Click on deploy to AWS. From the creation form, give your app a name and select a prefered deployment zone.

Screen Shot 2021-09-22 at 08.13.12.png Let’s take a look at the generated GraphQL schema.
From the project root folder, navigate to amplify/backend/api/ and open up schema.graphql.
Here's how it looks like

enum ChatItemType {
  TEXT
  IMAGE
}

type ChatItem @model @auth(rules: [{allow: public}]) @key(name: "byChat", fields: ["chatID"]) {
  id: ID!
  senderId: String!
  receiverId: String!
  message: String
  imageUrl: String
  itemType: ChatItemType!
  createdOn: AWSDateTime
  chat: Chat @connection
  chatID: ID
}

type Chat @model @auth(rules: [{allow: public}]) {
  id: ID!
  receiverId: String!
  senderId: String!
  createdOn: AWSDateTime
  updatedOn: AWSDateTime
  chatItems: [ChatItem] @connection(keyName: "byChat", fields: ["id"])
  chatUsers: [ChatUser] @connection(keyName: "byChat", fields: ["id"])
}

type User @model @auth(rules: [{allow: public}]) {
  id: ID!
  username: String!
  firstName: String!
  lastName: String
  isVerified: Boolean
  profilePicUrl: String
  email: AWSEmail
  createdOn: AWSDateTime
  updatedOn: AWSDateTime
  posts: [Post] @connection(keyName: "byUser", fields: ["id"])
  chats: [ChatUser] @connection(keyName: "byUser", fields: ["id"])
}

enum PostStatus {
  CREATED
  UPDATED
  DELETED
}

type Comment @model @auth(rules: [{allow: public}]) @key(name: "byPost", fields: ["postID"]) {
  id: ID!
  createdOn: AWSDateTime!
  updatedOn: AWSDateTime
  userId: String
  post: Post @connection
  postID: ID
  commentText: String!
}

enum PostType {
  TEXT
  TEXT_IMAGE
}

type Post @model @auth(rules: [{allow: public}]) @key(name: "byUsers", fields: ["userID"]) @key(name: "byUser", fields: ["userID"]) {
  id: ID!
  content: String!
  postImageUrl: String
  status: PostStatus!
  createdOn: AWSDateTime
  updatedOn: AWSDateTime
  comments: [Comment] @connection(keyName: "byPost", fields: ["id"])
  type: PostType!
  user: User @connection
  userID: ID
}

type ChatUser @model(queries: null) @key(name: "byChat", fields: ["chatID", "userID"]) @key(name: "byUser", fields: ["userID", "chatID"]) @auth(rules: [{allow: public}, {allow: public}]) {
  id: ID!
  chatID: ID!
  userID: ID!
  chat: Chat! @connection(fields: ["chatID"])
  user: User! @connection(fields: ["userID"])
}

If you want to know what @model,@key etc means, please check out this tutorial where I give a detailed overview of all that. phatrabbitapps.com/building-serverless-appl...

Authentication

We'll need users to be authenticated before accessing some app features such as, posting, commenting, seeing posts etc.. So let's go ahead and add the auth category.

The app would allow 2 types of authentication mechanism

  • Default Sign in with username and password
  • Sign in with Google.
    Run the command below and select default configuration with social provider.

    The Amplify Docs have outlined the steps required to configure Sign in with google, i recommend you check it out and follow the guide step by step.
amplify add auth

Screen Shot 2021-09-22 at 16.18.40.png

Add Storage

Posts can contain images. Each user can add a profile picture. Therefore we'll need to add the amplify storage category, to store the uploaded images.

amplify add storage

Screen Shot 2021-09-25 at 09.36.53.png

Update API

We have to update our Graphql API. Remember that the schema.graphql is saved in the backend/api folder of our project. And our datastore models are generated from this schema.

Each time you make an update to the schema.graphql file, be sure to update the API (amplify update API) and push(amplify push`) the update to the cloud. Then regenerate the datastore models to reflect the updated schema.

amplify codegen models

Screen Shot 2021-09-22 at 16.46.08.png

amplify add api

Once API has been added, run the command below to push all local changes to the cloud

amplify push

If you've pushed all your categories to the cloud, your amplify app home page should look like so. Take note of the added categories.

Screen Shot 2021-10-01 at 07.35.58.png

Now, we can start building our login and registration pages.

login.png

Here's a video on how login with google works. I apologize for the background noise.

1 October, 2021 - Loom Recording - Watch Video



This post is already getting too long, we'll pick it up in PART 2.
Thanks for checking this piece out. I appreciate it. If you loved it, please share.
Any feedback would be greatly appreciated.
If you enjoy building serverless apps, do check out my other blog posts. You most definitely learn a thing or two.
Till we meet again my internet comrade
Happy Coding ✌🏾