Clubhouse UI with Flutter

Umit Can Kaya
10 min readAug 7, 2022

--

In this blog post, I will show how you can easily create a clone of Clubhouse that is a complicated application, and we shouldn’t expect to replicate it all. We will just replicate its’ UI.

This clone will allow you to:

  • sign in and log in with fake account
  • entering phone number and verification
  • scrolling and joining different rooms
  • looking room and profile detail()
  • starting a room and choose a type(open, social, private)
  • adding and inviting a user to our room

Create a Flutter Application

The first step on this journey is… to create a new Flutter application 🚀.

If you don’t know how to create a flutter app with IDE, please click and follow the guides.

Package Dependencies

Open the pubspec.yaml file and add the following dependencies:

dependencies:flutter:sdk: fluttercupertino_icons: ^1.0.2country_code_picker: ^2.0.2pull_to_refresh: ^2.0.0

⚠️ NOTE: There may be updated versions of these packages at the time you’re reading this article. If you want to follow along exactly, it will be safer to use the versions mentioned above. Once you are familiar with all of the code, you can run flutter pub outdated to see which packages require updating.

Get Started

Before starting, let’s quickly discuss our approach to building our Clubhouse UI clone.

  • Login Screens: Where you will see login page included(welcome page, username, and full name page, phone verification page, pick photo page)
  • Home Screen: Where you will see lobby page and followers page.
  • Lobby Screen: Where you’ll see all rooms and their information.
  • Followers Screen: Where you’ll see who is online and available to invite to a new room.
  • Room Screen: Where you’ll see room information and all the user in the room who is speakers and listeners.
  • Profile Screen: Where you’ll see profile information.

We will start with the welcome page and go to the next screens. After finishing the login modules, we will start home screens, and iterate on them as we add more complexity and functionality layer by layer. The end result will be a (near) identical Clubhouse.

Folder Structure

Each project is unique and will require its own folder structure. What is used in this article is by no means better than any other method. Because we don't add backend logic or some features like this. It’s simply a convenient way to organize the code of this particular application.

├── lib
| │── data
│ ├── models
| └── room.dart
| └── user.dart
| ├── data.dart
| │── global_widgets
| └── round_button.dart
| └── round_image.dart
| │── pages
│ ├── home
| └── widgets
| └── home_bar.dart
| └── profile_page.dart
| ├── home_page.dart
│ ├── lobby
| └── widgets
| └── follower_list.dart
| └── lobby_bottom_sheet.dart
| └── room_card.dart
| └── schedule_card.dart
| ├── follower_page.dart
| ├── lobby_page.dart
│ ├── room
| └── widgets
| └── room_profile.dart
| ├── room_page.dart
│ ├── welcome
| └── fullname_page.dart
| └── invitation_page.dart
| └── phone_otp.dart
| └── pick_photo_page.dart
| └── username_page.dart
| └── welcome_page.dart
| │── utils
| └── navigator.dart
| └── style.dart
| │── main.dart

By the end of this project, your file and folder layout should look like this. You can create all of this now. We will not use MVC, MVMM, or MVP design patterns that’s why it’s just a clone of Clubhouse UI. This project doesn’t have service, API, or back-end futures.

step.1 Theme of Clubhouse UI

You will notice we use the same fonts and color schemes you see in Clubhouse, giving your clone the look and feel of the real-world app.

Create utils/style.dart and add the following:

step.2 Creating Models and Generating Fake Data

Create data/models/user.dart and add the following:

This is our User Data Class. This class has included the user’s many fields.

Create data/models/room.dart and add the following:

This is our Room Data Class. This class has included the room’s many fields. We need to know room information when we scroll on the home page. And then we will use these models in different areas because it’s easier than writing in each situation.

Who do you want to see when you talk in the Clubhouse? As I think, political leaders are wondering. I always wonder what they talk about each other in meetings. That’s why I choose those leaders for fake data. Also, my profile will be the nicest person in the world, Borat. Let’s imagine how leaders react when Borat joins the rooms.

As you see above code snippets, I created a list whose name is names_ProfileImages.This list has included the leader’s name and profile images.

userData is a list that is generated automatically from other data.

List userData = List.generate(20, (index) =>{‘name’: names_profileImages[index][‘name’],‘username’: ‘@${names_profileImages[index][‘name’].toString().split(‘ ‘)[0].toLowerCase()}’,‘profileImage’: names_profileImages[index][‘profileImage’],‘followers’: random.nextInt(1000),‘following’: random.nextInt(20),‘lastAccessTime’: ‘${index + 10}m’,‘isNewUser’: random.nextBool(),} );

myProfileUserData is include Borat’s information. His information {name, username,imageProfile} isn’t generated that is written by me.

dynamic myProfileUserData = {'name': 'Borat','username': '@borat','profileImage': 'https://www.bagimsizsinema.com/wp-content/uploads/2020/09/Borat.jpg','followers': random.nextInt(1000),'following': random.nextInt(20),'lastAccessTime': '${random.nextInt(10) + 10}m','isNewUser': random.nextBool(),};

roomData is a list that is generated from other information.

List roomData = List.generate(10,(index) => {'title': headlines[index],"users": List.generate(20, (index) => User.fromJson(userData[index]))..shuffle(),'speakerCount': 4,},);

rooms is a list that is generated from roomData .

List<Room> rooms = List.generate(10,(index) => Room.fromJson(roomData[index]),);

users is a list that is generated from userData .Also, myProfile converted to object from JSON to using some scenario.

List<User> users = List.generate(20, (index) => User.fromJson(userData[index]));User myProfile = User.fromJson(myProfileUserData);

step.3 Welcome Pages All-in-One

Next, we will create ‘login screens’, a welcome page to start for new users. By the way, we were able to forget to start with main.dart page. As well, we know this part is most important to start Flutter App.

step.3.a Welcome Page

After that, create the file welcome/welcome_page.dart and add the following:

As a quick summary, this page consists of three widgets.

  • buildTitle() just shows the title.
  • buildContent() return SingleChildScroolView because this content might overflow from the screen for any reason.
  • buildBottom() have two types of global widgets and navigate to another screen.

We have used global widgets(round_button and round_image)and a navigation class on this page. We will create a one-time and use it in different areas in this project.

step.3.a.a Round Button and Round Images

Create the file global_widgets/round_button.dart and add the following:

All of the fields can be nullable but I want to set final to some fields and so I added initial value. Also, you can see the disabled color section that is part of the above codes. This part is so important cause we will use it in the following pages.

backgroundColor: MaterialStateProperty.resolveWith<Color>((states){if (states.contains(MaterialState.disabled)){return disabledColor!; }
return color ;}),

Create the file global_widgets/round_images.dart and add the following:

step.3.a.b NavigatorClass

Create the file utils/navigator.dart and add the following:

NavigatorClass has managed three states, each difference.

  • pushpage() is executed when users click the next button and switch the page.
  • pushPageUntil() is executed when the user triggered this future and starts switching pages.İmportant point is here (Route<dynamic> route) => false will make sure that all routes before the pushed route be removed.
  • pushPageReplacement() is executed switch the page. You can see differences from others.

If you don't know anything about navigating to each screen, please click and read this tutorial below :

step.3.b Phone OTP Page

Create the file welcome/phone_otp.dart and add the following:

As a quick summary, this page consists of three widgets and used Country Code Picker package. Before starting, you should check your pubspec.yaml and which should look like below :

  • buildTitle() just shows the title.
  • buildForm() is include country code picker and TextFormFields.TextFormField class is so useable and very easy to enter data input. Another important point about buildForm(), here is :
validator: (value) {if (value!.isEmpty) {setState(() {SignUpButtonClick = null;});} else {setState(() {SignUpButtonClick = signUp;});}

In the code snippet above, SignUpButton color changes depending on the value. When users don’t enter a value, it is running null.RoundButton doesn’t allow clicks from users and its color is disabledColor.Other scenarios run signUp .

  • buildBottom() includes roundButton and some helpers for navigation.

I wouldn’t like to add phone OTP services like Firebase auth,Twilio because I just write a clone of Clubhouse UI. You should create a verification page and write a back-end service if you want to do this.

step.3.c Invitation Page

Create the file welcome/invitation.dart and add the following:

As a quick summary, this page consists of three widgets standardly.

  • buildTitle() just shows the title.
  • buildContent() return SingleChildScrollView, include RoundImage and other widgets.
  • buildBottom() have two types of widgets and navigate to another screen. Even this text is wrapped by GestureDetector .

I wouldn’t add a real invitation system in-app. You have to know about dynamic links and build an advanced-level database schema for the invitation system.

step.3.d Full Name Page

Create the file welcome/fullname_page.dart and add the following:

As a quick summary, this page consists of three widgets standardly. By the way, we should separate each widget on the page for clean code. If we would that, our codes are readable and more understandable than others.

  • buildTitle() just shows the title.
  • buildContent() is include two TextFormField widgets. Users can enter their names and surnames.RoundButton’s default color is disabledColor.When users enter any value to TextFormField, roundButton’s color changes to normal color which is set whatever.
  • buildBottom() includes roundButton and some helpers for navigation.

step.3.e Username Page

Create the file welcome/username_page.dart and add the following:

As a quick summary, this page consists of three widgets standardly. All structures of the page seem like other welcome pages.

step.3.f Pick Photo Page

Create the file welcome/pick_photo_page.dart and add the following:

As a quick summary, this page consists of four widgets. One widget in appBar, and three widgets in body section of this page.

  • buildActionButton() executed when users click and switch to another page. This widget is positioned inside appBar.
  • buildTitle() just shows the title.
  • buildContent() isn’t executed when users click to pick a photo from their gallery because I wouldn’t add that feature right now. You can download it if you want to add this feature :

After downloading it, you should use local storage for your selected picture or write a back-end service and some basic CRUD operations. Also, you might use some state management approaches for a selected picture.

  • buildBottom() contains roundButton and some helpers for nothing. This button doesn’t work for any navigation activities. We used the buildActionButton() for this action.

step.4 Home Page

This section is a real part of Clubhouse UI. We might be a little bit excited to code. We use PageView() because we would like to display two pages on one screen. We can switch the Lobby Page and Follower Page with horizontal scrolling. And also, we can see the next planned rooms section and some icons including myProfilewhose is HomeBar() .

Create the file home/home_page.dart and home/widgets {folder} add the following:

To be continued.

--

--