Tutorial

Node.js +Android Studio(Kotlin) 채팅 구현하기(1)

Gyeony95 2019. 9. 20. 23:30
반응형

이번 포스팅에서는 Backend는 Node.js  Frontend는 AndroidStudio를 사용하여 채팅을 구현하는 예제를 만들어 보겠습니다.

 

 node.js 는 visual studio code를 사용해서 작업하겠습니다.

 

node.js에는 Socket.io 라이브러리가 존재하여 소켓에대한 지식이 어느정도만 있다면 쉽게 통신을 구현할 수 있습니다.

 

우선 채팅을 해야하기 때문에 Recyclerview를 이용해 레이아웃을 만들어 보겠습니다.

 

첫번째로 채팅에 참여할 이름을 설정하기 위해 메인 액티비티에 간단하게 Edittext 하나와 Button하나를 만들어줍니다.

 

안드로이드 스튜디오에서 새로운 프로젝트를 생성해줍니다.  언어는 코틀린을 사용하겠습니다.

 

Node.js +Android Studio(Kotlin) 채팅 구현하기(1) 에서는 socket통신을 하기전에 채팅의 틀을 만들어주겠습니다.

 

 

 

<activity_main.xml>

//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity" android:orientation="vertical">
    <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPersonName"
            android:text="Name"
            android:ems="10"
            android:id="@+id/editText"/>
    <Button
            android:text="Button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" android:id="@+id/button"/>
</LinearLayout>
 
 

 

 

이어서 채팅할때 리사이클러뷰에 본인이 친 채팅을 올릴 아이템을 만들어줍니다.

<item_my_chat.xml>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
 
    android:layout_gravity="right"
    android:orientation="vertical">
 
    <TextView
        android:id="@+id/chat_Me_Name"
        android:layout_width="wrap_content"
        android:layout_height="1dp" />
 
    <TextView
        android:id="@+id/chat_Text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:layout_margin="10dp"
        android:background="@drawable/layout_round"
        android:gravity="center_vertical"
 
        android:padding="5dp"
        android:text="내가 입력한 내용"
        android:textSize="20dp" />
 
    <TextView
        android:id="@+id/chat_Time"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="right|center_vertical"
        android:text="시간" />
</LinearLayout>
 
 

그다음엔 상대방이 친 채팅을 기록하는 아이템을 만들어 줍니다.

 

<item_your_chat.xml>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal">
 
        <ImageView
            android:id="@+id/chat_You_Image"
            android:layout_width="100dp"
            android:layout_height="80dp"
            android:layout_weight="1"
            android:src="@drawable/ic_launcher_background" />
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical">
 
            <TextView
                android:id="@+id/chat_You_Name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="김xx"
                android:layout_marginLeft="10dp"
                android:textSize="20dp"
                android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
 
            <TextView
                android:id="@+id/chat_Text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="left"
                android:layout_margin="10dp"
                android:background="@drawable/layout_round"
                android:gravity="center_vertical"
                android:padding="5dp"
                android:text="상대가 입력한 내용"
                android:textSize="20dp" />
 
            <TextView
                android:id="@+id/chat_Time"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:gravity="center_vertical"
                android:text="시간" />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>
 
 

 

참고로 item_my_chat과 item_your_chat 레이아웃에 말풍선 처럼 보이게 하기위해

textview 속성중에  android:background="@drawable/layout_round"   이런게있는데

 

res/drawable 폴더안에 

 

<layout_round.xml>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle" >
            <solid android:color="#f2f2f2" />
        </shape>
    </item>
 
    <item  android:top="1dp" android:left="1dp" android:right="1dp">
        <shape>
            <solid android:color="@android:color/transparent" />
            <stroke
                android:width="1.0dp"
                android:color="#494646" />
            <corners android:radius="10dip"/>
        </shape>
    </item>
</layer-list>
 
 
 

이런 파일을 만들어 줍니다.

 

자, 이제부터 kotlin파일들을 만들어 주겠습니다.

 

먼저 데이터를 주고받기위해 사용할 채팅 모델을 만들어줍니다.

 

 

<ChatModel.kt>

class ChatModel(val name: String, val script:String, val profile_image:String, val date_time:String){
}

 

새로운 코틀린 클래스파일을 만든다음 위와같은 모델을 만들어 줍니다.

 

내 채팅에는 script와 date_time만 사용될 것이고 상대의 채팅에는 name, script, profile_image, date_time을 모두 사용할 것입니다.

 

다음으로 채팅룸에 참가하기위해 이름을 정의해주는 MainActivity 코드를 작성해 주겠습니다.

 

<MainActivity.kt>

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

class MainActivity : AppCompatActivity() {

    internal lateinit var preferences: SharedPreferences


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

        preferences = getSharedPreferences("USERSIGN", Context.MODE_PRIVATE)
        val editor = preferences!!.edit()


        //버튼을 클릭하면 입력한 이름을 쉐어드프리퍼런스에 내이름을 저장한다.
        //또한 그 이름을 가지고 채팅방으로 이동한다.
        button.setOnClickListener{
            editor.putString("name", editText.text.toString())
            val intent = Intent(this, ChatRoomActivity::class.java)
            startActivity(intent)
        }

    }
}

 

위의 코드는 간단합니다.

EditText에 자신의 고유 아이디를 입력한후 Button을 누르면 그 이름을 SharedPreferences에 저장한 후 ChatRoomActivity로 이동합니다.

 

//이름을 intent에 putextra로 넘겨주지 않은 이유는 한번 넘기고 마는게 아니라 어댑터에서도 사용해줄것이기 때문

 

자 그럼 위의 이름을 가지고 채팅방에 참가할 것이기 때문에 ChatRoomActivity 코드와 ChatAdapter코드를 동시에  작성해주겠습니다. 

 

<ChatRoomActivity.kt>

import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_chat_room.*
import java.text.SimpleDateFormat
import java.util.*


class ChatRoomActivity: AppCompatActivity() {
    internal lateinit var preferences: SharedPreferences
    private lateinit var chating_Text: EditText
    private lateinit var chat_Send_Button: Button
    //리사이클러뷰
    var arrayList = arrayListOf()
    val mAdapter = ChatAdapter(this, arrayList)


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_chat_room)
        preferences = getSharedPreferences("USERSIGN", Context.MODE_PRIVATE)


        //어댑터 선언
        chat_recyclerview.adapter = mAdapter
        //레이아웃 매니저 선언
        val lm = LinearLayoutManager(this)
        chat_recyclerview.layoutManager = lm
        chat_recyclerview.setHasFixedSize(true)//아이템이 추가삭제될때 크기측면에서 오류 안나게 해줌

        chat_Send_Button = findViewById(R.id.chat_Send_Button)
        chating_Text = findViewById(R.id.chating_Text)



        chat_Send_Button.setOnClickListener {
            //아이템 추가 부분
            sendMessage()

        }
    }


    fun sendMessage() {
        val now = System.currentTimeMillis()
        val date = Date(now)
        //나중에 바꿔줄것 밑의 yyyy-MM-dd는 그냥 20xx년 xx월 xx일만 나오게 하는 식
        val sdf = SimpleDateFormat("yyyy-MM-dd")

        val getTime = sdf.format(date)


        //example에는 원래는 이미지 url이 들어가야할 자리
        val item = ChatModel(preferences.getString("name",""),chating_Text.text.toString(),"example", getTime)
        mAdapter.addItem(item)
        mAdapter.notifyDataSetChanged()

        //채팅 입력창 초기화
        chating_Text.setText("")
    }

}

 

채팅 EditText에 내용을 입력하고 send버튼을 누르면 sendMessage() 메소드가 실행됩니다.

 

sendMessage() 에서는 채팅 모델을 정의해서 추가해줍니다.

 

채팅 모델에서 정의해준 데이터는  이름, 날짜(시간), 프로필이미지, 내용 이 네가지 요소를 가지고 reyclerview에 추가를 해주어야 합니다.

 

이름은 위에서 쉐어드프리퍼런스에 저장한것을 가져오고 내용은 채팅 EditText에 적은것을 가져와줍니다.

 

이미지는 원래 이미지 url을 가져와서 적어주어야 하나 여기서는 그냥 임의의 값을 넣어두고 어댑터에서 기본이미지를  넣어주는식으로 진행하겠습니다.

 

마지막으로 날짜(시간)은 현재 시간을 체크해주는 코드를 가지고 넣어줍니다. 

//위 코드에서 시간까지는 표기하고있지는 않습니다.

 

ChatRoomActivity에서 어댑터를 정의해줬습니다. 그코드는 아래에있습니다.

 

 

<ChatAdapter.kt>

import android.content.Context
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import java.net.URL
import java.util.ArrayList

class ChatAdapter(val context: Context, val arrayList: ArrayList)
    :  RecyclerView.Adapter() {

    internal lateinit var preferences: SharedPreferences

    fun addItem(item: ChatModel) {//아이템 추가
        if (arrayList != null) {
            arrayList.add(item)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view: View
        //getItemViewType 에서 뷰타입 1을 리턴받았다면 내채팅레이아웃을 받은 Holder를 리턴
        if(viewType == 1){
            view = LayoutInflater.from(context).inflate(R.layout.item_my_chat, parent, false)
            return Holder(view)
        }
        //getItemViewType 에서 뷰타입 2을 리턴받았다면 상대채팅레이아웃을 받은 Holder2를 리턴
        else{
            view = LayoutInflater.from(context).inflate(R.layout.item_your_chat, parent, false)
            return Holder2(view)
        }
    }

    override fun getItemCount(): Int {
        return arrayList.size

    }

    override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int) {
        //onCreateViewHolder에서 리턴받은 뷰홀더가 Holder라면 내채팅, item_my_chat의 뷰들을 초기화 해줌
        if (viewHolder is Holder) {
            (viewHolder as Holder).chat_Text?.setText(arrayList.get(i).script)
            (viewHolder as Holder).chat_Time?.setText(arrayList.get(i).date_time)
        }
        //onCreateViewHolder에서 리턴받은 뷰홀더가 Holder2라면 상대의 채팅, item_your_chat의 뷰들을 초기화 해줌
        else if(viewHolder is Holder2) {
            (viewHolder as Holder2).chat_You_Image?.setImageResource(R.mipmap.ic_launcher)
            (viewHolder as Holder2).chat_You_Name?.setText(arrayList.get(i).name)
            (viewHolder as Holder2).chat_Text?.setText(arrayList.get(i).script)
            (viewHolder as Holder2).chat_Time?.setText(arrayList.get(i).date_time)
        }

    }

    //내가친 채팅 뷰홀더
    inner class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        //친구목록 모델의 변수들 정의하는부분
        val chat_Text = itemView?.findViewById(R.id.chat_Text)
        val chat_Time = itemView?.findViewById(R.id.chat_Time)
    }

    //상대가친 채팅 뷰홀더
    inner class Holder2(itemView: View) : RecyclerView.ViewHolder(itemView) {

        //친구목록 모델의 변수들 정의하는부분
        val chat_You_Image = itemView?.findViewById(R.id.chat_You_Image)
        val chat_You_Name = itemView?.findViewById(R.id.chat_You_Name)
        val chat_Text = itemView?.findViewById(R.id.chat_Text)
        val chat_Time = itemView?.findViewById(R.id.chat_Time)


    }

    override fun getItemViewType(position: Int): Int {//여기서 뷰타입을 1, 2로 바꿔서 지정해줘야 내채팅 너채팅을 바꾸면서 쌓을 수 있음
        preferences = context.getSharedPreferences("USERSIGN", Context.MODE_PRIVATE)

        //내 아이디와 arraylist의 name이 같다면 내꺼 아니면 상대꺼
        return if (arrayList.get(position).name == preferences.getString("name","")) {
            1
        } else {
            2
        }
    }
}

 

ChatAdapter는 일반적인 recyclerview가 아닙니다.

 

상황에따라서 뷰타입을 두가지로 지정해주어야 하기때문에 평소에는 정의해주지 않는 getItemViewType 메소드를 오버라이드 해주어야 합니다.

 

getItemViewType() 메소드는 리사이클러뷰의 작동 순서에서 onCreateViewHolder 보다 먼저 작동하는 메소드입니다.

 

getItemViewType -> onCreateViewHolder -> onBindViewHolder 순서로 작동한다고 보시면 되겠습니다.

 

MainActivity에서 입력한 이름을 쉐어드프리퍼런스에 저장한 이유가 여기에 있습니다.

 

해당 포지션에 있는 객체의 이름이 쉐어드 프리퍼런스에 저장한 본인의 이름과 같을때 뷰타입 1번을 리턴 합니다.

그렇지 않다면 뷰타입 2번을 리턴합니다.

 

이렇게 리턴된 뷰타입은 onCreateViewHolder에서 1이면 item_my_chat 레이아웃을 가진 Holder를 리턴합니다.

2라면 item_your_chat 레이아웃을 가진 Holder2를 리턴합니다.

//각각의 홀더에서는 각각의 레이아웃에 있는 뷰들을 정의해주고 있습니다.

 

현재 아직까지는 소켓에 연결되어있지 않기때문에 뷰타입이 1일수 밖에없습니다.

 

채팅을 치게 되면 위와같은 화면이 나오게 됩니다.

 

다음 포스팅에서 Socket통신을 활용해서 채팅을 구현해보겠습니다.

반응형