とっとこメモ

困った時のエンジニアメモ。Unity, Kotlin, Flutter などなど

【Kotlin】Socket.io で簡易チャットアプリ

ネットワーク周りの設定

AndroidManifest.xml の修正

インターネット通信を許可するために

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

を追加する

http通信を許可するために

android:usesCleartextTraffic="true"

を追加する

以下全文

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.sample.websocket">

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

    <application
        android:allowBackup="true"
        android:usesCleartextTraffic="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ChatBoxActivity"></activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

ライブラリを追加

WebSocketを使用するために socket.io-client を追加

dependencies {
  ・・・・
    implementation('io.socket:socket.io-client:1.0.0') {
        exclude group: 'org.json', module: 'json'
    }
    implementation 'com.google.android.material:material:1.0.0-rc01'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
}

作成するファイル

ファイル名 内容
MainActivity.kt 簡易ログイン画面
activity_main.xml 簡易ログイン画面レイアウトファイル
Messsage.kt メッセージデータ用クラス
ChatBoxAdapter.kt チャット一覧用アダプターファイル
item.xml チャット一覧用レコードレイアウトファイル
ChatBoxActivity.kt チャット一覧画面
activity_chat_box.xml チャット一覧画面レイアウトファイル

簡易ログイン画面の作成

ユーザ 名を入力するためだけの簡易的な画面 チャット画面に表示させる名前を入力

MainActivity.kt

package com.example.android.sample.websocket

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    companion object{
        const val NICKNAME: String = "usernickname"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        enterchat.setOnClickListener{
            if(nickname.text.isNotEmpty()){
                var i: Intent = Intent(this, ChatBoxActivity::class.java)
                i.putExtra(NICKNAME, nickname.text.toString())
                startActivity(i)
            }
        }
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
        <EditText
            android:id="@+id/nickname"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:textSize="30dp"
            android:hint="Enter your name !"/>
        <Button
            android:layout_below="@+id/nickname"
            android:id="@+id/enterchat"
            android:text="Go to Chat"
            android:layout_height="wrap_content"
            android:layout_width="match_parent">
        </Button>
</RelativeLayout>

メッセージ用データクラスを作成

Message.kt

package com.example.android.sample.websocket
data class Message (var nickname: String, var message:String)

メッセージ一覧(RecylerView)のレコードレイアウトファイル

<LinearLayout android:orientation="horizontal"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <TextView
        android:id="@+id/nickname"
        android:textSize="15dp"
        android:textStyle="bold"
        android:text="NickName : "
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"></TextView>
    <TextView
        android:id="@+id/message"
        android:text="message "
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </TextView>
</LinearLayout>

メッセージ一覧(RecyclerView)に使用するアダプターを作成

取得したデータをRecylerViewに適用する

ChatBoxAdapter.kt

package com.example.android.sample.websocket

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item.view.*

class ChatBoxAdapter(private val MessageList: ArrayList<Message>) : RecyclerView.Adapter<ChatBoxAdapter.MyViewHolder>() {

    inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val nickName = view.nickname
        val message = view.message
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatBoxAdapter.MyViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
        return MyViewHolder(view)
    }

    override fun getItemCount(): Int {
        return MessageList.size
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val m: Message = MessageList.get(position)
        holder.nickName.text = m.nickname
        holder.message.text = m.message
    }
}

チャット画面の実装

ChatBoxActivity.kt

package com.example.android.sample.websocket

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import io.socket.client.IO
import io.socket.client.Socket
import io.socket.emitter.Emitter
import kotlinx.android.synthetic.main.activity_chat_box.*
import org.json.JSONException
import org.json.JSONObject
import java.net.URISyntaxException

class ChatBoxActivity : AppCompatActivity() {

    private lateinit var socket: Socket
    public var Nickname: String = ""
    public lateinit var  MessageList: ArrayList<Message>
    public lateinit var chatBoxAdapter: ChatBoxAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_chat_box)

        val intent: Intent = getIntent()
        Nickname = intent.getStringExtra(MainActivity.NICKNAME)

        try {
            socket = IO.socket("http://[チャットサーバのURL]")
            socket.connect()
            socket.emit("join", Nickname)
        } catch (e: URISyntaxException){
            e.printStackTrace()
        }

        MessageList = arrayListOf()
        messagelist.adapter = ChatBoxAdapter(MessageList)
        messagelist.layoutManager = LinearLayoutManager(this)

        send.setOnClickListener{
            socket.emit("messagedetection", Nickname, message.text.toString())
            message.setText("")
        }

        socket.on("userjoinedthechat", Emitter.Listener {args ->
            runOnUiThread(Runnable {
                val data = args[0] as String
                Toast.makeText(this,data,Toast.LENGTH_SHORT).show();
            })
        })

        socket.on("userdisconnect", Emitter.Listener {args ->
            runOnUiThread(Runnable {
                val data = args[0] as String
                Toast.makeText(this,data, Toast.LENGTH_SHORT).show();
            })
        })

        socket.on("message", Emitter.Listener {args ->
            runOnUiThread(Runnable {
                val data = args[0] as JSONObject
                try{
                    var nickNameString: String = data.getString("senderNickname")
                    var messageString: String = data.getString("message")
                    val m: Message = Message(nickNameString, messageString)
                    MessageList.add(m)

                    chatBoxAdapter = ChatBoxAdapter(MessageList)
                    chatBoxAdapter.notifyDataSetChanged()
                    messagelist.adapter = chatBoxAdapter

                } catch (e : JSONException){
                    e.printStackTrace()
                }
            })
        })
    }

    override fun onDestroy() {
        super.onDestroy()
        socket.disconnect()
    }
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ChatBoxActivity">
    <LinearLayout
        android:weightSum="3"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <View
            android:layout_marginTop="5mm"
            android:id="@+id/separator"
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="@android:color/darker_gray"/>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/messagelist"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="3"
            android:clipToPadding="false"
            android:scrollbars="vertical" />

        <LinearLayout
            android:weightSum="3"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/message"
                android:layout_weight="3"
                android:layout_width="wrap_content"
                android:hint="your message"
                android:layout_height="match_parent" />
            <Button
                android:id="@+id/send"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="#00000000"
                android:text="send"
                />
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>