Simple Multi User Chat App Example Using Node.js, WebSocket and Flutter

In this app example, we have made a communication server with node.js that helps to transfer messages from one user to another user.  It is a multiuser chat app, which means one user can send a message to another specific user. See the example below for more details.

const WebSocket = require('ws')
const express = require('express')
const moment = require('moment')
const app = express()
const port = 7878; //port for https

app.get('/', (req, res) => {
    res.send("Hello World");
});

app.listen(port, () => {
    console.log(`Example app listening at http://localhost:${port}`)
});
var  webSockets = {}

const wss = new WebSocket.Server({ port: 6060 }) //run websocket server with port 6060
wss.on('connection', function (ws, req)  {
    var userID = req.url.substr(1) //get userid from URL ip:6060/userid 
    webSockets[userID] = ws //add new user to the connection list

    console.log('User ' + userID + ' Connected ') 

    ws.on('message', message => { //if there is any message
        console.log(message);
        var datastring = message.toString();
        if(datastring.charAt(0) == "{"){
            datastring = datastring.replace(/\'/g, '"');
            var data = JSON.parse(datastring)
            if(data.auth == "chatapphdfgjd34534hjdfk"){
                if(data.cmd == 'send'){ 
                    var boardws = webSockets[data.userid] //check if there is reciever connection
                    if (boardws){
                        var cdata = "{'cmd':'" + data.cmd + "','userid':'"+data.userid+"', 'msgtext':'"+data.msgtext+"'}";
                        boardws.send(cdata); //send message to reciever
                        ws.send(data.cmd + ":success");
                    }else{
                        console.log("No reciever user found.");
                        ws.send(data.cmd + ":error");
                    }
                }else{
                    console.log("No send command");
                    ws.send(data.cmd + ":error");
                }
            }else{
                console.log("App Authincation error");
                ws.send(data.cmd + ":error");
            }
        }else{
            console.log("Non JSON type data");
            ws.send(data.cmd + ":error");
        }
    })

    ws.on('close', function () {
        var userID = req.url.substr(1)
        delete webSockets[userID] //on connection close, remove reciver from connection list
        console.log('User Disconnected: ' + userID)
    })
    
    ws.send('connected'); //innitial connection return message
})

Make sure you have installed modules such as express, ws and moment. Use the following command lines to install these modules.

npm install express  --save

npm install ws --save

npm install moment --save

Run the node.js script using the following command:

node app.js

Now on the flutter part, add  web_socket_channel flutter package to your dependency by adding the following line to your pubspec.yaml file.

dependencies:
  flutter:
    sdk: flutter
  web_socket_channel: ^2.0.0

Add Internet Permission by adding this line in android/app/src/main/AndroidManifest.xml before <application>

<uses-permission android:name="android.permission.INTERNET"/>

Here we are using ws, non-secure WebSocket protocol. Therefore, add the following lines to AndroidManifest.xml file as well.

<application
android:usesCleartextTraffic="true"

We have two files in the Flutter project:

  • main.dart
  • chat.dart

import 'package:chatapp/chat.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Chat App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ChatPage(),
    );
  }
}

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:web_socket_channel/io.dart';

class ChatPage extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
     return ChatPageState();
  }
}

class ChatPageState extends State<ChatPage>{
  
  IOWebSocketChannel channel; //channel varaible for websocket
  bool connected; // boolean value to track connection status

  String myid = "222"; //my id
  String recieverid = "111"; //reciever id
  // swap myid and recieverid value on another mobile to test send and recieve
  String auth = "chatapphdfgjd34534hjdfk"; //auth key

  List<MessageData> msglist = [];

  TextEditingController msgtext = TextEditingController();

  @override
  void initState() {
    connected = false;
    msgtext.text = "";
    channelconnect();
    super.initState();
  }

  channelconnect(){ //function to connect 
    try{
         channel = IOWebSocketChannel.connect("ws://192.168.0.109:6060/$myid"); //channel IP : Port
         channel.stream.listen((message) {
            print(message);
            setState(() {
                 if(message == "connected"){
                      connected = true;
                      setState(() { });
                      print("Connection establised.");
                 }else if(message == "send:success"){
                      print("Message send success");
                      setState(() {
                        msgtext.text = "";
                      });
                 }else if(message == "send:error"){
                     print("Message send error");
                 }else if (message.substring(0, 6) == "{'cmd'") {
                     print("Message data");
                     message = message.replaceAll(RegExp("'"), '"');
                     var jsondata = json.decode(message);

                       msglist.add(MessageData( //on message recieve, add data to model
                              msgtext: jsondata["msgtext"],
                              userid: jsondata["userid"],
                              isme: false,
                          )
                       );
                    setState(() { //update UI after adding data to message model
      
                    });
                 }
            });
          }, 
        onDone: () {
          //if WebSocket is disconnected
          print("Web socket is closed");
          setState(() {
                connected = false;
          });    
        },
        onError: (error) {
             print(error.toString());
        },);
    }catch (_){
      print("error on connecting to websocket.");
    }
  }

  Future<void> sendmsg(String sendmsg, String id) async {
         if(connected == true){
            String msg = "{'auth':'$auth','cmd':'send','userid':'$id', 'msgtext':'$sendmsg'}";
            setState(() {
               msgtext.text = "";
               msglist.add(MessageData(msgtext: sendmsg, userid: myid, isme: true));
            });
            channel.sink.add(msg); //send message to reciever channel
         }else{
            channelconnect();
            print("Websocket is not connected.");
         }
  }

  @override
  Widget build(BuildContext context) {
     return Scaffold(
        appBar: AppBar( 
          title: Text("My ID: $myid - Chat App Example"),
          leading: Icon(Icons.circle, color: connected?Colors.greenAccent:Colors.redAccent),
          //if app is connected to node.js then it will be gree, else red.
          titleSpacing: 0,
        ),
        body: Container( 
           child: Stack(children: [
               Positioned( 
                  top:0,bottom:70,left:0, right:0,
                  child:Container( 
                    padding: EdgeInsets.all(15),
                    child: SingleChildScrollView( 
                      child:Column(children: [

                            Container( 
                              child:Text("Your Messages", style: TextStyle(fontSize: 20)),
                            ),

                            Container( 
                              child: Column( 
                                children: msglist.map((onemsg){
                                  return Container( 
                                     margin: EdgeInsets.only( //if is my message, then it has margin 40 at left
                                             left: onemsg.isme?40:0,
                                             right: onemsg.isme?0:40, //else margin at right
                                          ),
                                     child: Card( 
                                        color: onemsg.isme?Colors.blue[100]:Colors.red[100],
                                        //if its my message then, blue background else red background
                                        child: Container( 
                                          width: double.infinity,
                                          padding: EdgeInsets.all(15),
                                          
                                          child: Column(
                                            crossAxisAlignment: CrossAxisAlignment.start,
                                            children: [

                                              Container(
                                                child:Text(onemsg.isme?"ID: ME":"ID: " + onemsg.userid)
                                              ),

                                              Container( 
                                                 margin: EdgeInsets.only(top:10,bottom:10),
                                                 child: Text("Message: " + onemsg.msgtext, style: TextStyle(fontSize: 17)),
                                              ),
                                                
                                            ],),
                                        )
                                     )
                                  );
                                }).toList(),
                              )
                            )
                       ],)
                    )
                  )
               ),

               Positioned(  //position text field at bottom of screen
               
                 bottom: 0, left:0, right:0,
                 child: Container( 
                      color: Colors.black12,
                      height: 70,
                      child: Row(children: [
                          
                          Expanded(
                            child: Container( 
                              margin: EdgeInsets.all(10),
                              child: TextField(
                                  controller: msgtext,
                                  decoration: InputDecoration( 
                                    hintText: "Enter your Message"
                                  ),
                              ),
                            )
                          ),

                          Container(
                            margin: EdgeInsets.all(10),
                            child: ElevatedButton( 
                              child:Icon(Icons.send),
                              onPressed: (){
                                if(msgtext.text != ""){
                                  sendmsg(msgtext.text, recieverid); //send message with webspcket
                                }else{
                                  print("Enter message");
                                }
                              },
                            )
                          )
                      ],)
                    ),
               )
           ],)
        )
     );
  }
}

class MessageData{ //message data model
    String msgtext, userid;
    bool isme;
    MessageData({this.msgtext, this.userid, this.isme});
     
}

Read the comments inside the code to understand better.

User with ID: 111 User with ID: 222

In this way, you can make an instant chat app with the multiuser, database-less features. Study the code, and understand the basic method to build a chat app using node.js and Flutter.

No any Comments on this Article


Please Wait...