본문 바로가기
AndroidStudio

Firebase : google 클라우드 백엔드 플랫폼 *실시간 채팅창만들기

by EUN-JI 2023. 9. 7.

<firebase적용하면 인터넷설정 기본으로 됨  // **바인딩하기 >

android {
    buildFeatures{
        viewBinding true
    }
}

 firebase 문서 > 안드로이드> 등록  <애플리케이션아이디는 gradle module:app 에 있음 >

*프로젝트추가>단계별로 프로젝트 추가 후 > 안드로이드랑 연결후 > 사이트에 규칙 true 변경

*안드로이드스튜디오>사이트 다운로드 문서 중복되면 기존 꺼 삭제 후 사용 (이름변경 x )>  ::프로젝트형식으로 변경 후 >APP폴더에 >다운로드 형식 복붙 :::(google-services.json)

 

*안드로이드 gradle(프로젝트) <추가>

plugins {
    id 'com.google.gms.google-services' version '4.3.15' apply false
}

*안드로이드 gradle(모듈앱) <추가>  <기본설정(1개),firebasestorage(1개), firebasefirestoreDB(1개)  둘다 총 3개 추가>

plugins {
    id 'com.google.gms.google-services'
}
dependencies {

 	implementation 'com.github.bumptech.glide:glide:4.16.0'//글라이드 범프테크
    implementation platform('com.google.firebase:firebase-bom:32.2.3')  //둘 다 원래 기본설정
    implementation("com.google.firebase:firebase-firestore") //스토리지
    implementation("com.google.firebase:firebase-storage")  //파이얼베이스DB
}


ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
Firebase  : Google 클라우드 백엔드 플랫폼.
storage :   //저장소 :테이블형식
FirestoreDatabase:  //NoSQL DB  :가지형식

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

<Ex72FirebaseChatting.app>

drawable>bg_edit.xml  >shape

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">

    <stroke android:width="2dp" android:color="#DE0BED"/>
    <solid android:color="@color/white"/>
    <corners android:radius="4dp"/>

</shape>

drawable>bg_mymsgbox.xml> shape

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <stroke android:width="1dp" android:color="#FFFFFF"/>
    <solid android:color="#DE0BED"/>
    <corners android:radius="4dp"/>

</shape>

drawable>bg_othermsgbox.xml> shape

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <stroke android:width="1dp" android:color="#DE0BED"/>
    <solid android:color="#F6F4F6"/>
    <corners android:radius="4dp"/>

</shape>

layout>

ac_chatting.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".ChattingActivity">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#DE0BED"
        app:title="채팅방"
        app:titleTextColor="@color/white"
        app:subtitle="room"
        app:subtitleTextColor="@color/white"
        app:subtitleCentered="true"
        app:titleCentered="true"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        android:orientation="vertical"
        app:stackFromEnd="true"/>
<!-- app:stackFromEnd="true" 밑에서 부터 쌓아 올림-->

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="4dp"
        android:background="#DE0BED"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/et"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@drawable/bg_edit"
            android:padding="10dp"
            android:inputType="textMultiLine"
            android:maxLines="3"
            android:hint="enter message"/>
        <Button
            android:id="@+id/btn_send"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginLeft="8dp"
            android:backgroundTint="@color/white"
            style="@style/Widget.MaterialComponents.Button"
            android:textColor="#DE0BED"
            android:text="SEND"/>

    </LinearLayout>

</LinearLayout>

ac_main.xml

<?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=".MainActivity">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/input_layout_nickname"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:hint="닉네임"
        android:layout_centerInParent="true">
        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="text"
            android:gravity="center"/>


    </com.google.android.material.textfield.TextInputLayout>

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_above="@id/input_layout_nickname"
        android:layout_centerHorizontal="true"
        app:civ_border_width="1dp"
        app:civ_border_color="@color/black"
        android:src="@mipmap/ic_launcher"
        android:layout_marginBottom="16dp"/>

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="입장"
        android:layout_margin="16dp"
        android:layout_alignParentBottom="true"
        android:backgroundTint="#DE0BED"
        android:layout_alignParentRight="true"/>



</RelativeLayout>

my_message_item

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:padding="16dp"
    android:layout_height="wrap_content">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@mipmap/ic_launcher"
        android:layout_alignParentRight="true"/>

    <TextView
        android:id="@+id/tv_nickname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="nickname"
        android:textColor="#DE0BED"
        android:textStyle="bold"
        android:layout_toLeftOf="@id/civ"
        android:layout_marginRight="16dp"/>

    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="this is message"
        android:layout_alignRight="@id/tv_nickname"
        android:layout_below="@id/tv_nickname"
        android:background="@drawable/bg_mymsgbox"
        android:textColor="@color/white"
        android:maxWidth="250dp"
        android:padding="12dp"/>
    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="15:37"
        android:textSize="12sp"
        android:layout_toLeftOf="@id/tv_msg"
        android:layout_alignBottom="@id/tv_msg"
        android:layout_marginRight="8dp"/>



</RelativeLayout>

other_message_item

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:padding="16dp"
    android:layout_height="wrap_content">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@mipmap/ic_launcher"
        android:layout_alignParentLeft="true"/>

    <TextView
        android:id="@+id/tv_nickname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="nickname"
        android:textColor="#DE0BED"
        android:textStyle="bold"
        android:layout_toRightOf="@id/civ"
        android:layout_marginLeft="16dp"/>

    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="this is message"
        android:layout_alignLeft="@id/tv_nickname"
        android:layout_below="@id/tv_nickname"
        android:background="@drawable/bg_othermsgbox"
        android:textColor="#DE0BED"
        android:maxWidth="250dp"
        android:padding="12dp"/>
    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="15:37"
        android:textSize="12sp"
        android:layout_toRightOf="@id/tv_msg"
        android:layout_alignBottom="@id/tv_msg"
        android:layout_marginLeft="8dp"/>



</RelativeLayout>

java>

chattingAC

package com.eunji0118.ex72firebasechatting;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;

import com.eunji0118.ex72firebasechatting.databinding.ActivityChattingBinding;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentChange;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.QueryDocumentSnapshot;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map;

public class ChattingActivity extends AppCompatActivity {

    ActivityChattingBinding binding;

    String roomName="room1";
    FirebaseFirestore firestore;
    CollectionReference roomRef;

    ArrayList<MessageItem> messageItems=new ArrayList<>();

    MessageAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding=ActivityChattingBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        binding.toolbar.setSubtitle(roomName);  //밑에 글씨 바뀜. 섭 타이틀
        binding.btnSend.setOnClickListener(view -> clickSend());

        adapter=new MessageAdapter(this,messageItems);
        binding.recyclerView.setAdapter(adapter);

        firestore=FirebaseFirestore.getInstance();
        roomRef=firestore.collection(roomName);

        roomRef.addSnapshotListener((value, error) -> {
            //변경된 도큐먼트들만 가져오기  //바뀐 정보만 가져옴
            List<DocumentChange> documentChangeList=value.getDocumentChanges();
            for (DocumentChange documentChange:documentChangeList){
                QueryDocumentSnapshot snapshot =documentChange.getDocument();

                Map<String,Object> fields =snapshot.getData();
                String nickname=fields.get("nickname").toString();
                String message=fields.get("message").toString();
                String time=fields.get("time").toString();
                String profileUrl=fields.get("profileUrl").toString();
                //값이 바뀔때 마다 변경된 것 알려줌
                messageItems.add(new MessageItem(nickname,message,time,profileUrl));
                adapter.notifyItemInserted(messageItems.size()-1);  //한개만 추가적으로 갱신
                
            }
            //몇개인지 확인차 .
            Toast.makeText(this, ""+messageItems.size(), Toast.LENGTH_SHORT).show();
        });

    }
    void clickSend(){
        //저장할 메세지의 데이터들 [nickname, message, time,profileUrl] 을 가진 메세지아이템 클래스객체 생성.
        String message=binding.et.getText().toString();

        //메세지 작성 시간을 문자열로 [시:분]
        Calendar calendar=Calendar.getInstance();  //달력객체OFDAY24시간제
        String time=calendar.get(Calendar.HOUR_OF_DAY)+":"+calendar.get(Calendar.MINUTE);

        MessageItem messageItem=new MessageItem(G.nickname,message,time,G.profileUrl);
        //퍼블릭이여야 쓸수있음.
        roomRef.document("MSG_"+System.currentTimeMillis()).set(messageItem);

        binding.et.setText(""); //글씨써서 날리면 비어있음.
        InputMethodManager imm=(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        //키보드내리기  0==지금즉시
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),0);

    }
}

G

package com.eunji0118.ex72firebasechatting;

public class G {
    public static String nickname;  //닉네임
    public static String profileUrl; //프로필 사진의 URL(인터넷주소)
}

MAINAC

package com.eunji0118.ex72firebasechatting;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.content.SharedPreferences;
import android.media.MediaDataSource;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.Toast;

import com.bumptech.glide.Glide;
import com.eunji0118.ex72firebasechatting.databinding.ActivityMainBinding;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity {
    ActivityMainBinding binding;
    Uri imgUri;

    boolean isFirst= true;
    boolean isChangeProfile=false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding= ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        binding.civ.setOnClickListener(view -> clickImage());
        binding.btn.setOnClickListener(view -> clickBtn());

        //디바이스에 저장되어 있는 닉네임과 사진 로딩하기
        loadAccountData();

        if (G.nickname !=null){
            binding.inputLayoutNickname.getEditText().setText(G.nickname);
            Glide.with(this).load(G.profileUrl).into(binding.civ);

            isFirst=false;
        }
    }

    void loadAccountData(){
        SharedPreferences pref=getSharedPreferences("account",MODE_PRIVATE);
        G.nickname=pref.getString("nickname",null);
        G.profileUrl=pref.getString("profileUrl",null);
    }
    void clickBtn(){
        //저장된 적이 없는 첫방문 이거나 프로필 사진을 변경한 적이 있는지
        if (isFirst||isChangeProfile) {
            saveData();
        }
        else {
            startActivity(new Intent(this, ChattingActivity.class));
            finish();
        }
    }

    void saveData(){
        if (imgUri==null) return;

        G.nickname= binding.inputLayoutNickname.getEditText().getText().toString();
        //우선 이미지부터 Firebasee Storage에 저장

        SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddHHmmss");
        String refName= sdf.format(new Date());

        FirebaseStorage storage=FirebaseStorage.getInstance();
        StorageReference imgRef= storage.getReference("profile/"+refName);

        imgRef.putFile(imgUri).addOnSuccessListener(taskSnapshot -> {
           //계쩡정보를 DB에 저장할때 프로필 이미지의 다운로드 URL이 필요함 (언티넷찐주소)
            imgRef.getDownloadUrl().addOnSuccessListener(uri -> {
               G.profileUrl=uri.toString();
                Toast.makeText(this, "프로필 이미지 저장 완료", Toast.LENGTH_SHORT).show();
                //1.서버의 Firestore DB에 닉네임과 이미지URL을 저장  //서버에 저장하고
                FirebaseFirestore firestore=FirebaseFirestore.getInstance();
                //account 라는 이름의 컬렉션을 참조(테이블이름 같은 것)
                CollectionReference accountRef=firestore.collection("accountRef");

                Map<String,Object> account=new HashMap<>();
                account.put("profileUrl",G.profileUrl);
                //닉네임을 document명으로 사용하여 계정들 구분
                accountRef.document(G.nickname).set(account);

                //2.디바이스에도 닉네임과 이미지 URL을 저장- SharedPreferences를 이용하여 저장  //핸드폰에 저장하고
                SharedPreferences pref=getSharedPreferences("account",MODE_PRIVATE);
                SharedPreferences.Editor editor =pref.edit();

                editor.putString("nickname",G.nickname);
                editor.putString("profileUrl",G.profileUrl);

                editor.commit();  //내부적으로 트랜젝션으로 동적

                //저장이 완료되면 채팅창으로 전환함.
                Intent intent=new Intent(MainActivity.this, ChattingActivity.class);
                startActivity(intent);
                finish();  //액티비티 끄는것.
            });
        });


    }
    void clickImage(){
        Intent intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
        resultLauncher.launch(intent);
    }

    ActivityResultLauncher<Intent> resultLauncher=registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),result -> {
        if (result.getResultCode()==RESULT_CANCELED) return;

        imgUri= result.getData().getData();
        Glide.with(this).load(imgUri).into(binding.civ);


        isChangeProfile= true;

    });
}

MessageAdapter

package com.eunji0118.ex72firebasechatting;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.Glide;
import com.eunji0118.ex72firebasechatting.databinding.MyMessageItemBinding;
import com.eunji0118.ex72firebasechatting.databinding.OtherMessageItemBinding;

import java.util.ArrayList;

public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    Context context;
    ArrayList<MessageItem> messageItems;
    final int TYPE_MY= 1;
    final int TYPE_OTHER= 2;

    public MessageAdapter(Context context, ArrayList<MessageItem> messageItems) {
        this.context = context;
        this.messageItems = messageItems;
    }
    //alt+insert

    //리사이클러뷰가 만들어낼 아이템뷰의 모양이 다르게 해야 할 때..아이템별로 뷰의 타입을 지정하기 위한 콜백메소드


    @Override
    public int getItemViewType(int position) {
        if (messageItems.get(position).nickname.equals(G.nickname)) return TYPE_MY;
        else return TYPE_OTHER;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType==TYPE_MY){
            View itemView= LayoutInflater.from(context).inflate(R.layout.my_message_item,parent,false);
            return new VH_MY(itemView);
        }else {
            View itemView= LayoutInflater.from(context).inflate(R.layout.other_message_item,parent,false);
            return new VH_OTHER(itemView);

        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        MessageItem item=messageItems.get(position);

        if (item.nickname.equals(G.nickname)){
            VH_MY vh=(VH_MY) holder;
            vh.binding.tvNickname.setText(item.nickname);
            vh.binding.tvMsg.setText(item.message);
            vh.binding.tvTime.setText(item.time);
            Glide.with(context).load(item.profileUrl).into(vh.binding.civ);

        }else {
            VH_OTHER vh=(VH_OTHER) holder;
            vh.binding.tvNickname.setText(item.nickname);
            vh.binding.tvMsg.setText(item.message);
            vh.binding.tvTime.setText(item.time);
            Glide.with(context).load(item.profileUrl).into(vh.binding.civ);

        }

    }

    @Override
    public int getItemCount() {
        return messageItems.size();
    }

    class VH_MY extends RecyclerView.ViewHolder{
        MyMessageItemBinding binding;

        public VH_MY(@NonNull View itemView) {
            super(itemView);
            binding=MyMessageItemBinding.bind(itemView);
        }
    }

    class VH_OTHER extends RecyclerView.ViewHolder{
        OtherMessageItemBinding binding;

        public VH_OTHER(@NonNull View itemView) {
            super(itemView);
            binding=OtherMessageItemBinding.bind(itemView);
        }
    }

}

MessageItem

package com.eunji0118.ex72firebasechatting;

public class MessageItem {
    public String nickname;
    public String message;
    public String time;
    public String profileUrl;

    public MessageItem(String nickname, String message, String time, String profileUrl) {
        this.nickname = nickname;
        this.message = message;
        this.time = time;
        this.profileUrl = profileUrl;
    }

    public MessageItem() {
    }
}

내가 봤을 때 화면
상대방이 봤을 때 화면