DEV Community

Cover image for Realtime Chat App - Flutter, Node.js & Socket.io
Ibtesam Ansari
Ibtesam Ansari

Posted on

Realtime Chat App - Flutter, Node.js & Socket.io

In this blog, we will see how to make a realtime chat app using Node.js as the backend and Flutter as the frontend. We will use sockets to communicate between devices.

Prerequisites
The following should be installed and running in your PC.

Nodejs (Server-Side)

Create a folder named real_chat_node and open it in the terminal. Then run the following command:

npm init

Accept the defaults by pressing Enter. Next install the required packages through npm, which is available by default when you install node.

npm install express nodemon http socket.io

Open the folder with your favourite IDE. Then go to package.json and in scripts add a dev key.

package.json

Next create a index.js file in your root directory. Write the following code in it.

const app = require('express')()
const http = require('http').createServer(app)
app.get('/', (req, res) => {
   res.send("Node Server is running. Yay!!")
})
http.listen(8080)
Enter fullscreen mode Exit fullscreen mode

On your terminal type the following command:
npm run dev

Leave it running and go to http://localhost:8080 and you will get the message.

Now lets add socket in our nodejs app.

const app = require('express')()
const http = require('http').createServer(app)


app.get('/', (req, res) => {
    res.send("Node Server is running. Yay!!")
})

//Socket Logic
const socketio = require('socket.io')(http)

socketio.on("connection", (userSocket) => {
    userSocket.on("send_message", (data) => {
        userSocket.broadcast.emit("receive_message", data)
    })
})

http.listen(process.env.PORT)
Enter fullscreen mode Exit fullscreen mode

The connection event is triggered whenever a socket is connected to our app. We then add a listener to the send_message event which forwards any data that is sent to it to receive_message event.

Voila!! Our backend is ready. Let’s deploy it to heroku and then we can start our Flutter app.

Quick Note: The following are the ways to send and listen on events.
send listen socket

Heroku (Deployment)

Heroku is a cloud platform which will deploy our app so that we can access it from anywhere though a url. Let’s get started.

Before we deploy our app, we just need to make few changes.

  1. In index.js file replace port 8080 with process.env.PORT.
    http.listen(process.env.PORT)

  2. Create a file named Procfile in the root directory and write the following in it.
    web: node index.js

  3. Also create a .gitignore file and add the folowing
    /node_modules

deploy

  1. Next open the root directory in your terminal and setup heroku cli
    heroku login
    You will be asked to login in. Enter your credentials and you are good to go.

  2. Now create your heroku app. heroku create <your-app-name-here>

  3. Now initialize git and commit everything to heroku master.

git init
git add .
git commit -m "Initial Commit"
git push heroku master
Enter fullscreen mode Exit fullscreen mode

Wait for it to finish and done. You can go to the url generated to see the same message as earlier.

Note: Use your own url that will be generated.

deploy done

Flutter (Client-Side)

So our backend part is complete and now its time to start making our chat app in Flutter.

Open your terminal and type in the following command to create our flutter app.

flutter create --androidx real_chat_flutter

After the project is created open the folder in your IDE.

In your pubspec.yaml file add the following dependency

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.2
  flutter_socket_io: ^0.6.0  //Add this dependency
Enter fullscreen mode Exit fullscreen mode

Open the main.dart in the lib folder and delete all the code and add the following code:

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

void main() => runApp(MyMaterial());

class MyMaterial extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: ChatPage(),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we have to create the ChatPage. Create ChatPage.dart file inside lib folder. Lets write the code for our Chat Page.

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_socket_io/flutter_socket_io.dart';
import 'package:flutter_socket_io/socket_io_manager.dart';

class ChatPage extends StatefulWidget {
  @override
  _ChatPageState createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {
  SocketIO socketIO;
  List<String> messages;
  double height, width;
  TextEditingController textController;
  ScrollController scrollController;

  @override
  void initState() {
    //Initializing the message list
    messages = List<String>();
    //Initializing the TextEditingController and ScrollController
    textController = TextEditingController();
    scrollController = ScrollController();
    //Creating the socket
    socketIO = SocketIOManager().createSocketIO(
      '<ENTER THE URL OF YOUR DEPLOYED APP>',
      '/',
    );
    //Call init before doing anything with socket
    socketIO.init();
    //Subscribe to an event to listen to
    socketIO.subscribe('receive_message', (jsonData) {
      //Convert the JSON data received into a Map
      Map<String, dynamic> data = json.decode(jsonData);
      this.setState(() => messages.add(data['message']));
      scrollController.animateTo(
        scrollController.position.maxScrollExtent,
        duration: Duration(milliseconds: 600),
        curve: Curves.ease,
      );
    });
    //Connect to the socket
    socketIO.connect();
    super.initState();
  }

  Widget buildSingleMessage(int index) {
    return Container(
      alignment: Alignment.centerLeft,
      child: Container(
        padding: const EdgeInsets.all(20.0),
        margin: const EdgeInsets.only(bottom: 20.0, left: 20.0),
        decoration: BoxDecoration(
          color: Colors.deepPurple,
          borderRadius: BorderRadius.circular(20.0),
        ),
        child: Text(
          messages[index],
          style: TextStyle(color: Colors.white, fontSize: 15.0),
        ),
      ),
    );
  }

  Widget buildMessageList() {
    return Container(
      height: height * 0.8,
      width: width,
      child: ListView.builder(
        controller: scrollController,
        itemCount: messages.length,
        itemBuilder: (BuildContext context, int index) {
          return buildSingleMessage(index);
        },
      ),
    );
  }

  Widget buildChatInput() {
    return Container(
      width: width * 0.7,
      padding: const EdgeInsets.all(2.0),
      margin: const EdgeInsets.only(left: 40.0),
      child: TextField(
        decoration: InputDecoration.collapsed(
          hintText: 'Send a message...',
        ),
        controller: textController,
      ),
    );
  }

  Widget buildSendButton() {
    return FloatingActionButton(
      backgroundColor: Colors.deepPurple,
      onPressed: () {
        //Check if the textfield has text or not
        if (textController.text.isNotEmpty) {
          //Send the message as JSON data to send_message event
          socketIO.sendMessage(
              'send_message', json.encode({'message': textController.text}));
          //Add the message to the list
          this.setState(() => messages.add(textController.text));
          textController.text = '';
          //Scrolldown the list to show the latest message
          scrollController.animateTo(
            scrollController.position.maxScrollExtent,
            duration: Duration(milliseconds: 600),
            curve: Curves.ease,
          );
        }
      },
      child: Icon(
        Icons.send,
        size: 30,
      ),
    );
  }

  Widget buildInputArea() {
    return Container(
      height: height * 0.1,
      width: width,
      child: Row(
        children: <Widget>[
          buildChatInput(),
          buildSendButton(),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    height = MediaQuery.of(context).size.height;
    width = MediaQuery.of(context).size.width;
    return Scaffold(
      body: SingleChildScrollView(
        child: Column(
          children: <Widget>[
            SizedBox(height: height * 0.1),
            buildMessageList(),
            buildInputArea(),
          ],
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Now run the app in two devices and chat back and forth πŸ˜„.

If you face any problem, you can check out my github repo :

Don’t forget to star ⭐ the repo and give claps πŸ‘ if you liked the article. If you have any queries you can ask in comments. Thank you πŸ˜„

Top comments (6)

Collapse
 
sreeanir profile image
Sree • Edited

It didnt work with my code ..It gave this error

[{"cause":{"cause":{"detailMessage":"Control frames must be final.","stackTrace":[],"suppressedExceptions":[]},"detailMessage":"websocket error","stackTrace":[],"suppressedExceptions":[]},"detailMessage":"Connection error","stackTrace":[],"suppressedExceptions":[]}]

Collapse
 
hsul4n profile image
Huthaifah Mustafa

Try to downgrade you're socket.io package using the below command

npm uninstall socket.io
npm install socket.io@2.4.1
Enter fullscreen mode Exit fullscreen mode
Collapse
 
adithyadesign profile image
adithya-design

I have the same error too.
Have you gotten a solution a solution for this yet

Collapse
 
sreeanir profile image
Sree

Can you check heroku log if the request is coming to server or not?

Collapse
 
sreeanir profile image
Sree • Edited

WIth this code,It didnt work.I changed to socket_io client .I was also getting control frames issues.

Collapse
 
nochphanith profile image
Phanith

I wanna a request from postman to send to flutter, can you provide some code for sending from Postman? I tried to do that but can't succeed because I'm new at NodeJS