Flutter: Create Chat Page UI (Messaging App)
In this blog, I am going to introduce you to a mix of both: we're going to build a chat app UI. Along with learning the awesome Chat UI implementation in Flutter, we will also learn how its coding workflows and structures work.
While this UI looks simple, there are a few things to consider:
- All text messages are shown inside a "chat bubble" with rounded corners and a fill color and user avatar
- Chat bubbles are left-aligned or right-aligned depending on who sent the message
- The text in each bubble should wrap if it doesn't fit in one line
So let's see how to build this!
How to Create a New Future Project
First, we need to create a new Flutter project. For that, make sure that you've installed the Flutter SDK and other Flutter app development-related requirements.
If everything is properly set up, then in order to create a project we can simply run the following command in our desired local directory:
flutter create flutter-chat-page-ui
After we've set up the project, we can navigate inside the project directory and execute the following command in the terminal to run the project in either an available emulator or an actual device:
flutter run
How to create the Main Chat Screen UI
Now, we are going to start building the UI for our chat application.
First, we need to make some simple configurations to the default boilerplate code in the main.dart file. We'll remove some default code and add the simple MaterialApp
pointing to the remove Container
and Call ChatPage page for now:
import 'package:chatpageui/pages/chatpage/chatPage.dart'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: ChatPage(), ); } }
How to Build the Main Chat Screen
Inside the ./lib directory of our root project folder, we need to create a folder called ./pages. This folder will hold all the dart files for different screens.
Inside ./lib/pages/ directory, we need to create a file called ChatPage.dart. Inside the ChatPage.dart file, we need to add the basic Stateless widget code as shown in the code snippet below:
import 'package:avatars/avatars.dart'; import 'package:flutter/material.dart'; class ChatPage extends StatefulWidget { const ChatPage({super.key}); @override State<ChatPage> createState() => _ChatPageState(); } class _ChatPageState extends State<ChatPage> { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.purple, body: SafeArea( child: Stack( children: [ Column( children: [ headerChat(), bodyChat(), ], ), inputChat() ], ) ), ); } Widget headerChat(){ return Container( padding: EdgeInsets.symmetric(horizontal: 25, vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon(Icons.arrow_back_ios, size: 25,color: Colors.white,), Text("Devhub", style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold, color: Colors.white),) ], ), Row( children: [ Container( padding: EdgeInsets.all(10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(50), color: Colors.black12 ), child: Icon(Icons.call, size: 25, color: Colors.white,), ), SizedBox(width: 20), Container( padding: EdgeInsets.all(10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(50), color: Colors.black12 ), child: Icon(Icons.videocam, size: 25, color: Colors.white,), ) ], ) ], ), ); } Widget bodyChat(){ return Expanded( child: Container( padding: EdgeInsets.only(left: 20, right: 20, top: 10), decoration: BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(40), topRight: Radius.circular(40), ), color: Colors.white, ), child: ListView( physics: BouncingScrollPhysics(), children: [ itemChat( avatar : 'DevHub', chat : 1, message : 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. ', time : '10:00', ), itemChat( avatar : 'DevHub', chat : 0, message : 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. ', time : '10:00', ), itemChat( avatar : 'DevHub', chat : 1, message : 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. ', time : '10:00', ), itemChat( avatar : 'DevHub', chat : 0, message : 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. ', time : '10:00', ), itemChat( avatar : 'DevHub', chat : 1, message : 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. ', time : '10:00', ), itemChat( avatar : 'DevHub', chat : 0, message : 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. ', time : '10:00', ), itemChat( avatar : 'DevHub', chat : 1, message : 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. ', time : '10:00', ), itemChat( avatar : 'DevHub', chat : 0, message : 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. ', time : '10:00', ), ], ), ), ); } Widget itemChat({required int chat, required String message, required String avatar, required String time}){ return Row( mainAxisAlignment: chat == 1 ? MainAxisAlignment.end: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ chat == 1 ? Avatar( useCache: true, name :avatar, shape: AvatarShape.circle(25) ): Text('$time', style: TextStyle(color: Colors.grey.shade400)), Flexible( child: Container( margin: EdgeInsets.only(left: 15, right: 15, top: 20), padding: EdgeInsets.all(20), decoration: BoxDecoration( color: chat==0 ? Colors.purple.shade100: Colors.purple.shade50, borderRadius: chat == 0 ? BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), bottomLeft: Radius.circular(30) ): BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), bottomRight: Radius.circular(30) ) ), child: Text('$message', style: TextStyle(color: Colors.black, fontSize: 16),), ) ), chat == 1 ? Text('$time', style: TextStyle(color: Colors.grey.shade400)): SizedBox() ], ); } Widget inputChat(){ return Positioned( // bottom: 0, child: Align( alignment: Alignment.bottomCenter, child: Container( color: Colors.white, height: 100, padding: EdgeInsets.symmetric(vertical: 5, horizontal: 20), child: TextField( decoration: InputDecoration( hintText: "Message...", filled: true, fillColor: Colors.purple.shade100, labelStyle: TextStyle(fontSize: 12), contentPadding: EdgeInsets.all(20), enabledBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.purple.shade100), borderRadius: BorderRadius.circular(25) ), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.purple.shade100), borderRadius: BorderRadius.circular(25) ), suffixIcon: Container( padding: EdgeInsets.all(15), decoration: BoxDecoration( borderRadius: BorderRadius.circular(50), color: Colors.purple ), child: Icon(Icons.send_rounded, color: Colors.white, size: 25,), ) ), ), ), ) ); } }
Output: