https://github.com/lnjky/fashion_people
GitHub - lnjky/fashion_people: 패션 추천 어플
패션 추천 어플. Contribute to lnjky/fashion_people development by creating an account on GitHub.
github.com
코드 전체를 보려면 위에 링크를 통해 확인할 수 있습니다.
이를 설명하기 전에 해당 게시글과 연관되어 있기 때문에 이에 대해 알고 작성하면 좋다.
[안드로이드] 파이어스토어 게시글 작성 만들기
https://github.com/lnjky/fashion_people GitHub - lnjky/fashion_people: 패션 추천 어플 패션 추천 어플. Contribute to lnjky/fashion_people development by creating an account on GitHub. github.com 코드 전체를 보려면 위에 링크를 통해
loasd.tistory.com
[안드로이드] 작성글 리사이클러뷰로 불러오기
https://github.com/lnjky/fashion_people GitHub - lnjky/fashion_people: 패션 추천 어플 패션 추천 어플. Contribute to lnjky/fashion_people development by creating an account on GitHub. github.com 코드 전체를 보려면 위에 링크를 통해
loasd.tistory.com
우선 어떻게 구현할까 생각을 해보았는데 이는 게시글 작성과 거의 유사하다.
다른점이 있다면 저장 위치이다.
게시글의 경우 새로운 Collection인 post를 만들어서 랜덤한 documentID를 가진 document를 만들어서 생성해주었다.
그래서 댓글을 게시글에 작성하기 위해서 경로를 똑같이 하되 그 밑에 새로운 collection을 만들어주기로 했다.
그렇기때문에 XML의 경우 게시글 자세히보기와 같다.
이렇게 상단에는 작성했던 글의 제목, 내용이 나오게 하고 하단의 Edit Text를 배치하여 댓글을 작성할 수 있게 하였다.
또한 리사이클러뷰를 넣어서 작성한 댓글들을 볼 수 있게 하였다.
XML의 전체 내용을 보면 다음과 같다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".CommentsActivity">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar">
</include>
<ImageView
android:id="@+id/iv_comment_back"
android:layout_width="40dp"
android:layout_height="33dp"
android:background="@drawable/ic_baseline_arrow_back_24"
app:layout_constraintBottom_toBottomOf="@+id/toolbar"
app:layout_constraintEnd_toStartOf="@+id/tv_tool_title"
app:layout_constraintHorizontal_bias="0.064"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/toolbar"
app:layout_constraintVertical_bias="0.481">
</ImageView>
<TextView
android:id="@+id/tv_tool_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/koreanah1_r"
android:text=""
android:textColor="@color/white"
android:textSize="25dp"
android:hint="제목"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="@+id/toolbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/toolbar" />
<Button
android:id="@+id/btn_post_delete"
android:layout_width="54dp"
android:layout_height="31dp"
android:layout_marginEnd="20dp"
android:background="@drawable/edit_bg"
android:fontFamily="@font/koreanah1_r"
android:text="삭제"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="@+id/toolbar"
app:layout_constraintEnd_toEndOf="@+id/toolbar"
app:layout_constraintTop_toTopOf="@+id/toolbar"
app:layout_constraintVertical_bias="0.517" />
<TextView
android:id="@+id/tv_post_title"
android:layout_width="344dp"
android:layout_height="34dp"
android:gravity="center_vertical"
android:hint="제목을 입력하세요"
android:padding="5dp"
android:textColor="@color/black"
android:textSize="20dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintVertical_bias="0.0">
</TextView>
<TextView
android:layout_width="70dp"
android:layout_height="35dp"
android:background="#B388FF"
android:gravity="center"
android:text="제목"
android:textColor="@color/white"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/bar_nickname"
app:layout_constraintEnd_toStartOf="@+id/tv_post_title"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar">
</TextView>
<TextView
android:id="@+id/bar_nickname"
android:layout_width="match_parent"
android:layout_height="20dp"
android:background="#B388FF"
android:text="닉네임"
android:textColor="@color/white"
android:textSize="15dp"
app:layout_constraintBottom_toTopOf="@+id/tv_post_contents"
app:layout_constraintTop_toBottomOf="@+id/tv_post_title"
app:layout_constraintVertical_bias="0.0"
tools:layout_editor_absoluteX="0dp">
</TextView>
<TextView
android:id="@+id/bar_time"
android:layout_width="match_parent"
android:layout_height="20dp"
android:background="#B388FF"
android:text="시간"
android:textColor="@color/white"
android:textSize="15dp"
app:layout_constraintBottom_toTopOf="@+id/tv_post_contents"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bar_nickname"
app:layout_constraintVertical_bias="0.0">
</TextView>
<TextView
android:id="@+id/tv_post_contents"
android:layout_width="match_parent"
android:layout_height="220dp"
android:layout_marginTop="78dp"
android:background="@drawable/button_round"
android:ems="10"
android:hint="내용"
android:padding="10dp"
android:textColor="@color/black"
android:textSize="24dp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar">
</TextView>
<EditText
android:id="@+id/et_write_comments"
android:layout_width="381dp"
android:layout_height="40dp"
android:layout_marginTop="5dp"
android:background="@drawable/button_round"
android:ems="10"
android:hint=" 댓글을 입력하세요"
android:paddingLeft="5dp"
android:textSize="18dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_post_contents">
</EditText>
<ImageView
android:id="@+id/btn_write_comments"
android:layout_width="30dp"
android:layout_height="33dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="24dp"
android:background="@drawable/ic_baseline_create_24"
app:layout_constraintBottom_toBottomOf="@+id/comments_line"
app:layout_constraintEnd_toEndOf="@+id/et_write_comments">
</ImageView>
<TextView
android:id="@+id/comments_line"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#B388FF"
android:ems="5"
android:text=" 댓글"
android:textColor="@color/white"
android:textSize="18dp"
app:layout_constraintTop_toBottomOf="@+id/et_write_comments"></TextView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_comments"
android:layout_width="410dp"
android:layout_height="335dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/comments_line"
app:layout_constraintVertical_bias="0.0"
tools:layout_editor_absoluteX="0dp">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
여기서 제목, 내용, 닉네임 등의 정보들은 게시글 작성하기에서 리사이클러뷰의 어댑터의 OnClickListener를 통해 데이터가 Intent되게 하였다.
해당 내용은 상단의 게시글 작성 링크에서 확인 가능하다.
//글 작성자의 닉네임, 내용, uID, 제목
intent = getIntent();// 인텐트 받아오기
title = intent.getStringExtra("board_title"); //Adapter에서 받은 키값 연결
board_nickname = intent.getStringExtra("board_nickname");
contents = intent.getStringExtra("board_contents");
board_documentID = intent.getStringExtra("board_documentID");
board_timestamp = intent.getStringExtra("board_timestamp");
board_collectionID = intent.getStringExtra("board_collectionID");
이렇게 받아온 데이터들 중 제목, 작성시간, 닉네임 등은 setText()를 통해 배치해준다.
tv_tool_title.setText(contents);
tv_post_title.setText(contents);
tv_post_contents.setText(board_nickname);
bar_time.setText(" 작성 시간 : " + board_timestamp);
bar_nickname.setText(" 작성자 : " + title);
게시글 작성 코드는 다음과 같다.
//작성 이미지 클릭시 행동
@Override
public void onClick(View view) {
if(mAuth.getCurrentUser() != null) {
String commentId = mStore.collection(FirebaseID.post).document(board_collectionID).collection(FirebaseID.comments).getId();
Map<String, Object> data = new HashMap<>();
// data에 데이터 추가(삽입)
data.put(FirebaseID.documentId, mAuth.getCurrentUser().getUid());
data.put(FirebaseID.nickname, nickname); // 현재 사용중인 사용자의 닉네임
data.put(FirebaseID.time, FieldValue.serverTimestamp()); // 서버 시간
data.put(FirebaseID.comments, et_write_comments.getText().toString()); // 작성한 댓글 내용 입력
data.put(FirebaseID.timestamp, current_time);
mStore.collection(FirebaseID.post).document(board_collectionID).collection(FirebaseID.documentId)
.document(et_write_comments.getText().toString()).set(data, SetOptions.merge());
Log.d("board_collectionID2", board_collectionID);
}
et_write_comments.setText(null); // 댓글 작성 후 Edit Text 비우기
}
게시글 작성하기때 처럼 데이터들을 파이어스토어에 입력해준다.
다른점은 경로이다. 게시글 작성시에는 'post - 랜덤 documentID'의 경로에 저장되었지만 댓글의 경우
'post - 게시글 작성자의 documentID - documentId - 댓글내용' 으로 생성되게 하였다.
생각보다 복잡하게 경로를 설정해놔서 읽기 어렵긴 한데 이런점은 고쳐야 할 필요가 있어보인다.
파이어스토어에 저장된 경로는 다음과 같다.
이렇게 작성한 댓글은 게시글의 하위 collection에 들어가게 해주었다.
그리고 Edit Text 안에 작성했던 내용은 비워주었다.
et_write_comments.setText(null);
리사이클러뷰의 경우 리사이클러뷰를 만든 게시글이 있어 여기서 자세한 설명을 보고 이 게시글에서는 간단히 코드만 작성하도록 하겠다.
1) Comments < 모델 >
package com.example.styleplt.models;
import android.provider.ContactsContract;
import com.google.firebase.firestore.ServerTimestamp;
import java.util.Date;
public class Comments {
private String documentID;
private String nickname;
private String comments;
private String time;
private String collectionID;
@ServerTimestamp
private ContactsContract.Data data;
// Alt + Insert Consturctor
public Comments() {
}
public Comments(String documentID, String nickname, String comments, String time, String collectionID) {
this.documentID = documentID;
this.nickname = nickname;
this.comments = comments;
this.time = time;
this.collectionID = collectionID;
}
public String getDocumentID() {
return documentID;
}
public void setDocumentID(String documentID) {
this.documentID = documentID;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getComments() {
return comments;
}
public void setComments(String comments) {
this.comments = comments;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getCollectionID() {
return collectionID;
}
public void setCollectionID(String collectionID) {
this.collectionID = collectionID;
}
@Override
public String toString() {
return "Comments{" +
"documentID='" + documentID + '\'' +
", nickname='" + nickname + '\'' +
", comments='" + comments + '\'' +
", time='" + time + '\'' +
", collectionID='" + collectionID + '\'' +
'}';
}
}
2) Comments Adapter < 어댑터 >
package com.example.styleplt.adapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.styleplt.utility.FirebaseID;
import com.example.styleplt.R;
import com.example.styleplt.models.Comments;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore;
import java.util.List;
public class CommentsAdapter extends RecyclerView.Adapter<CommentsAdapter.CommentsViewHolder> {
private FirebaseFirestore mStore = FirebaseFirestore.getInstance();
private FirebaseAuth mAuth = FirebaseAuth.getInstance();
private List<Comments> datas;
// Alt + Insert Consturctor
public CommentsAdapter(List<Comments> datas) {
this.datas = datas;
}
@NonNull
@Override
public CommentsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new CommentsViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_comments, parent, false));
}
@Override
public void onBindViewHolder(@NonNull CommentsViewHolder holder, int position) {
Comments data = datas.get(position);
holder.comments.setText(data.getComments());
holder.nickname.setText(data.getNickname());
holder.time.setText(data.getTime());
holder.documentid.setText(data.getDocumentID());
}
@Override
public int getItemCount() {
return datas.size();
}
class CommentsViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView comments;
private TextView time;
private TextView nickname;
private TextView documentid;
private ImageView btn_comments_delete;
//생성자
public CommentsViewHolder(@NonNull View itemView) {
super(itemView);
comments = (TextView) itemView.findViewById(R.id.item_comments_comments);
time = (TextView) itemView.findViewById(R.id.item_comments_timestamp);
nickname = (TextView) itemView.findViewById(R.id.item_comments_nickname);
documentid = (TextView) itemView.findViewById(R.id.item_comments_documentid);
btn_comments_delete = itemView.findViewById(R.id.btn_comments_delete);
Log.d("commentsAdapter", "uid : " + documentid.getText().toString());
//Toast.makeText(comments.getContext(), "uid : " + documentid.getText().toString(), Toast.LENGTH_SHORT).show();
}
}
}
그리고 이전의 리사이클러뷰를 통해 댓글을 가져오듯이 아래의 코드를 OnStart() 안에 입력해주었다.
@Override
protected void onStart() {
super.onStart();
//현재 사용자의 uid, 닉네임 가져오기
if (mAuth.getCurrentUser() != null) {
mStore.collection(FirebaseID.user).document(mAuth.getCurrentUser().getUid())
.get()
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
@Override
public void onComplete(@NonNull Task<DocumentSnapshot> task) {
if(task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if(document.exists()) {
Log.d("TAG","Document is exists");
nickname = (String) document.getData().get(FirebaseID.nickname);
documentID = (String) document.getData().get(FirebaseID.documentId);
}
else
Log.d("TAG","Document is not exists");
}
}
});
}
//댓글 불러오기
mDatas = new ArrayList<>();
mStore.collection(FirebaseID.post).document(board_collectionID).collection(FirebaseID.documentId)
.orderBy(FirebaseID.time, Query.Direction.DESCENDING) // DESCENDING = 오름차순, ASCENDING = 내림차순 정렬
.addSnapshotListener(new EventListener<QuerySnapshot>() {
@Override
public void onEvent(@Nullable QuerySnapshot value, @Nullable FirebaseFirestoreException error) {
if (value != null) {
mDatas.clear();
for (DocumentSnapshot snap : value.getDocuments()) {
Map<String, Object> shot = snap.getData();
// 작성한 댓글 불러오기
String documentID = String.valueOf(shot.get((FirebaseID.documentId))); // 작성한 사용자의 uID
String nickname = String.valueOf(shot.get(FirebaseID.nickname)); // 작성한 사용자의 닉네임
String timestamp = String.valueOf(shot.get(FirebaseID.time)); // 작성한 시간
String comments = String.valueOf(shot.get(FirebaseID.comments)); // 작성한 댓글 내용
String collectionID = String.valueOf(shot.get(FirebaseID.collectionId));
String time = String.valueOf(shot.get(FirebaseID.timestamp));
Comments data = new Comments(documentID, nickname, comments, time, collectionID);
mDatas.add(data);
}
mAdapter = new CommentsAdapter(mDatas);
mCommentsRecyclerView.setAdapter(mAdapter);
}
}
});
}
이렇게 하면 댓글을 작성하면 바로 CommentsActivity의 하단에 댓글이 달리게 된다.
item의 형식에 맞게 댓글이 생성된 것을 확인할 수 있다.
큰 맥락은 앞서 말했다싶이 게시글 작성을 만드는 것과 같기때문에 댓글이 파이어스토어에 저장되는 경로를 설정하는것 말고는 크게 어려운 점은 없었다.
글이 길어지다보니 오류나 빠진점이 있을 수 있는데 이는 댓글을 통해 알려주시면 바로 수정하도록 하겠습니다.
'안드로이드' 카테고리의 다른 글
[안드로이드] 기상청 단기예보 API 사용하기 - 2 (0) | 2023.01.06 |
---|---|
[안드로이드] 기상청 단기예보 API 사용하기 - 1 (0) | 2023.01.06 |
[안드로이드] 별점 추가하기 ( XML ) (0) | 2023.01.05 |
[안드로이드] 작성글 리사이클러뷰로 불러오기 (0) | 2023.01.05 |
[안드로이드] 파이어스토어 게시글 작성 만들기 (0) | 2023.01.05 |