Firebase 서버에 내가 보낸 데이터를 저장하고 남들이 내 데이터를 읽어 올 수 있도록 한다.
Step1. Firebase 연동 작업을 진행한다.
step2. MainActivity는 프로필설정 화면으로 만들어준다.
<?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">
<EditText
android:id="@+id/et"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:maxLength="10"
android:hint="닉네임"
android:layout_centerInParent="true"
android:gravity="center"
android:padding="8dp"
android:background="@drawable/bg_edit"/>
<!-- 최대길이 10개, ems도 길이이지만 10 넘어가면 스크롤됨-->
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/civ"
android:layout_marginBottom="16dp"
android:layout_above="@+id/et"
app:civ_border_width="2dp"
app:civ_border_color="#D6EE00"
android:layout_centerHorizontal="true"
android:src="@drawable/ms18"
android:layout_width="80dp"
android:layout_height="80dp"/>
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="입장"
android:backgroundTint="#DA76E4"
android:layout_margin="16dp"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
step3. MainActivity 작업 시작
프로필 이미지와 이름을 설정하자.
public class MainActivity extends AppCompatActivity {
//1.뷰바인딩
ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
binding=ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); //우리로치면 렐러티브
binding.civ.setOnClickListener(view-> clickImage());
binding.btn.setOnClickListener(view-> clickBtn());
}
void clickImage(){
//2. 이미지 바꿔주게 그 화면줘
Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
resultLauncher.launch(intent);
}//clickImage
//3.
ActivityResultLauncher<Intent> resultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if(result.getResultCode()==RESULT_CANCELED) return;
//이제 이미지의 Uri를 알고있어야한다. Uri를 전역변수에 만들어준다.
imgUri = result.getData().getData(); //택배기사, 물건 순서대로 받아오자
Glide.with(this).load(imgUri).into(binding.civ);
});
이미지는 permission이 없는 것으로 설정한다. ActivityResultLauncher는 결과를 받아오는 객체이다.
액티비티의 결과에 대해서 계약서도 써주고 StartActivityForResult(), 그 결과를 받아주는 callBack 객체를 만들어서 대응시키자.
여기까지하면 clickImage()를 실행 하였을때 밑에서 사진 선택할 수 있게 해준다.
step4. 내가 작성한 사진을 다른사람들이 봐야한다. 이미지를 서버(Firebase)에 올려야한다.
그런데 서버에 이미지를 언제 올려야할까? 입장을 눌렀을때!
(그림을 누를때마다 서버에이미지를 저장하는 것은 적절하지 않다. 채팅방 입장을 결심했을때 이미지를 서버에 업로드하자)
void clickBtn(){
saveData(); //saveData(); 라는 함수를 만들자
}
가정으로 이미지를 선택하지 못하면 채팅 못하게하자 .
프로필닉네임과 이미지를 저장해야하는데…. saveData(); 에서만 쓰지 않고 전역변수로 만들고 싶다. G라는 클래스를 만들어서 쓰자.
- 참고전역변수를 만들자! 꼼수로 G.class에!
- EditText에 있는 닉네임 가져와서 전역변수 역할의 G클래스에 대입- 하지만 이건 좋은 방법은 아니다. 그냥 쓰는데는 문제없음 - 다른 좋은 별도 라이브러리 클래스가 잇음
- case1: application 안에 액티비티가 있다. 여기에 멤버변수를 만들면 언제든 불러서 쓸수있다. 귀찮음 ㅠ case2: c에는 자바에는 전역변수가 없다. R클래스는 static으로 변수가 만들어짐. 그래서 우리가 new안하고 씀.
public class G {
public static String nickname; //new 안해도 된다. 정적변수
public static String profileUrl;// 사진이미지의 다운로드주소
}
//R장부를 힌트로 비슷하게 사용하자.
//static으로 만들면 객체를 만들지않아도 된다.
//public은 패키지명 상관없이 안쓴다.
// 전역변수 Global에서 이름을 따오자. G
//화면이 넘어가도 G를 쓰면 사용할 수 있다.
서버에 업로드하면 다운로드url (인터넷 주소)도 필요하다. 왜????
이미지 업로드를 우선적으로 시작하자! Uri주소를 먼저 가져오자.
Uri imgUri = null; //자바는..! 코틀린은 자동 null아님
void saveData(){
//5. 서버에 연동하고 저장하는 함수를 만들자.
//일단 이미지를 선택하지 않으면 ..! 채팅 불가
if(imgUri==null) return; //이미지 없으면 리턴하자
//먼저 닉네임부터 저장하자
G.nickname = binding.et.getText().toString();
//case1: application 안에 액티비티가 있다. 여기에 멤버변수를 만들면 언제든 불러서 쓸수있다. 귀찮음 ㅠ
//case2: c에는 자바에는 전역변수가 없다. R클래스는 static으로 변수가 만들어짐. 그래서 우리가 new안하고 씀.
//7. 이미지 업로드가 오래걸리기때문에 우선 파이어베이스스토리지에 먼저 업로드하자
FirebaseStorage storage = FirebaseStorage.getInstance();//파이어베이스에게 겟인스턴스
//참조위치명이 중복되지 않도록 날짜를 이용
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String fileName = "img_"+ sdf.format(new Date());
StorageReference imgRef = storage.getReference("profileImage/"+fileName);
//슬래쉬로 폴더명인걸 눈치챈다. 최상위말고 폴더를 만들어서 저장하자.
//8.이미지업로드
imgRef.putFile(imgUri).addOnSuccessListener(new OnSuccessListener() {
//너한테 파일을 넣어줄께. putFile(imgUri)
@Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
Toast.makeText(MainActivity.this, "성공", Toast.LENGTH_SHORT).show();
//근데 <https://firebasestorage.googleapis.com---------------?ae>
//이 주소를 저장해야함...
}
이제 Studio에서 다운로드URL을 알아내서 DB에 저장한다. (다운로드 url과 닉네임정보를 DB에 저장하자!)
step5. Studio에서 다운로드URL을 알아내자
void saveData(){
//5. 서버에 연동하고 저장하는 함수를 만들자.
//일단 이미지를 선택하지 않으면 ..! 채팅 불가
if(imgUri==null) return; //이미지 없으면 리턴하자
//먼저 닉네임부터 저장하자
G.nickname = binding.et.getText().toString();
//case1: application 안에 액티비티가 있다. 여기에 멤버변수를 만들면 언제든 불러서 쓸수있다. 귀찮음 ㅠ
//case2: c에는 자바에는 전역변수가 없다. R클래스는 static으로 변수가 만들어짐. 그래서 우리가 new안하고 씀.
//6. 전역변수를 만들자! 꼼수로 G.class에!
//EditText에 있는 닉네임 가져와서 전역변수 역할의 G클래스에 대입- 하지만 이건 좋은 방법은 아니다.
//그냥 쓰는데는 문제없음 - 다른 좋은 별도 라이브러리 클래스가 잇음
//7. 이미지 업로드가 오래걸리기때문에 우선 파이어베이스스토리지에 먼저 업로드하자
FirebaseStorage storage = FirebaseStorage.getInstance();//파이어베이스에게 겟인스턴스
//참조위치명이 중복되지 않도록 날짜를 이용
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String fileName = "img_"+ sdf.format(new Date());
StorageReference imgRef = storage.getReference("profileImage/"+fileName);
//슬래쉬로 폴더명인걸 눈치챈다
//8.이미지업로드
imgRef.putFile(imgUri).addOnSuccessListener(new OnSuccessListener() {
@Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
Toast.makeText(MainActivity.this, "성공", Toast.LENGTH_SHORT).show();
//근데 <https://---------------------------0afe>
//이 주소를 저장해야함...
//9. 여기까지 오면 업로드가 성공 되었으니...
//업로드된 파일의 [다운로드 Url주소]가 필요. 이 주소를 얻어오자
//경로를 저장하는게 아니라 다운로드url을 저장하는거다
imgRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener() {
@Override
public void onSuccess(Uri uri) {
//업로드가 되면..!(성공) 다운로드 url이 uri안에 들어가있다.
G.profileUrl= uri.toString();// 성공해서 얻어온 uri값은 G.profileUrl에 넣자
Toast.makeText(MainActivity.this, "프로필 이미지 저장 완료", Toast.LENGTH_SHORT).show();
//10. 서버의 Firestore DB에 닉네임과 이미지 url 저장
//app을 처음 실행할때만 사진과 닉네임을 입력하도록 디바이스에 영구적으로
//데이터를 저장하자 그중에서 가장 쉬운 SharedPreferences
FirebaseFirestore firestore = FirebaseFirestore.getInstance();
//'profiles'라는 이름의 컬렉션 참조객체를 소환하자
CollectionReference profileRef = firestore.collection("profiles"); //없으면 만들고 있으면 여기에 넣는다
//닉네임을 도큐먼트명으로 정하고, 필드값은 다운로드 url을 저장하는 방식으로 한다.
//나중 확장성을 고려해서.. 필드틑 맵으로!
Map<string, object=""> profile = new HashMap<>();
profile.put("profileUrl",G.profileUrl); //글로벌 안에 넣어둔 G.profileUrl을 넣자
profileRef.document(G.nickname).set(profile); //닉네임은 중복될리가 없다.
// profileRef.document(G.nickname) 이름만들고 profile에 세팅하자
//document안에 아무것도 안쓰면 랜덤하게 들어간다
//채팅하려면 내 정보를 디바이스에 저장시켜줘야한다. SharedPreferences
//11. 앱을 처음 실행할때만 닉네임과 사진을 입력하도록 하기위해 디바이스에 영구적으로 데이터를 저장하자 [sharedPreference로 저장하기]
//(파일명은 뭘로할까? , 모드은 언제나 프라이빗 - 외부에서 쓸수없도록)
SharedPreferences pref = getSharedPreferences("profile" , MODE_PRIVATE);//액티비티야 프리퍼런스 내놔, 이 앱에서만 쓸수있도록 MODE_PRIVATE
SharedPreferences.Editor editor = pref.edit();
//pref야 edit해줘
editor.putString("nickName",G.nickname);
editor.putString("profileUrl",G.profileUrl);
editor.commit(); //내부적으로 트렌젝션 상태이기 때문에 commmit으로 완료시켜야한다.
//여기는 내 디바이스에 저장하는 실행문이다.
//저장이 완료 되었으면 채팅 화면으로 이동하자...
Intent intent = new Intent(MainActivity.this, ChattingActivity.class);
startActivity(intent);
//사진결과를 받는게 아니다! 그냥 ChattingActivity.class로 넘겨주면 된다.
finish(); //채팅방을 끌때 이 프로필설정 화면이 안보이게한다.
}
});
}
});
}
</string,>
그런데, 이름과 이미지를 저장하고 채팅 액티비티를 끈다면? 메인이 안나온다. SharedPreferences에는 저장했지만 그걸 불러오는 작업은 아직 하지 않았다.
step6. SharedPreferences한 정보를 불러오자
디바이스에 저장되어있는 프로필정보를 가져오자.
프로필 정보는 SharedPreferences에 저장되어 있다.
public class MainActivity extends AppCompatActivity {
//1.뷰바인딩
ActivityMainBinding binding;
//프로필 uri를 알고있어야한다.
Uri imgUri = null; //자바는..! 코틀린은 자동 null아님
//15
Boolean isFirst = true; //처음 입장이니? 네
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
binding=ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); //우리로치면 렐러티브
binding.civ.setOnClickListener(view-> clickImage());
binding.btn.setOnClickListener(view-> clickBtn());
//12. 디바이스에 저장되어있는 로그인정보(profile)가 있는지 확인
//SharedPreference에 저장되어있는 닉네임, 프로필이미지가 있다면 읽어오라고 명령
loadData();
//14
if(G.nickname!=null){ //닉네임이 있다면 isFirst= false로 바꿔줘야한다.
binding.et.setText(G.nickname);
Glide.with(this).load(G.profileUrl).into(binding.civ); //글라이드에서도 이미지 가져오기
isFirst= false; //닉네임 저장된게 있으면 isFirst는 false이다.
//그런데 ! 입장을 이미 했던 상황이라면??
//case 1: 입장 처음이라면 데이터를 저장한다.
//case 2: 이미 입장한적 있다면? 데이터를 불러온다.
}
}
void loadData(){ //13. 로드데이터의 함수를 만든다
SharedPreferences pref = getSharedPreferences("profile",MODE_PRIVATE);
G.nickname = pref.getString("nickName",null);
G.profileUrl = pref.getString("profileUrl",null);
//저장된게 없으면 null로 줘
}
void clickBtn(){
//4. 입장을 시작할때 프로필 이미지를 Firebase스토리지에 업로드하자
//채팅화면 가기전에 프로필이미지와 닉네임을 서버에 저장해야한다.
if(isFirst){
saveData(); //saveData(); 라는 함수를 만들자
}else{ //16
startActivity(new Intent(this, ChattingActivity.class));
}
}//clickBtn 입장버튼
처음 입장이면 데이터를 저장하고, 아니라면 그 화면을 실행해라(이미저장되어있으니까)
step7. 채팅화면을 설계하자
리사이클러뷰
<?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:orientation="vertical"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ChattingActivity">
<!-- stackFromEnd 스크롤을 항상 밑에 둔다 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:stackFromEnd="true"
android:layout_above="@+id/lay"/>
<LinearLayout
android:id="@+id/lay"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="4dp"
android:background="#E4B1EA">
<EditText
android:id="@+id/et"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:hint="message"
android:inputType="textMultiLine"
android:maxLines="3"
android:background="@drawable/bg_edit"/>
<!-- 최대 3줄까지만 커진다. 3줄이상쓸수는 있다 -->
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="#EA60A7"
android:text="send"
android:layout_marginLeft="8dp"
android:layout_gravity="center"/>
</LinearLayout>
</RelativeLayout>
내채팅방
<?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="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:padding="16dp"
tools:viewBindingIgnore="true">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/civ"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ms18"
android:layout_alignParentRight="true"/>
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="nick name"
android:textColor="@color/black"
android:layout_toLeftOf="@+id/civ"
android:layout_marginRight="16dp"/>
<TextView
android:id="@+id/msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="this is message"
android:maxWidth="250dp"
android:layout_alignRight="@+id/tv_name"
android:layout_below="@+id/tv_name"
android:background="@drawable/bg_mymsgbox"
android:padding="12dp"/>
<!-- 맥스라인을 하면 안되지-->
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="13:42"
android:textSize="12sp"
android:layout_toLeftOf="@+id/msg"
android:layout_alignBottom="@id/msg"
android:layout_marginRight="8dp"/>
</RelativeLayout>
상대방채팅방
<?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="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:padding="16dp"
tools:viewBindingIgnore="true">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/civ"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ms17"
/>
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="nick name"
android:textColor="@color/black"
android:layout_toRightOf="@+id/civ"
android:layout_marginLeft="16dp"/>
<TextView
android:id="@+id/msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="this is message"
android:maxWidth="250dp"
android:layout_alignLeft="@+id/tv_name"
android:layout_below="@+id/tv_name"
android:background="@drawable/bg_othermsgbox"
android:padding="12dp"/>
<!-- 맥스라인을 하면 안되지-->
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="13:42"
android:textSize="12sp"
android:layout_toRightOf="@+id/msg"
android:layout_alignBottom="@id/msg"
android:layout_marginLeft="8dp"/>
</RelativeLayout>
public class ChattingActivity extends AppCompatActivity {
ActivityChattingBinding binding;
String chatName = "앱개발자 취준생 준비방";
//send버튼 눌렀을때 채팅 데이터가 남아야한다(프로필정보, 메세지)
FirebaseFirestore firestore; //아예 찾아두자 많이쓸꺼니까
CollectionReference chatRef;
//왜 멤버변수로 만들었나? send뿐만 아니라 값을 불러올때도 쓰니까
//아예 찾아둔다.
//시작할때 채팅방 하나를 잡을꺼니까
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_chatting);
binding = ActivityChattingBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//17. 채팅방이름
getSupportActionBar().setTitle(chatName);
getSupportActionBar().setSubtitle("상대방 이름"); //액션바 나중에 내가 만들어보기
//20.FirebaseFirestore 관리객체 및 참조객체 소환
firestore=FirebaseFirestore.getInstance();
chatRef=firestore.collection(chatName); //레퍼런스에는 채팅방 이름을 준다.
//18. 이제 메세지입력하고 SEND했을때 채팅메세지들을 저장해보기 채팅방명 (컬렉션) - 시간 (도큐먼트) - 채팅정보 (사진,닉네임, 메세지, 시간 - 필드값)
//샌드버튼 누르면 파이어베이스에 저장하자
binding.btn.setOnClickListener(view->clickSend());
}
void clickSend(){ //21.
//firebase DB에 저장할 데이터(사진,닉네임,시간, 메세지)
String nickName = G.nickname;
String message = binding.et.getText().toString();
String profileUrl = G.profileUrl;
//시간은 간단하게 만들자 [시:분]
Calendar calendar = Calendar.getInstance();
String time = calendar.get(Calendar.HOUR_OF_DAY)+":"+calendar.get(Calendar.MINUTE); //데이터에 저장할 변수를 지역변수로 만들어줬다. HOUR_OF_DAY : 24시간이다
//22. 필드값을 해쉬맵으로 만들지 말고 객체를 만들어서 객체를 통으로 넣으면 편하다. 리사이클러뷰 쓰기도 편하다.
//필드에 넣을 값들을 아예 MessageItem 객체로 만들어서 한방에 입력하자. 해쉬맵은 여기밖에 못쓰잖아
MessageItem item = new MessageItem(nickName,message,profileUrl,time);
//23. 채팅방이름으로 된 컬렉션에 채팅메세지들을 저장하기 (채팅방이 여러개 있을수도있으니까!)
//단 시간순으로 정렬되도록 도큐먼트에 이름은 현재날짜(1970년부터 카운트 된 ms)로 지정하자.
chatRef.document("MSG_"+System.currentTimeMillis()).set(item);
binding.et.setText(""); //다음 메세지 입력이 수월하도록 Edittext 글씨를 없애기
//소프트 키보드를 안보이도록 키보드 숨기기
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);// 스머프내놔 getSystemService
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),0); //소프트키보드에 대한 권한은 누가 갖고잇지? 그걸 토큰을 갖고있다한다. 토큰을 갖고있는 애한테 그걸 뺏어와야한다.
//0은 바로종료
}
step8. 리사이클러뷰를 만들자.
public class ChattingActivity extends AppCompatActivity {
ActivityChattingBinding binding;
//31.
MessageAdapter adapter;
//19.아예 찾아두자 많이쓸꺼니까
FirebaseFirestore firestore; //아예 찾아두자 많이쓸꺼니까
CollectionReference chatRef;
String chatName = "앱개발자 취준생 준비방";
//24. 리사이클러뷰에 대한 대량의 데이터 만들기
ArrayList<MessageItem> messageItems = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_chatting);
binding = ActivityChattingBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//17. 채팅방이름
getSupportActionBar().setTitle(chatName);
getSupportActionBar().setSubtitle("상대방 이름"); //액션바 나중에 내가 만들어보기
//32,
adapter=new MessageAdapter(this,messageItems);
binding.recycler.setAdapter(adapter);
//20.FirebaseFirestore 관리객체 및 참조객체 소환
firestore=FirebaseFirestore.getInstance();
chatRef=firestore.collection(chatName); //레퍼런스에는 채팅방 이름을 준다.
//25. 채팅방이름으로 된 컬렉션에 저장되어있는 데이터들을 읽어오기
//chatRef.get(); 그 순간만 가져온다. 새로추가된거는 못가져온다. 일회용
//이 chatRef가 변경될때마다 반응하는 리스너 추가 . 누가 글을 적던 반응한다
chatRef.addSnapshotListener(new EventListener<QuerySnapshot>() {
@Override
public void onEvent(@Nullable QuerySnapshot value, @Nullable FirebaseFirestoreException error) {
//변경된 도큐먼트만 찾아달라고 요청하자
List<DocumentChange> documentChanges = value.getDocumentChanges();
//처음시작할때가 문제이다. 어제쓴 메시지 10개가 있는데... 이걸 DocumentChange가 10개가 바뀌었다고 생각한다.
for(DocumentChange documentChange : documentChanges){
DocumentSnapshot snapshot=documentChange.getDocument();//변경된 문서내역 중에서 데이터를 촬영한 SnapShot 얻어오기
//이제 Document에 있는 필드값 가져오기
Map<String,Object> msg = snapshot.getData(); //물론 우린다 String이지만 못 읽으니까 Object로 쓰자
String name = msg.get("nickName").toString();
String message = msg.get("message").toString();
String profileUrl = msg.get("profileUrl").toString();
String time = msg.get("time").toString();
//읽어온 메세지를 리스트에 추가
messageItems.add(new MessageItem(name,message,profileUrl,time));
//아답터에게 추가 되었다고 공지해줘야한다. 화면 갱신을 위해 !!!
//26 아답터와 xml만들기
}//for문
Toast.makeText(ChattingActivity.this, ""+messageItems.size(), Toast.LENGTH_SHORT).show();
}
});
//18. 이제 메세지입력하고 SEND했을때 채팅메세지들을 저장해보기 채팅방명 (컬렉션) - 시간 (도큐먼트) - 채팅정보 (사진,닉네임, 메세지, 시간 - 필드값)
//센드버튼 누르면 파이어베이스에 저장하자
binding.btn.setOnClickListener(view->clickSend());
}
public class MessageItem {
//파이어베이스에서 사용하려면 반드시 public
public String nickName;
public String message;
public String profileUrl; //이게 식별자 이름이된다.
public String time;
//파이어베이스에서는 생성자를 둘 다 만들어줘야한다.
public MessageItem(String nickName, String message, String profileUrl, String time) {
this.nickName = nickName;
this.message = message;
this.profileUrl = profileUrl;
this.time = time;
}
public MessageItem() {
}
}
아답터를 만드는일이 까다롭다! 뷰홀더를 어디로 잡아야할지 헷갈린다.
(상대방 채팅방? 내 채팅방? 어디로 뷰홀더를 연결시켜야하나 애매하다.)
public class
MessageAdapter extends RecyclerView.Adapter<MessageAdapter.VH> {
Context context;
ArrayList<MessageItem> messageItems; //new안하기
final int TYPE_MY = 0; //타입상수, 값이 불변했으면 좋겠다
final int TYPE_OTHER = 1;
public MessageAdapter(Context context, ArrayList<MessageItem> messageItems) {
this.context = context;
this.messageItems = messageItems;
}
//29. 리사이클러뷰의 항목뷰가 경우에 따라 다른 모양으로 보여야할때 사용하는 콜백메소드
//이 메소드에서 해당 position 에 따른 식별값(ViewType번호)를 정하여 리턴하면
//그 값이 onCreateViewHolder의 두번째 파라미터에 전달된다.
//onCreateViewHolder() 메소드안에서 그 값에 따라 다른 xml문서를 inflate하면 됨.
@Override
public int getItemViewType(int position) {
if(messageItems.get(position).nickName.equals(G.nickname)){
return TYPE_MY; //맞으면 내꺼
}else {//틀리면 니꺼
return TYPE_OTHER;
}
}
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 저기의 viewType 내려온다
//자 이제 아이템뷰를 만들어야하는데..! 메세지 아이템의 닉네임이 내꺼면 내 데이터를 불러오고
//상대방 닉네임이면 상대방 데이터 가져오면 된다!
//int viewType을 이용하면 된다! 뷰가 경우에 따라 달라져야할때쓰는 callback : viewType
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//28.홀더를 만드는데 무엇을 만들지 모르겠어... viewType을 설정해준다. viewType은 내가 정하는것이다.
View itemview = null;
//30. viewType에 따라 xml을 inflate하자
if(viewType==TYPE_MY) itemview = LayoutInflater.from(context).inflate(R.layout.my_messagebox,parent,false);
else itemview=LayoutInflater.from(context).inflate(R.layout.other_messagebox,parent,false);
return new VH(itemview);
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
MessageItem item=messageItems.get(position);
holder.tvName.setText(item.nickName);
holder.tvMsg.setText(item.message);
holder.tvTime.setText(item.time);
Glide.with(context).load(item.profileUrl).into(holder.civ);
}
@Override
public int getItemCount() {
return messageItems.size();
}
class VH extends RecyclerView.ViewHolder{
//뷰바인딩 안하고 작업하기!!! -> 안쓰면 xml의 Root에 tools:viewBindingIgnore="true" 설정
CircleImageView civ;
TextView tvName;
TextView tvMsg;
TextView tvTime; //뷰홀더는 아이템뷰안에 있는 조그만 액자들을 홀더한다.
public VH(@NonNull View itemView) {
super(itemView);
civ=itemView.findViewById(R.id.civ);
tvName=itemView.findViewById(R.id.tv_name);
tvMsg=itemView.findViewById(R.id.msg);
tvTime=itemView.findViewById(R.id.tv_time);
}
//27
//메세지 타입에 따라 뷰가 다르기에 바인딩클래스를 고정하지 못함. 바인딩이 두종류이다.
//MyMessageboxBinding,OtherMessageboxBinding
//뷰 홀더를 2개 만들면 onBind할때 분기처러가 필요하므로 다른방법으로 시도해보자 : 뷰바인딩 안쓰고 써보기!
//안쓰는데 클래스가 있는게 싫어 xml의 Root에 tools:viewBindingIgnore="true"해주면 뷰바인딩 무시한다.
}
}
step9. 채팅액티비티에서 아답터연결하자
public class ChattingActivity extends AppCompatActivity {
ActivityChattingBinding binding;
//31.
MessageAdapter adapter;
//19.아예 찾아두자 많이쓸꺼니까
FirebaseFirestore firestore; //아예 찾아두자 많이쓸꺼니까
CollectionReference chatRef;
String chatName = "앱개발자 취준생 준비방";
//24. 리사이클러뷰에 대한 대량의 데이터 만들기
ArrayList<MessageItem> messageItems = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_chatting);
binding = ActivityChattingBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//17. 채팅방이름
getSupportActionBar().setTitle(chatName);
getSupportActionBar().setSubtitle("상대방 이름"); //액션바 나중에 내가 만들어보기
//32,
adapter=new MessageAdapter(this,messageItems);
binding.recycler.setAdapter(adapter);
//20.FirebaseFirestore 관리객체 및 참조객체 소환
firestore=FirebaseFirestore.getInstance();
chatRef=firestore.collection(chatName); //레퍼런스에는 채팅방 이름을 준다.
//25. 채팅방이름으로 된 컬렉션에 저장되어있는 데이터들을 읽어오기
//chatRef.get(); 그 순간만 가져온다. 새로추가된거는 못가져온다. 일회용
//이 chatRef가 변경될때마다 반응하는 리스너 추가 . 누가 글을 적던 반응한다
chatRef.addSnapshotListener(new EventListener<QuerySnapshot>() {
@Override
public void onEvent(@Nullable QuerySnapshot value, @Nullable FirebaseFirestoreException error) {
//변경된 도큐먼트만 찾아달라고 요청하자
List<DocumentChange> documentChanges = value.getDocumentChanges();
//처음시작할때가 문제이다. 어제쓴 메시지 10개가 있는데... 이걸 DocumentChange가 10개가 바뀌었다고 생각한다.
for(DocumentChange documentChange : documentChanges){
DocumentSnapshot snapshot=documentChange.getDocument();//변경된 문서내역 중에서 데이터를 촬영한 SnapShot 얻어오기
//이제 Document에 있는 필드값 가져오기
Map<String,Object> msg = snapshot.getData(); //물론 우린다 String이지만 못 읽으니까 Object로 쓰자
String name = msg.get("nickName").toString();
String message = msg.get("message").toString();
String profileUrl = msg.get("profileUrl").toString();
String time = msg.get("time").toString();
//읽어온 메세지를 리스트에 추가
messageItems.add(new MessageItem(name,message,profileUrl,time));
//아답터에게 추가 되었다고 공지해줘야한다. 화면 갱신을 위해 !!!
//26 아답터와 xml만들기
//33. 데이터셋체인지는 한꺼번에 바뀔때
adapter.notifyItemInserted(messageItems.size()-1); //아이템하나가 바뀔때는 notifyItemInserted
//34. 이대로면 리사이클러뷰가 안보이게된다. 글이 밑으로 계속 생기니까
//리사이클러뷰의 스크롤 위치가 가장 아래로 이동해야한다.
binding.recycler.scrollToPosition(messageItems.size()-1);
//35 메니페스트로 가서 수정 - 다시봐야함
}//for문
//Toast.makeText(ChattingActivity.this, ""+messageItems.size(), Toast.LENGTH_SHORT).show();
}
});
//18. 이제 메세지입력하고 SEND했을때 채팅메세지들을 저장해보기 채팅방명 (컬렉션) - 시간 (도큐먼트) - 채팅정보 (사진,닉네임, 메세지, 시간 - 필드값)
//센드버튼 누르면 파이어베이스에 저장하자
binding.btn.setOnClickListener(view->clickSend());
}
'Android Studio(Java)' 카테고리의 다른 글
Android Studio Retrofit (3) (0) | 2023.03.19 |
---|---|
Android Studio Retrofit (2) (0) | 2023.03.16 |
Android Studio Retrofit (1) (0) | 2023.03.16 |
Android Studio BackEnd (0) | 2023.03.10 |
Android Studio Fragment랑 FragmentActivity 차이점 (0) | 2023.03.06 |