Realtime Chat App - Flutter, Node.js & Socket.io
Tags: flutter, dart, node, socketio
Published: 2020-08-28 17:14:40

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.
* Node.js : https://nodejs.org/en/download/
* Flutter : https://flutter.dev/docs/get-started/install
* Heroku CLI : https://devcenter.heroku.com/articles/heroku-cli
* git : https://git-scm.com/downloads


#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](https://miro.medium.com/max/463/1*7nzwOQP5pYeRYuhpY3m7hQ.png)

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)
```
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)
```
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](https://miro.medium.com/max/360/1*fNrtU4TpdWFwyXOxzPlFNQ.png)


#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](https://miro.medium.com/max/700/1*ssgP-pRF8Uq6bUB09MroJA.png)

4. 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.

5. Now create your heroku app. `heroku create <your-app-name-here>`

6. Now initialize git and commit everything to heroku master.
```
git init
git add .
git commit -m "Initial Commit"
git push heroku master
```

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](https://miro.medium.com/max/511/1*fnq9gxWN5dCw2hNW5ZOWZQ.png)


#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
```

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(),
    );
  }
}
```

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(),
          ],
        ),
      ),
    );
  }
}
```

Now run the app in two devices and chat back and forth 😄.

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

{% github ibtesam123/real_chat_node no-readme %}

{% github ibtesam123/real_chat_flutter no-readme %}

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 😄