​​Build a Whatsapp UI Clone Application with Flutter


One of the many fascinating things about using flutter is that everything is a widget in Flutter. So in today’s tutorial, we will be leveraging this to create a beautiful Whatsapp UI Clone application.

Screenshots

These are screenshots of how our application will look like.

Getting Started

We are going to start by creating a new android studio project. If you have properly setup flutter on your machine, you will find an option to create a new Flutter project. When you open Android Studio, just like the photo below.

Next, select the Flutter Application below.

After that configure your application with a name and also set your applications package name. Follow the necessary prompts and click on Finish when done. This will be followed by android studio generating the necessary files needed for our application. Once this is complete. We are going to create some files and and folders for our project structure. See the image below.

The project structure simply means that we are going to create two packages under our “lib” folder and name them “data”and “pages”. The “data” folder will house our models and dummy data for the app and “pages” folder will contain each of the screens of our Whatsapp UI Clone. This was we can carefully separate or User Interface from the Business Logic.
Under the data folder create two files. 

  • call_item_model.dart
  • chat_item_model.dart

And under the pages folder create five files namely:

  • call_page.dart
  • camera_page.dart
  • chat_page.dart
  • status_page.dart
  • story_view_page.dart

And Under the main lib folder, we are going to create another file and call it homepage.dart.
This is basically because we are going to have just one homepage which contains the rest of the screen and we will cycle through each of the screens using the tab bar widget in a flutter.
All our files have been created. Let’s head over to our pubspec.yaml file and add the following packages that we are going to be making use of to crate our Whatsapp UI Clone.

camera: ^0.5.8+5
story_view: ^0.12.3

add the packages above and pubspec file should look like this.

For story_view, you can find the latest versions here and here for the camera package. If you need more information on how to use these packages, you can check out their docs here and here.
Let’s start coding our main.dart file which is the entry point for our application. Replace all the code in this file with the following. 

import 'dart:async';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'homepage.dart';

List<CameraDescription> cameras;

Future<Null> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  cameras = await availableCameras();
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "WhatsApp",
      theme: new ThemeData(
        primaryColor: new Color(0xff075E54),
        accentColor: new Color(0xff25D366),
      ),
      debugShowCheckedModeBanner: false,
      home: new HomePage(cameras:cameras),
    );
  }
}

What we just did is create a MateriallApp Widget and set the color of the app using ThemeData property which has primaryColor and accentColor fields. and set the home property to another widget which we are going to call HomePage and it takes in a named parameter camera, which is an array, whose type is CameraDescription.
N/B: Make sure to correct the imports after you are done. Next part is our Homepage file. Add the following code to it.


import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:whatsapp_ui/pages/call_page.dart';
import 'package:whatsapp_ui/pages/camera_page.dart';
import 'package:whatsapp_ui/pages/chat_page.dart';
import 'package:whatsapp_ui/pages/status_page.dart';

class HomePage extends StatefulWidget {
   final List<CameraDescription> cameras;
   HomePage({this.cameras});

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {

//Creates a tabcontroller variable which will be used to hold reference to and cycle //through the our different screens.
  TabController _tabController;
//this will be used to detect when to show our FAB
  bool showFab = true;
  bool isCallsPage = false;

  @override
  void initState() {
    super.initState();

    _tabController = TabController(vsync: this, initialIndex: 1, length: 4);
    _tabController.addListener(() {
      if (_tabController.index == 1) {
        showFab = true;
        isCallsPage = false;
      } else if(_tabController.index==3){
        showFab = true;
        isCallsPage = true;
      }else {
        showFab = false;
        isCallsPage = false;
      }
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("WhatsApp"),
        elevation: 0.7,
        bottom: TabBar(
          controller: _tabController,
          indicatorColor: Colors.white,
          tabs: <Widget>[
            Tab(icon: Icon(Icons.camera_alt)),
            Tab(text: "CHATS"),
            Tab(
              text: "STATUS",
            ),
            Tab(
              text: "CALLS",
            ),
          ],
        ),
        actions: <Widget>[
          Icon(Icons.search),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 5.0),
          ),
          Icon(Icons.more_vert)
        ],
      ),
      body: TabBarView(
        controller: _tabController,
        children: <Widget>[
          CameraPage(widget.cameras),
          ChatPage(),
          StatusPage(),
          CallsPage(),
        ],
      ),
      floatingActionButton: showFab
          ? FloatingActionButton(
        backgroundColor: Theme.of(context).accentColor,
        child: isCallsPage? Icon(
          Icons.add_call,
          color: Colors.white,
        ):Icon(
          Icons.message,
          color: Colors.white,
        ),
        onPressed: () => print("fab clicked"),
      )
          : null,
    );
  }
}

Our Application has 4 screens which we will be cycling through using the tabBar. The first point to note is the 

initState: this function is the default function that is called whenever a state is created for the first time in our application. All the logic housed in this method is called the first time this screen is called. So here in our first function, we initialize the tab controller, and also set the showfab value to three if we are on the second and last screen of our app. This is wrapped in a setState method, simply because we want our state to be rebuilt every time the value of the Boolean changes.

build: Also, in our build method we have are returning a Scaffold widget and it has three main parameters which we are making use of namely: appBar, bottom, and floatingActionButton.The bottom field carries the tabBar and the rest of the logic inside it is easy to understand. the floatingActionButton displays an icon by using a ternary condition check if isCallpage is true or false.
We continue with the rest of the screen, go to the data folder, and in our call_item_page.dart add the following lines of code.


import 'package:flutter/material.dart';
class CallItemModel {
  final String name;
  final String time;
  final String avatarUrl;
  final Color colorItem;
  CallItemModel({this.name, this.time, this.avatarUrl, this.colorItem});
}

List<CallItemModel> callItemData = [
  new CallItemModel(
      name: "Mike Kemi",
      time: "September 12, 15:30 AM",
      avatarUrl:
      "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg",
      colorItem: Colors.red),
  new CallItemModel(
      name: "Mike Mike",
      time: "September 12, 15:30 AM",
      avatarUrl:
      "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg",
      colorItem: Colors.green),
  new CallItemModel(
      name: "Mike Kemi",
      time: "September 12, 15:30 AM",
      avatarUrl:
      "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg",
      colorItem: Colors.red),
  new CallItemModel(
      name: "Deven",
      time: "September 12, 15:30 AM",
      avatarUrl:
      "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg",
      colorItem: Colors.green),
  new CallItemModel(
      name: "Flutter",
      time: "September 12, 15:30 AM",
      avatarUrl:
      "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg",
      colorItem: Colors.red),
  new CallItemModel(
      name: "Mark Zuckerberg",
      time: "September 12, 15:30 AM",
      avatarUrl:
      "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg",
      colorItem: Colors.green),
];

What we just did is create a model which has a name, timestamp, avatarUrl and color item, and after that we created a dummy list with our CallItemModel as type and added some data, to help us get a feel of what our UI is going to feel like.

Go to chat_item_model.dart and also add the following to it.

class ChatItemModel {
  final String name;
  final String message;
  final String time;
  final String avatarUrl;

  ChatItemModel({this.name, this.message, this.time, this.avatarUrl});
}

List<ChatItemModel> chatItemData = [
  new ChatItemModel(
      name: "Mike Kemi",
      message: "Welcome to Codesource.io",
      time: "15:30",
      avatarUrl:
      "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg"),
  new ChatItemModel(
      name: "Mike Mike",
      message: "Welcome to Codesource.io",
      time: "17:30",
      avatarUrl:
      "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg"),
  new ChatItemModel(
      name: "Mike Kemi",
      message: "Hello from Codesource.io",
      time: "5:00",
      avatarUrl:
      "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg"),
  new ChatItemModel(
      name: "Deven",
      message: "Hi there",
      time: "10:30",
      avatarUrl:
      "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg"),
  new ChatItemModel(
      name: "Flutter",
      message: "Hey there",
      time: "12:30",
      avatarUrl:
      "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg"),
  new ChatItemModel(
      name: "Mark Zuckerberg",
      message: "I am the CEO of Facebook",
      time: "15:30",
      avatarUrl:
      "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg"),
];

What we did here is similar to our call item model, the only difference being that we would be using this for our chat page instead of the calls page.
Now for our camera page add the following code snippet to it.

import 'package:flutter/material.dart';
import 'package:camera/camera.dart';

class CameraPage extends StatefulWidget {
  final List<CameraDescription> cameras;

  CameraPage(this.cameras);

  @override
  CameraPageState createState() {
    return new CameraPageState();
  }
}

class CameraPageState extends State<CameraPage> {
  CameraController controller;

  @override
  void initState() {
    super.initState();
    controller =
    new CameraController(widget.cameras[0], ResolutionPreset.medium);
    controller.initialize().then((_) {
      if (!mounted) {
        return;
      }
      setState(() {});
    });
  }

  @override
  void dispose() {
    controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return new Container();
    }
    return new AspectRatio(
      aspectRatio: controller.value.aspectRatio,
      child: new CameraPreview(controller),
    );
  }
}

What is going on here is quite simple and to get more information, on how to use the camera plugin just refer to the documentation link here
Go to the ChatPage and the code below.

import 'package:flutter/material.dart';
import 'package:whatsapp_ui/data/chat_item_model.dart';

class ChatPage extends StatefulWidget {

  @override
  ChatPageState createState() {
    return new ChatPageState();
  }
}

class ChatPageState extends State<ChatPage> {
  @override
  Widget build(BuildContext context) {
    return new ListView.builder(
      itemCount: chatItemData.length,
      itemBuilder: (context, i) => new Column(
        children: <Widget>[
          new Divider(
            height: 10.0,
          ),
          new ListTile(
            leading: new CircleAvatar(
              foregroundColor: Theme.of(context).primaryColor,
              backgroundColor: Colors.grey,
              backgroundImage: new NetworkImage(chatItemData[i].avatarUrl),
            ),
            title: new Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                new Text(
                  chatItemData[i].name,
                  style: new TextStyle(fontWeight: FontWeight.bold),
                ),
                new Text(
                  chatItemData[i].time,
                  style: new TextStyle(color: Colors.grey, fontSize: 14.0),
                ),
              ],
            ),
            subtitle: new Container(
              padding: const EdgeInsets.only(top: 5.0),
              child: new Text(
                chatItemData[i].message,
                style: new TextStyle(color: Colors.grey, fontSize: 15.0),
              ),
            ),
          )
        ],
      ),
    );
  }
}

The build function of the ChatPage contains a listview which has a listTile as its descendant widget. This listview makes use ofo the chatItemData list from our chat_item_model class. For the image we wouldlbe using a circleAvatar widget with the leeading property of the ListTile, while our title property has a row to house our name and timestampt from the array. The subtitle has the message property, and that is all for our ChatPage.
Go to our status_view_page.dart file and add the following lines of code to it. 

import 'package:flutter/material.dart';
import 'package:story_view/story_view.dart';

class StoryPageView extends StatelessWidget {
  final _storyController = StoryController();
  @override
  Widget build(BuildContext context) {
    final controller = StoryController();
    final List<StoryItem> storyItems = [
      StoryItem.text(title: '''By Failing to Plan you are planning to fail''', backgroundColor: Colors.red),
      StoryItem.pageImage(
          url:
          "https://i.ibb.co/54Vqkz8/IMG-20190714-123654.jpg",
          controller: _storyController),
      StoryItem.pageImage(
          url:
          "https://media.wired.com/photos/5e82883c6e4b250008c02ecc/1:1/w_572,h_572,c_limit/gear-emails-914669386.jpg",
          controller: _storyController,
          imageFit: BoxFit.contain),
    ];
    return Material(
      child: StoryView(
        storyItems: storyItems,
        controller: controller,
        inline: false,
        repeat: true,
      ),
    );
  }
}

This view makes use of the story_view package, and the class has just two variables which are a story controller and a list of storyItems.The build method simply returns a storyview widget and that’s about it for our story_view_page.
Lets move to your status_page.dart and add the following lines of code.

import 'package:flutter/material.dart';
import 'story_view_page.dart';

class StatusPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Color(0xfff2f2f2),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Card(
            color: Colors.white,
            elevation: 0.0,
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: ListTile(
                leading: Stack(
                  children: <Widget>[
                    CircleAvatar(
                      radius: 30,
                      backgroundImage: NetworkImage(
                          'https://www.mockrabbit.com/wp-content/uploads/2018/12/Become-a-Full-Stack-Developers.jpg'),
                    ),
                    Positioned(
                      bottom: 0.0,
                      right: 1.0,
                      child: Container(
                        height: 20,
                        width: 20,
                        child: Icon(
                          Icons.add,
                          color: Colors.white,
                          size: 15,
                        ),
                        decoration: BoxDecoration(
                          color: Colors.green,
                          shape: BoxShape.circle,
                        ),
                      ),
                    )
                  ],
                ),
                title: Text(
                  "My Status",
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
                subtitle: Text("Tap to add status update"),
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Text(
              "Viewed updates",
              style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold),
            ),
          ),
          Expanded(
            child: Container(
              padding: const EdgeInsets.all(8.0),
              color: Colors.white,
              child: ListView(
                children: <Widget>[
                  ListTile(
                    leading: CircleAvatar(
                      radius: 30,
                      backgroundImage: NetworkImage(
                          "https://www.gradjobs.co.uk/documents/blog/motivationjpeg-2245.jpeg"),
                    ),
                    title: Text(
                      "Mike Eshiet",
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    subtitle: Text("Today, 09:20 AM"),
                    onTap: () => Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: (context) => StoryPageView())),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Once we are done with this page,  we can move to our calls_page.dart and also add the following lines of code.

import 'package:flutter/material.dart';
import 'package:whatsapp_ui/data/call_item_model.dart';
import 'package:whatsapp_ui/data/chat_item_model.dart';

class CallsPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new ListView.builder(
      itemCount: callItemData.length,
      itemBuilder: (context, i) => new Column(
        children: <Widget>[
          new Divider(
            height: 10.0,
          ),
          new ListTile(
            leading: new CircleAvatar(
              foregroundColor: Theme.of(context).primaryColor,
              backgroundColor: Colors.grey,
              backgroundImage:  NetworkImage(callItemData[i].avatarUrl),
            ),
            title: Text(
                  callItemData[i].name,
                  style:  TextStyle(fontWeight: FontWeight.bold),
            ),
            subtitle:  Container(
              padding: const EdgeInsets.only(top: 5.0),
              child: Row(
                children: [
                Icon(Icons.call_received, color: callItemData[i].colorItem,),
                SizedBox(width: 10.0,),
                Text(
                    callItemData[i].time,
                  style: new TextStyle(color: Colors.grey, fontSize: 15.0),
                  ),
                ],
              )
            ),
            trailing: Icon(Icons.call, color: Theme.of(context).primaryColor,),
          )
        ],
      ),
    );
  }
}

The build function of this page has a listview that works similar to our ChatPage. The difference here is that we use the list in the call_item_model and add other fields to our UI. 

The Full source code of this tutorial can be found here
If you found this post on creating a Whatsapp UI Clone with Flutter informative, or have a question do well to drop a comment below, and don’t forget to share it with your friends.


Share on social media

//