Build a Flutter chat system in under 5 minutes

Build a Flutter chat system in under 5 minutes

May 19, 2023·

6 min read

Chat features are typically an essential element in many applications, enabling users to communicate with one another or with a company representative. This interaction often leads to increased satisfaction for the customer or user, as well as heightened engagement with the app, since humans are inherently social beings and generally appreciate contact with others.

However, if you're thinking about building a chat system for your app, you've probably already recognized the vast array of features you'll need to develop, from designing one-on-one chats and group chat architecture to user management, message sending and notifications, as well as considering privacy and security aspects, and so on.

For this reason, in this article, I provide you with a quick tutorial on how to add pre-built chat functionality to your application using ZEGOCLOUD, a company specializing in offering video and audio streaming services, as well as chat services in this case. I've been working with this company for a few months now, and I know for a fact that using their SDK, it's possible to bypass all the complexity I mentioned earlier and build your chat system quickly and easily.

With the ZEGOCLOUD SDK you can create a chat system quickly and easily.

How to add Chat functionality to your Flutter app

Let's start with the tutorial, first go to their official page and sign up. You can sign up and create a test project to test the service completely free of charge without entering any payment method. Once you have done it, click on "Create your project":

Create project

Then we will have to select which is the use case of our project, we will select "In-app Chat".

In-app Chat use case

Then we will simply have to give it a name, you can choose the one you want:

Set project name

Once the project has been created, click on it to see the configuration information. On this screen you will see two values: AppId and AppSign. You will need to use these values later to connect your application to the project you just created.

Now go to Service Management, In-app Chat and activate the functionality:

Perfect. We already have everything ready. Next, I'm going to create a base app from which you could start building your chat system.

Adding chat functionality to a Flutter app

Let's start by creating a new app for Android and iOS:

flutter create --platforms android,ios flutter_chat_app

For the chat functionality, we need to add the zego_zim dependency:

flutter pub add zego_zim

Next, you'll see a large block of code. If you run it, you'll be able to send messages from one device to another. You can find detailed explanations of each step in the form of comments:

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

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Chat App',
      home: ChatScreen(),
    );
  }
}

/// This class will help us to represent the information
/// of the messages within our app.
class Message {
  final String content;
  final String userId;
  final String userName;

  Message({
    required this.content,
    required this.userId,
    required this.userName,
  });
}

class ChatScreen extends StatefulWidget {
  const ChatScreen({super.key});

  @override
  State<ChatScreen> createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  // Make sure you protect these values correctly, ideally you should
  // get them from your secured backend after the user has logged in.
  static const _appId = your_app_id;
  static const _appSign =
      'your_app_sign';

  // For this example we are going to hardcode the ID of user A
  // and user B. It is important that in your chat system you use
  // your own identifiers, for example the ones you are already
  // using to identify users.
  static const _userAId = 'userA1234';
  static const _userBId = 'userB1234';

  // Set this variable to true on one device and false on
  // another to simulate different users.
  final bool _isUserA = true;

  late final ZIM _zim;
  late final ZIMAppConfig _appConfig;
  late final ZIMUserInfo _userInfo;
  late final Future<void> _future;

  final List<Message> _messages = [];

  final TextEditingController _controller = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  final ScrollController _scrollController = ScrollController();

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

    // Initialize the ZIMAppConfig with your appId and appSign
    _appConfig = ZIMAppConfig()
      ..appID = _appId
      ..appSign = _appSign;
    _zim = ZIM.create(_appConfig)!;

    final id = _isUserA ? _userAId : _userBId;

    _userInfo = ZIMUserInfo()
      ..userID = id
      ..userName = 'user_$id';

    // For now we will use a FutureBuilder that will log the user.
    _future = _initialize();
  }

  @override
  void dispose() {
    _controller.dispose();
    _scrollController.dispose();
    _zim.logout();
    _zim.destroy();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => FutureBuilder(
        future: _future,
        builder: (context, snapshot) {
          Widget bodyWidget;

          //In case of error we show a message on the screen
          if (snapshot.hasError) {
            bodyWidget = Center(
              child: Container(
                padding: const EdgeInsets.only(left: 24.0, right: 24.0),
                child: const Text(
                  'An error occurred while trying to initialize the chat.',
                  textAlign: TextAlign.center,
                ),
              ),
            );
          } else if (snapshot.connectionState == ConnectionState.done) {
            bodyWidget = Column(
              children: [
                // Show the list of message
                Expanded(
                  child: ListView(
                    controller: _scrollController,
                    children: [
                      for (final message in _messages)
                        Align(
                          alignment: message.userId == _userInfo.userID
                              ? Alignment.centerRight
                              : Alignment.centerLeft,
                          child: Container(
                            padding: const EdgeInsets.all(8.0),
                            child: _MessageEntry(
                              message: message,
                              isOwnMessage: message.userId == _userInfo.userID,
                            ),
                          ),
                        )
                    ],
                  ),
                ),
                // Send a message
                Container(
                  padding: const EdgeInsets.all(12.0),
                  color: Colors.grey[200],
                  child: Row(
                    children: [
                      Expanded(
                        child: TextFormField(
                          controller: _controller,
                          focusNode: _focusNode,
                          decoration: const InputDecoration(
                              hintText: 'Write a message here'),
                        ),
                      ),
                      ElevatedButton(
                        onPressed: () {
                          _focusNode.unfocus();
                          _sendMessage(_controller.text);
                          _controller.clear();
                        },
                        child: Row(
                          children: const [
                            Text('SEND'),
                            Padding(
                                padding: EdgeInsets.only(left: 4.0),
                                child: Icon(Icons.send)),
                          ],
                        ),
                      ),
                    ],
                  ),
                )
              ],
            );
          } else {
            bodyWidget = const Center(
              child: CircularProgressIndicator(),
            );
          }

          return Scaffold(
            appBar: AppBar(
              title: const Text('Flutter Chat App'),
            ),
            body: bodyWidget,
          );
        },
      );

  /// In this method we will login the user and we will also
  /// set up a listener to receive messages from the other user.
  Future<void> _initialize() async {
    await _zim.login(_userInfo);

    ZIMEventHandler.onReceivePeerMessage =
        (ZIM zim, List<ZIMMessage> messageList, String fromUserID) {
      for (ZIMMessage message in messageList) {
        switch (message.type) {
          case ZIMMessageType.text:
            message as ZIMTextMessage;
            _addMessage(
              Message(
                  content: message.message,
                  userId: message.senderUserID,
                  userName: 'user_${message.senderUserID}'),
            );
            break;
          case ZIMMessageType.command:
            message as ZIMCommandMessage;
            break;
          case ZIMMessageType.image:
            message as ZIMImageMessage;
            break;
          case ZIMMessageType.file:
            message as ZIMFileMessage;
            break;
          default:
        }
      }
    };
  }

  /// This method will send a message to the other user.
  Future<void> _sendMessage(String message) async {
    final scaffoldMessenger = ScaffoldMessenger.of(context);

    final textMessage = ZIMTextMessage(message: message);
    final sendConfig = ZIMMessageSendConfig()
      ..priority = ZIMMessagePriority.low;

    final pushConfig = ZIMPushConfig();
    pushConfig.title = "New message";
    pushConfig.content = message;

    sendConfig.pushConfig = pushConfig;

    try {
      await _zim.sendMessage(
        textMessage,
        _isUserA ? _userBId : _userAId,
        ZIMConversationType.peer,
        sendConfig,
      );
      _addMessage(Message(
        content: message,
        userId: _userInfo.userID,
        userName: _userInfo.userName,
      ));
    } on Exception catch (_) {
      scaffoldMessenger.showSnackBar(
        const SnackBar(
          content: Text(
            'An error occurred while trying to send a message',
          ),
        ),
      );
    }
  }

  void _addMessage(Message message) {
    setState(() {
      _messages.add(message);
      _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
    });
  }
}

/// This widget will be the visual representation of
/// each message within the message list.
class _MessageEntry extends StatelessWidget {
  final Message message;
  final bool isOwnMessage;

  const _MessageEntry({required this.message, required this.isOwnMessage});

  @override
  Widget build(BuildContext context) => SizedBox(
        width: 200.0,
        child: Container(
          padding: const EdgeInsets.all(8.0),
          decoration: BoxDecoration(
            borderRadius: const BorderRadius.all(Radius.circular(8.0)),
            color: isOwnMessage ? Colors.blue[800] : Colors.blue,
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                message.userName,
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 10.0,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const Divider(
                color: Colors.white54,
              ),
              Text(
                message.content,
                style: const TextStyle(color: Colors.white),
              ),
            ],
          ),
        ),
      );
}

And there you have it! This straightforward tutorial has demonstrated how to incorporate chat functionality into your Flutter application by utilizing the powerful ZEGOCLOUD SDK. I sincerely hope that you found this guide to be informative and beneficial, as it aims to provide you with a solid foundation for swiftly implementing a chat system within your app.

I wish you the best of luck in your future endeavors, and I look forward to sharing more valuable insights with you in the future. Until next time, happy coding!

Did you find this article valuable?

Support David Serrano by becoming a sponsor. Any amount is appreciated!