ساخت یک برنامه چت Realtime با استفاده از اندروید، Node.js و Socket.io

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 08 مرداد 1397
دسته بندی ها : اندروید , نود جی اس

معرفی

WebSocketها ابزار زیبایی هستند که ما را قادر می‌سازند تا یک ارتباط Realtime در وب‌اپلیکیشن‌های مدرن برقرار کنیم. در واقع،‌ این مکانیزم بسیار قدرتمند بوده، و برای ساخت انواع مختلف برنامه مانند چت Realtime یا سیستم اعلانات و... استفاده می‌شود.

در این مقاله، به شما نشان خواهیم داد که چگونه می‌توانید یک برنامه چت Realtime با استفاده از اندروید، Node.js و Socket.io بسازید.

شروع کار

برنامه چت ما، به ۲ بخش تقسیم می‌شود:

‍۱- سمت سرور: یک سرور Node.js با پیاده‌سازی Socket.io برای آن سرور.

۲- سمت کاربر: ساخت برنامه اندروید و پیاده‌سازی Socket.io برای کاربر.

سرور Node.js ما

برای ساده‌سازی، معماری پروژه ما از دو بخش تشکیل می‌شود:

فایل package.json که Dependencyهای برنامه Node.js را مدیریت می‌کند، و فایل index.js که سرور اصلی ما خواهد بود.

پس از ساخت این دو فایل، خط دستوری را در شاخه پروژه خود باز می‌کنیم و این دستور را اجرا می‌کنیم:

npm install --save  express socket.io  

حال فایل index.js ما ساخته می‌شود. تمام پیکربندی‌ها را ویرایش کنید، تا به این صورت باشند:

const express = require('express'),
http = require('http'),
app = express(),
server = http.createServer(app),
io = require('socket.io').listen(server);
app.get('/', (req, res) => {

res.send('Chat Server is running on port 3000')
});


server.listen(3000,()=>{

console.log('Node app is running on port 3000')

});

برای اطمینان از اجرای سرور، به خط دستوری در شاخه پروژه بروید و این دستور را اجرا کنید:

node index.js

نکته: با استفاده از دستور Node، می‌توانیم هر سروری که با محیط Node ساخته می‌شود را بسازیم، ولی مشکل در اینجاست که هر زمان که می‌خواهیم فایل index.js را بروزرسانی کنیم، باید دستور تکراری را اجرا کنیم. پس برای ساده‌تر کردن این کار، می‌توانیم از دستور nodemon استفاده کنیم، که هر زمان که تغییری اعمال می‌کنیم، همه چیز به طور خودکار ری‌استارت می‌شود.

پس nodemon را در خط دستوری خود نصب کنید و این دستور را اجرا کنید:

npm install -g nodemon

برای اطمینان از این که پروژه ما در حال اجراست، باید این log را در کنسول خود ببینید:

حال به بهترین بخش می‌رسیم!

 در اینجا، تلاش خواهیم کردتا چند متد Socket.io را بر روی سرور خود پیاده‌سازی کنیم، تا تمام رویدادهای برنامه چت ما، شامل وضعیت ارتباط و پیام‌ها را مدیریت کند.

در فایل inces.js، ابتدا بررسی وضعیت ارتباط کاربر به سرور خود را پیاده‌سازی می‌کنیم:

io.on('connection', (socket) => {

console.log('user connected')

socket.on('join', function(userNickname) {

        console.log(userNickname +" : has joined the chat "  )

        socket.broadcast.emit('userjoinedthechat',userNickname +" : has joined the chat ")
    });

});

در واقع، مکانیزم Socket.io بر پایه رویدادها بنا شده است. در این پیاده‌سازی که در ابتدا انجام دادیم و دو پارامتر دارد، یک listener بر روی رویدادی به نام connection تعریف می‌کند و این رویداد از سمت کاربر اجرا می‌شود تا Node.js بتواند به آن رسیدگی کند. پس از آن، یک متد تعریف کردیم، که منتظر رویداد join می‌ماند و نام کاربری که به چت موجود در کنسول ملحق شده است را نمایش می‌دهد.

حال، وقتی که Node.js ورود یک کاربر را تشخیص داده است، رویدادی در سمت کاربر به نام userjoinedthechat را فعال می‌کند. دقت کنید که socket.broadcast.emit این رویداد را به تمام کاربران متصل به سرور، به جز ارسال کننده ارسال می‌کند.

اگر می‌خواهیم پیامی را به تمام کاربران، شامل ارسال کننده نیز ارسال کنیم، باید از io.emit() به جای socket.emit() استفاده کنیم.

حال برای مدیریت پیام‌ها، این چند خط را اضافه می‌کنیم. این آرگومان‌های جدید که اضافه کرده‌ایم، نام مستعار کاربر و محتویات پیام هستند. این اطلاعات، از سمت کاربر و با فعال کردن رویداد messagedetection ارسال می‌شوند.

socket.on('messagedetection', (senderNickname,messageContent) => {

       //لاگ کردن پیام‌ها در کنسول 
       console.log(senderNickname+" :" +messageContent)

       //ساخت آبجکت پیام
       let  message = {"message":messageContent, "senderNickname":senderNickname}

       // ارسال پیام‌ها به سمت کاربر  
       socket.emit('message', message )
});

و در آخر وقتی که یک کاربر از سمت کاربر خارج می‌شود، رویداد مربوطه، با پیاده‌سازی کد زیر مدیریت می‌شود:

 socket.on('disconnect', function() {
    console.log( 'user has left ')
    socket.broadcast.emit( "userdisconnect" ,' user has left')
});

حال که سرور ما آماده است، فایل index.js باید به این صورت باشد:

const express = require('express'),

http = require('http'),

app = express(),

server = http.createServer(app),

io = require('socket.io').listen(server);

app.get('/', (req, res) => {

res.send('Chat Server is running on port 3000')

});

io.on('connection', (socket) => {

console.log('user connected')

socket.on('join', function(userNickname) {

        console.log(userNickname +" : has joined the chat "  );

        socket.broadcast.emit('userjoinedthechat',userNickname +" : has joined the chat ");

    })

socket.on('messagedetection', (senderNickname,messageContent) => {

       //لاگ کردن پیام‌ها در کنسول 

       console.log(senderNickname+" : " +messageContent)

      //ساخت آبجکت پیام 

      let  message = {"message":messageContent, "senderNickname":senderNickname}

       // ارسال پیام‌ها به تمام کاربران، شامل ارسال کننده 

      io.emit('message', message )

      })

socket.on('disconnect', function() {

        console.log(userNickname +' has left ')

        socket.broadcast.emit( "userdisconnect" ,' user has left')

    })

})

server.listen(3000,()=>{

console.log('Node app is running on port 3000')

})

برنامه اندروید ما

برای شروع، اندروید استودیو را باز کنید، یک پروژه جدید با یک activity جدید باز کنید، فایل bild.gradle را باز کنید،  این Dependencyها را اضافه کرده، و پروژه خود را همگام سازی کنید:

compile 'com.android.support:recyclerview-v7:25.3.1'
compile('com.github.nkzawa:socket.io-client:0.5.0') {
    exclude group: 'org.json', module: 'json'
}

حال درباره این چند خط کد:

خط اول، Recycler view است که برای نمایش لیست پیام‌های خود استفاده می‌کنیم. خط دوم، کتابخانه‌ای است که پیاده‌سازی Socket.io برای سمت کاربر را فراهم می‌کند تا بتوانیم رویدادها را اجرا کنیم.

فراموش نکنید که در فایل manifest.xml، مجوز Internet را فعال کنید:


 

در فایل activity_main.xml یک EditText برای کاربر اضافه می‌کنیم، تا نام مستعار خود را وارد کند، و همچنین دکمه‌ای قرار می‌دهیم تا بتواند وارد چت‌باکس شود:




   

 

پیشنمایش ما به این صورت خواهد بود:

حال فایل MainActivity.java شما باید به این صورت باشد:

import android.content.Intent;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

public class MainActivity extends AppCompatActivity {

    private Button btn;

    private EditText nickname;

    public static final String NICKNAME = "usernickname";

    @Overrideprotected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        //فراخوانی کامپوننت‌های رابط کاربری با استفاده از آیدی

        btn = (Button)findViewById(R.id.enterchat) ;

        nickname = (EditText) findViewById(R.id.nickname);

        btn.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                //اگر نام مستعار خالی نیست، به اکتیویتی مورد نظر برو و آن نام را اضافه کن

    if(!nickname.getText().toString().isEmpty()){

              Intent i  = new Intent(MainActivity.this,ChatBoxActivity.class);

                     //دریافت نام مستعار و اضافه کردن آن

                     i.putExtra(NICKNAME,nickname.getText().toString());

                     startActivity(i);

                 }

            }

        });

    }

}

حال یک activity دیگر به نام ChatBoxActivity در فایل activity_chat_box.xml بسازید و این کد را به آن اضافه کنید:








            

            

    

پیشنمایش ما به این صورت خواهد بود:

حال قبل از پیاده‌سازی socket کاربر، بهتر است یک adapter برای مدیریت نمایش پیام‌ها بسازیم. برای این کار، باید فایلی به نام item.xml و یک کلاس جاوا به نام messages بسازیم، که دو ویژگی ساده دارد. (nickname و message)

در شاخه پروژه خود، و در کنار activityها، فایلی به نام Message.java بسازید و این کد را در آن قرار دهید:

public class Message {

    private String nickname; 
    private String message ;

    public  Message(){

    }
    public Message(String nickname, String message) {
        this.nickname = nickname;
        this.message = message;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

حال فایلی به نام item.xml زیر شاخه layout بسازید و این کد را به آن اضافه کنید:


فایلی به نام ChatBoxAdapter.java بسازید و این کد را در آن قرار دهید:

package com.example.aymen.androidchat;

import android.support.annotation.NonNull;

import android.support.v7.app.AppCompatActivity;

import android.support.v7.widget.RecyclerView;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewGroup;

import android.widget.ImageView;

import android.widget.TextView;

import java.util.List;

public class ChatBoxAdapter  extends RecyclerView.Adapter {

    private List MessageList;

    public  class MyViewHolder extends RecyclerView.ViewHolder {

        public TextView nickname;

        public TextView message;

        public MyViewHolder(View view) {

            super(view);

            nickname = (TextView) view.findViewById(R.id.nickname);

            message = (TextView) view.findViewById(R.id.message);

        }

    }

public ChatBoxAdapter(ListMessagesList) {

        this.MessageList = MessagesList;

    }

    @Overridepublic int getItemCount() {

        return MessageList.size();

    }

    @Overridepublic ChatBoxAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View itemView = LayoutInflater.from(parent.getContext())

                .inflate(R.layout.item, parent, false);

        return new ChatBoxAdapter.MyViewHolder(itemView);

    }

    @Overridepublic void onBindViewHolder(final ChatBoxAdapter.MyViewHolder holder, final int position) {

        Message m = MessageList.get(position);

        holder.nickname.setText(m.getNickname());

        holder.message.setText(m.getMessage() );

    }

}

حال که همه چیز را راه‌اندازی کردیم، می‌توانیم socket کاربر را در فایل ChatBoxActivity.java پیاده‌سازی کنیم. پس به این صورت پیش خواهیم رفت:

۱. نام مستعار کاربر را می‌گیریم.

۲. تمام متدهای مربوط به Recycler view شامل adapter را فراخوانی کرده، و پیاده‌سازی می‌کنیم.

۳. میزبان را برای socket کاربر تعریف می‌کنیم، تا با سرور ارتباط برقرار کنیم.

۴. تمام رویدادهای اجرا شده از سرور را مدیریت می‌کنیم.

۵. وقتی که کاربر متصل می‌شود، قطع می‌شود یا پیامی ارسال می‌کند، رویداد مربوطه را اجرا می‌کنیم.

اما قبل از آن، بیایید بررسی کنیم که همه چیز صحیح است یا نه. در ChatBoxActivity، آبجکت socket را تعریف می‌کنیم و ارتباط socket را در متد onCreate اضافه می‌کنیم، تا وقتی که این activity فراخوانی می‌شود، socket کاربر مستقیما رویداد مربوطه را اجرا می‌کند:

public class ChatBoxActivity extends AppCompatActivity {

    //تعریف آبجکت سوکت

private Socket socket;

private String Nickname ;

@Overrideprotected 

void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_chat_box);

// دریافت نام مستعار کاربر

  Nickname= (String)getIntent().getExtras().getString(MainActivity.NICKNAME);

//اتصال سوکت به سرور

try {

 socket = IO.socket("http://yourlocalIPaddress:3000");

 //برقراری ارتباط

socket.connect()

socket.emit('join',Nickname); 

        } catch (URISyntaxException e) {

            e.printStackTrace();

        }

    }

}

حال شبیه‌ساز خود را اجرا کرده، و یک نام مستعار در activity اول وارد کنید. سپس بر روی دکمه go کلیک کنید. حال یک log در کنسول می‌بینید که نشان می‌دهد که یک کاربر با موفقیت با سرور ارتباط برقرار کرده است.

حال که همه چیز به درستی کار می‌کند، بهتر است فراموش نکنیم که وقتی سرور ما یک رویداد را مدیریت می‌کند، برخی رویدادهای دیگر را نیز پخش می‌کند، پس آن رویدادها باید در سمت کاربر مدیریت شوند. برای این کار، یک listener برای رویداد userjoinedthechat می‌سازیم.

در ChatBoxActivity،‌ این کد را اضافه کنید:

socket.on("userjoinedthechat", new Emitter.Listener() {

    @Overridepublic void call(final Object... args) {

        runOnUiThread(new Runnable() {

            @Overridepublic void run() {

                String data = (String) args[0];

                Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();

            }

        });

}

حال دو شبیه‌ساز را به طور همزمان اجرا می‌کنیم، دو نام مستعار انتخاب می‌کنیم و می‌بینیم که هر دو آن‌ها می‌گویند که یک کاربر جدید وارد شده است.

حال به بخش پیام‌های چت برنامه می‌رسیم.

برای نمایش پیام‌ها، باید به این صورت پیش برویم:

۱. onclickListener را به دکمه send اضافه کرده، و محتویات پیام را از EditText بگیریم. سپس رویداد messagedetection را به همراه نام متسعار و محتویات پیام، با استفاده از متد emit() بیرون بریزیم.

۲. رویداد مورد نظر توسط سرور مدیریت می‌شود و به تمام کاربران ارسال می‌شود.

۳. یک Socket Listener برای رویداد message به اندروید اضافه می‌کنیم.

۴. نام مستعار و پیام را از داده‌ها استخراج کنیم و نمونه جدیدی برای آبجکت Message بسازیم.

۵. نمونه به دست آمده را به ArrayList پیام‌ها اضافه کنیم و به adapter بگوییم که Recycler View را بروزرسانی کند.

اما قبل از آن، بیایید Recycler view، Adapter، فیلد پیام و دکمه send را راه‌اندازی کنیم.

کد زیر را به ChatBoxActivity اضافه کنید:

public RecyclerView myRecylerView ;
public List MessageList ;
public ChatBoxAdapter chatBoxAdapter;
public  EditText messagetxt ;
public  Button send ;

در متد onCreate، این خط‌ها را اضافه کنید:

messagetxt = (EditText) findViewById(R.id.message) ;
send = (Button)findViewById(R.id.send);
MessageList = new ArrayList<>();
myRecylerView = (RecyclerView) findViewById(R.id.messagelist);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
myRecylerView.setLayoutManager(mLayoutManager);
myRecylerView.setItemAnimator(new DefaultItemAnimator());

حال Button Action در ChatBoxActivity باید به این صورت باشد:

send.setOnClickListener(new View.OnClickListener() {

    @Overridepublic void onClick(View v) {

        //دریافت نام مستعار، محتویات پیام و اجرای رویداد

 messagedetection

  if(!messagetxt.getText().toString().isEmpty()){

            socket.emit("messagedetection",Nickname,messagetxt.getText().toString());

            messagetxt.setText(" ");         

    }

    }

});

و Listener نیز باید به این صورت باشد:

socket.on("message", new Emitter.Listener() {

    @Overridepublic void call(final Object... args) {

        runOnUiThread(new Runnable() {

            @Overridepublic void run() {

                JSONObject data = (JSONObject) args[0];

                try {

                    //استخراج داده‌ها از رویداد اجرا شده

              String nickname = data.getString("senderNickname");

              String message = data.getString("message");

           // ایجاد نمونه از پیام

          Message m = new Message(nickname,message);

          //اضافه کردن پیام‌ها به مسج‌لیست

          MessageList.add(m);

          // اضافه کردن لیست بروزرسانی شده به آداپتور 

          chatBoxAdapter = new ChatBoxAdapter(MessageList);

          chatBoxAdapter.notifyDataSetChanged();

          myRecylerView.setAdapter(chatBoxAdapter);

                } catch (JSONException e) {

                    e.printStackTrace();

                }

            }

        });

    }

});

همانطور که در اسکرین‌شات زیر می‌توانید ببینید، همه چیز به درستی کار می‌کند و پیام‌ها برای هر دو طرف نمایش داده می‌شوند. دقت کنید که با چندین کاربر دیگر نیز می‌توانیم وارد شویم، اما باید از شبیه‌ساز‌های دیگر استفاده کنیم.

قبل از پایان این آموزش، باید عملکرد آخر که تشخیص می‌دهد که یک کاربر خارج شده است را تعریف کنیم.

در ChatBoxActivity، متد onDestroy() را بازنویسی کنید و این کد را در آن قرار دهید:

@Override
protected void onDestroy() {
    super.onDestroy();
    socket.disconnect(); 
}

و برای Listener نیز:

socket.on("userdisconnect", new Emitter.Listener() {

    @Overridepublic void call(final Object... args) {
        runOnUiThread(new Runnable() {

            @Overridepublic void run() {

                String data = (String) args[0];

                Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();

            }

        });

    }
});

در آخر، ChatBoxActivity به این صورت خواهد بود:

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.support.v7.widget.DefaultItemAnimator;

import android.support.v7.widget.LinearLayoutManager;

import android.support.v7.widget.RecyclerView;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

import android.widget.Toast;

import com.github.nkzawa.emitter.Emitter;

import com.github.nkzawa.socketio.client.IO;

import com.github.nkzawa.socketio.client.Socket;

import org.json.JSONException;

import org.json.JSONObject;

import java.net.URISyntaxException;

import java.util.ArrayList;

import java.util.List;

public class ChatBoxActivity extends AppCompatActivity {

    public RecyclerView myRecylerView ;

    public List MessageList ;

    public ChatBoxAdapter chatBoxAdapter;

    public  EditText messagetxt ;

    public  Button send ;

    public String Nickname ;

    @Overrideprotected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_chat_box);

        messagetxt = (EditText) findViewById(R.id.message) ;

        send = (Button)findViewById(R.id.send);

        // دریافت نام مستعار کاربر

        Nickname= (String)getIntent().getExtras().getString(MainActivity.NICKNAME);

        //اتصال سوکت کاربر به سرور {

            socket = IO.socket("http://yourlocalIPaddress:3000");

            socket.connect();

            socket.emit("join", Nickname);

        } catch (URISyntaxException e) {

            e.printStackTrace();

        }

       //راه‌اندازی recyler

        MessageList = new ArrayList<>();

        myRecylerView = (RecyclerView) findViewById(R.id.messagelist);

        RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());

        myRecylerView.setLayoutManager(mLayoutManager);

        myRecylerView.setItemAnimator(new DefaultItemAnimator());

        // اکشن ارسال پیام

        send.setOnClickListener(new View.OnClickListener() {

            @Overridepublic void onClick(View v) {

                //دریافت نام مستعار، محتویات پیام و اجرای رویداد messagedetectionif(!messagetxt.getText().toString().isEmpty()){

                    socket.emit("messagedetection",Nickname,messagetxt.getText().toString());

                    messagetxt.setText(" ");

                }

            }

        });

        socket.on("userjoinedthechat", new Emitter.Listener() {

            @Overridepublic void call(final Object... args) {

                runOnUiThread(new Runnable() {

                    @Overridepublic void run() {

                        String data = (String) args[0];

                        Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();

                    }

                });

            }

        });

        socket.on("userdisconnect", new Emitter.Listener() {

            @Overridepublic void call(final Object... args) {

                runOnUiThread(new Runnable() {

                    @Overridepublic void run() {

                        String data = (String) args[0];

                        Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();

                    }

                });

            }

        });

        socket.on("message", new Emitter.Listener() {

            @Overridepublic void call(final Object... args) {

                runOnUiThread(new Runnable() {

                    @Overridepublic void run() {

                        JSONObject data = (JSONObject) args[0];

                        try {

                            //استخراج داده‌ها از رویداد اجرا شده

                            String nickname = data.getString("senderNickname");

                            String message = data.getString("message");

                            // ساخت نمونه از پیام

                            Message m = new Message(nickname,message);

                            //اضافه کردن پیام به مسج لیست

                            MessageList.add(m);

                            // اضافه کردن لیست بروزرسانی شده به آداپتور

                            chatBoxAdapter = new ChatBoxAdapter(MessageList);

                            chatBoxAdapter.notifyDataSetChanged();

                            myRecylerView.setAdapter(chatBoxAdapter);

                        } catch (JSONException e) {

                            e.printStackTrace();

                        }

                    }

                });

            }

        });

    }

    @Override

protected void onDestroy() {

        super.onDestroy();

        socket.disconnect(); 

    }

}

نتیجه گیری

در این مثال، نگاه خوبی به استفاده از Socket.io به همراه Node.js و اندروید داشتیم. ما سعی کردیم که پایه‌ها را توضیح دهیم و مکانیزم Socket.io را درک کنیم. امیدوارم که این آموزش برای شما کاربردی بوده باشد.

کدهای مربوط به اندروید : https://github.com/medaymenTN/AndroidChat

کدهای مربوط به سرور : https://github.com/medaymenTN/NodeJSChatServer

منبع

مقالات پیشنهادی

ساخت یک برنامه چت Real-time با Sails.js | بخش اول

اگر توسعه دهنده ای هستید که در حال حاضر از framework هایی مثل Django، Laravel یا Rails استفاده می کند، احتمالا درباره Node.js شنیده اید. شاید در حال ح...

هک css - ساخت یک Dropdowns تنها با checkbox

در این آموزش سریع ، ما یک نگاه به تنها روش ساخت Dropdowns کردیم بعد تصمصیم گرفتیم به دنبال یک راه دیگه برای ساخت Dropdowns بگردیم که به یک روش هوشمندا...

آموزش ساخت برنامه موبایل توسط React Native – قسمت اول

در این سری آموزش‌ها میخوایم درباره ساخت اپلیکیشن های native برای ios و اندروید یاد بگیریم. اما این کار رو توسط فریمورک محبوب React Native Javascript ا...

آموزش ساخت برنامه موبایل توسط React Native – قسمت دوم

در قسمت قبل با ساختار و نحوه راه اندازی React Native آشنا شدیم, در این قسمت میخواهیم بیشتر روی کار عملی تمرکز کنیم و پروژه رو جلو ببریم.