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

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

ยท

5 min read

In PART 1 and PART 2 of this series, we covered how to configure Amplify with Flutter, create login/registration screens and update profile screen.
In this post, we'll look at how to let a user add and display all posts in the database in real time.

POST

As always, everything concerning the post entity is in the post branch on the Github page.

The next major component in our system after user login is creating and displaying a Post.
A post consist of a postText, postStatus, postImage, and the user who made the post.
For the postText, we'll use a TextFormField to grab user input, and then, we'll use this image_picker: ^0.8.2 library we already added to the pubspec.yaml file. createPost function

Future<bool> createPost(String userId) async{
    loading = true;
    /**
     * first retrieve user model
     */
    try {
      List<User> user = await Amplify.DataStore.query(
          User.classType, where: User.ID.eq(userId));
      print("user details" + user[0].toString());
      Post post = Post(content: postTextController.text.trim(),
          type: PostType.TEXT_IMAGE,
          status: PostStatus.CREATED,
          userID: userId,
          postImageUrl: postImageUrl,
          user: user[0],
          //user model
          createdOn: TemporalDateTime.now(),
          updatedOn: TemporalDateTime.now());
      await Amplify.DataStore.save(post);
      loading = false;
      return true;
    }catch(ex){
      print(ex.toString());
      loading = false;
      return false;
    }
  }

What the code above does is, firstly, it gets the details of the user(UserModel) who's about to make the post. Then, create a new post Object and adds all the corresponding Post fields.

Remember that there's a one-to-many relationship between a User and a Post.

So when saving a post item, we have to satisfy that relationship.

We do that by adding the userId and user[0] object to the Post object.
We call await Amplify.DataStore.save(post); to save the Post Object to the datastore, which would later synchronize this local data with our remote database. You don't have to worry about that.

Getting a List of All Posts

After saving a post, the next obvious step is to display all posts in our Posts database.
From the Official Amplify Docs

Predicates Predicates are filters that can be used to match items in the DataStore. When applied to a query(), they constrain the returned results. When applied to a save(), they act as a pre-requisite for updating the data. You can match against fields in your schema by using the following predicates:

Strings: eq | ne | le | lt | ge | gt | contains | notContains | beginsWith | between

Numbers: eq | ne | le | lt | ge | gt | between

Lists: contains | notContains

So We'll be using predicates to filter and grab the data we need

getAllPosts

 Future<List<Post>>queryAllPosts() async{
    List<Post> posts = await Amplify.DataStore.query(Post.classType,sortBy: [Post.CREATEDON.descending()]);
    return posts;
  }

We want to retrieve the latest posts first. So we sortBy [Post.CREATEDON.descending()]

Pagination is also a piece of cake. Here's a function to paginate through the posts

paginatePosts

 Future<List<Post>>paginatePosts(int page) async{
    List<Post> posts = await Amplify.DataStore.query(Post.classType,sortBy: [Post.CREATEDON.descending()], 
                 pagination: QueryPagination(page:page, limit:100));
    return posts;
  }

getSinglePost

 Future<List<Post>>getSinglePost(String postID) async{
    List<Post> posts = await Amplify.DataStore.query(Post.classType,where: Post.ID.eq(postID));
    return posts;
  }

updatePost

  Future<void> updatePost(String postId) async{
    Post oldPost = (await Amplify.DataStore.query(Post.classType,
        where: Post.ID.eq(postId)))[0];
    Post newPost = oldPost.copyWith(id: oldPost.id,
        createdOn: TemporalDateTime.now(),
        updatedOn: TemporalDateTime.now());

    await Amplify.DataStore.save(newPost);
  }

You can also get all posts for a particular user

queryAllUserPosts

  Future<List<Post>>queryAllUserPosts(String userId) async{
    List<Post> posts = await Amplify.DataStore.query(Post.classType,
        where: Post.USERID.eq(userId),
        sortBy: [Post.CREATEDON.descending()]);
    return posts;
  }

Now, how do we display a list of posts on our home page?
Like so

postRepo.queryAllPosts().then((value) {
        postRepo.posts = value;
        print("something posts "+value.toString());

      });

Firstly, we'll query all the posts and save them in a List, then we loop through the list and display each individual post.

ListView.builder(


            itemBuilder: (context,index){
              return PostItem(userId!,postRepo.posts[index]);
            },itemCount: postRepo.posts.length,)

Real Time Posts

GraphQL supports subscriptions, that allow us to retrieve data in realtime when a query or mutation occurs.
Amplify gives us the ability to subscribe to changes on our Models.
In the above scenario when we created a post, on the home screen, we had to hit refresh in order to view the newly created post in our listview.
With subscriptions, we no longer need a refresh. The post would automatically be retrieved and added to our listview.

How cool is that ? COOL

So how do we accomplish this ?

late StreamSubscription postStream;
      postStream = Amplify.DataStore.observe(Post.classType).listen((event) {

       if(event.eventType != EventType.create){
          return;
        }
        if(postRepo.posts[0].id != event.item.id){

          postRepo.posts.insert(0, event.item);
          print('Received event of type ' + event.eventType.toString());
          print('Received post ' + event.item.toString());

        }





      });

From the above code, we define a StreamSubscription and listen to all events happening in the POST model.
You can use event.eventType to know if the event was an onCreate, onUpdate or onDelete.
At this stage, we want to get updates for create events only.
I noticed that subscriptions get fired multiple times, which wasn't what I wanted. It added duplicates to my listview.
So I added this if statement if(postRepo.posts[0].id != event.item.id) to make sure not to add duplicates to the listview.
Please get the complete code here Friends_GitHUB and it out.
Here's also a video illustration.

14 October, 2021 - Create/List Post - Watch Video

In the next post, we'll be going through creating and getting a list of comments for a post in real-time.
So stay tuned ๐Ÿ˜‰ Thanks for checking this out.
I'll love to know what you think about this series or how you feel about the Amplify Framework.
Happy Coding โœŒ๐Ÿพ