안드로이드

[안드로이드] 파이어베이스로 피드 만들기 - 2

loasd 2023. 1. 11. 00:19
반응형

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://loasd.tistory.com/79

 

[안드로이드] 파이어베이스로 피드만들기 - 1

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


UploadActivity를 만들어보도록 하자.

우선 업로드하기 위해 입력해야 할정보는 사진과 코멘트 2개이다.

아이템을 이렇게 만들었기 때문에 닉네임, 시간은 따로 입력이 필요하진 않고 작성할 코멘트와 사진만 직접 입력받으면 된다.

우선 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=".UploadActivity">

    <include
        android:id="@+id/toolbar"
        layout="@layout/toolbar">

    </include>

    <ImageView
        android:id="@+id/iv_upload_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_post_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_post_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"
        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_upload"
        android:layout_width="54dp"
        android:layout_height="31dp"
        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_constraintHorizontal_bias="0.74"
        app:layout_constraintStart_toEndOf="@+id/tv_post_title"
        app:layout_constraintTop_toTopOf="@+id/toolbar" />

    <Button
        android:id="@+id/btn_upload_gallery"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:background="#eeeeee"
        android:text="갤러리"
        app:layout_constraintEnd_toEndOf="@+id/et_upload_contents"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="@+id/et_upload_contents"
        app:layout_constraintTop_toBottomOf="@+id/et_upload_contents">

    </Button>

    <ImageView
        android:id="@+id/iv_upload_image"
        android:layout_width="match_parent"
        android:layout_height="325dp"
        android:scaleType="centerInside"
        android:src="@drawable/add_image_512"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar">

    </ImageView>

    <EditText
        android:id="@+id/et_upload_contents"
        android:layout_width="400dp"
        android:layout_height="100dp"
        android:background="@drawable/button_round"
        android:hint="내용을 입력하세요"
        android:padding="5dp"
        android:textSize="20dp"
        android:gravity="left"
        android:textColor="@color/black"
        app:layout_constraintEnd_toEndOf="@+id/iv_upload_image"
        app:layout_constraintStart_toStartOf="@+id/iv_upload_image"
        app:layout_constraintTop_toBottomOf="@+id/iv_upload_image">

    </EditText>

</androidx.constraintlayout.widget.ConstraintLayout>

 

그리고 액티비티 전문은 이렇다.

package com.example.styleplt;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;

import com.example.styleplt.utility.FirebaseID;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FieldValue;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.SetOptions;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference;
import com.google.firebase.storage.UploadTask;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class UploadActivity extends AppCompatActivity {

    private ImageView iv_upload_back, iv_upload_image;
    private Button btn_upload, btn_upload_gallery;
    private EditText et_upload_contents;

    final int GET_GALLERY_IMAGE = 200;

    private FirebaseAuth mAuth = FirebaseAuth.getInstance();
    private FirebaseFirestore mStore = FirebaseFirestore.getInstance();
    private FirebaseStorage mStorage = FirebaseStorage.getInstance();

    private String nickname, documentId;

    private Uri imageUri;
    private String pathUri;
    private String TAG = "IMAGE";

    String uploadID = mStore.collection(FirebaseID.upload).document().getId();
    private StorageReference storageRef = mStorage.getReference();

    long now = System.currentTimeMillis();
    java.util.Date mDate = new Date(now);
    // 날짜, 시간의 형식 설정
    SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy.MM.dd hh:mm:ss");
    String current_time = simpleDateFormat1.format(mDate);

    private UploadTask uploadTask = null; // 파일 업로드하는 객체
    private String imageFileName = "IMAGE_" + documentId + "_" + uploadID + "_.png"; // 파일명
    private String url;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_upload);

        iv_upload_back = findViewById(R.id.iv_upload_back);
        btn_upload = findViewById(R.id.btn_upload);

        iv_upload_image = findViewById(R.id.iv_upload_image);
        btn_upload_gallery = findViewById(R.id.btn_upload_gallery);
        et_upload_contents = findViewById(R.id.et_upload_contents);


        iv_upload_back.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                setResult(RESULT_OK, intent);
                finish();
            }
        });

        btn_upload_gallery.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.setType("image/*");
                intent.setAction(Intent.ACTION_GET_CONTENT);
                startActivityResult.launch(intent);
            }
        });

        iv_upload_image.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.setType("image/*");
                intent.setAction(Intent.ACTION_GET_CONTENT);
                startActivityResult.launch(intent);
            }
        });

        // 현재 사용자의 데이터 가져오기
        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");
                            }
                        }
                    });
        }

        btn_upload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //사진을 스토리지에 올리는 코드
                // 이미지 파일 경로 지정 (/item/사용자 documentId / IAMGE_DOCUMENTID_UPLOADID_.png)
                storageRef = mStorage.getReference().child("image").child(documentId).child(imageFileName);
                uploadTask = storageRef.putFile(imageUri); // 업로드할 파일과 업로드할 위치 설정
                //파일 업로드 시작
                uploadTask.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                    @Override
                    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                        //업로드 성공 시 이미지를 올린 uri 가져오기
                        Log.d(TAG, "onSuccess: upload");
                        downloadUri(); // 업로드 성공 시 업로드한 파일 Uri 다운받기
                        storageRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
                            @Override
                            public void onSuccess(Uri uri) {
                                url = uri.toString();
                                Log.d("uri : ", uri.toString());

                                //작성한 글, 닉네임 등을 파이어스토어의 upload 컬렉션에 올리는 코드
                                Map<String, Object> data = new HashMap<>();
                                data.put(FirebaseID.documentId, mAuth.getCurrentUser().getUid());
                                data.put(FirebaseID.nickname, nickname);
                                data.put(FirebaseID.contents, et_upload_contents.getText().toString());
                                data.put(FirebaseID.collectionId, uploadID);
                                data.put(FirebaseID.image, imageUri);
                                data.put(FirebaseID.time, FieldValue.serverTimestamp());
                                data.put(FirebaseID.timestamp, current_time);
                                data.put("TOTAL_SCORE", 0);
                                data.put(FirebaseID.ratingcount, 1);
                                data.put(FirebaseID.rating, 0.0F);
                                data.put("url", url);
                                mStore.collection(FirebaseID.upload).document(uploadID).set(data, SetOptions.merge());
                                finish();
                            }
                        }).addOnFailureListener(new OnFailureListener() {
                            @Override
                            public void onFailure(@NonNull Exception e) {

                            }
                        });
                    }
                }).addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                      //업로드 실패 시 동작
                        Log.d(TAG, "onFailure: upload");
                    }
                });

            }
        });


    }

    // 클릭시 갤러리로 이동하는 구문
    ActivityResultLauncher<Intent> startActivityResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            if ( result.getResultCode() == RESULT_OK && result.getData() != null) {
                imageUri = result.getData().getData();
                try {
                    Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri);
                    iv_upload_image.setImageBitmap(bitmap);
                }
                catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    });

    // 지정한 경로(reference)에 대한 uri 을 다운로드하는 method
    // uri를 통해 이미지를 불러올 수 있음
    void downloadUri() {
        storageRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
            @Override
            public void onSuccess(Uri uri) {
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.d(TAG, "onFailure: download");
            }
        });
    }
}

위에서부터 차근차근 보도록 하자.

 

자주쓰는 뒤로가기 버튼이다.

        iv_upload_back.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                setResult(RESULT_OK, intent);
                finish();
            }
        });

 

그리고 갤러리나 중앙의 이미지뷰를 클릭할시 갤러리가 열리게 하였다.

        btn_upload_gallery.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.setType("image/*");
                intent.setAction(Intent.ACTION_GET_CONTENT);
                startActivityResult.launch(intent);
            }
        });

        iv_upload_image.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.setType("image/*");
                intent.setAction(Intent.ACTION_GET_CONTENT);
                startActivityResult.launch(intent);
            }
        });

그리고 파이어스토어를 사용할 때 저장할 때 구분을 위해 현재 사용자의 데이터를 가져온다.

회원가입시 입력했던 데이터들중 닉네임과 documentId를 가져왔다.

        // 현재 사용자의 데이터 가져오기
        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");
                            }
                        }
                    });
        }

 

그리고 가장 핵심인 이미지 업로드이다.

우선 샘플코드를 보도록 하자.

Uri file = Uri.fromFile(new File("path/to/images/rivers.jpg"));
StorageReference riversRef = storageRef.child("images/"+file.getLastPathSegment());
uploadTask = riversRef.putFile(file);

// Register observers to listen for when the download is done or if it fails
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, etc.
        // ...
    }
});

파이어베이스에서 제공해주는 샘플코드이다.

putFile()에 원하는 이미지를 넣어주고 onFailure에는 업로드에 실패했을 때, onSuccess에는 업로드를 성공했을 때의 동작을 입력해준다.

여기서 스토리지에 업로드 -> 업로드한 사진의 Uri를 가져오고 이 Uri와 현재 사용자의 데이터를 파이어스토어에 저장해주게 하였다.

그리고 저장한 데이터를 리사이클러뷰를 통해 띄워주는 방식으로 피드를 구성하였다.

 

스토리지 저장 -> (성공시) 저장한 이미지의 Uri + 사용자 데이터 파이어스토어에 저장

btn_upload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //사진을 스토리지에 올리는 코드
                // 이미지 파일 경로 지정 (/item/사용자 documentId / IAMGE_DOCUMENTID_UPLOADID_.png)
                storageRef = mStorage.getReference().child("image").child(documentId).child(imageFileName);
                uploadTask = storageRef.putFile(imageUri); // 업로드할 파일과 업로드할 위치 설정
                //파일 업로드 시작
                uploadTask.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                    @Override
                    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                        //업로드 성공 시 이미지를 올린 url 가져오기
                        Log.d(TAG, "onSuccess: upload");
                        downloadUri(); // 업로드 성공 시 업로드한 파일 Uri 다운받기
                        storageRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
                            @Override
                            public void onSuccess(Uri uri) {
                                url = uri.toString();
                                Log.d("uri : ", uri.toString());

                                //작성한 글, 닉네임 등을 파이어스토어의 upload 컬렉션에 올리는 코드
                                Map<String, Object> data = new HashMap<>();
                                data.put(FirebaseID.documentId, mAuth.getCurrentUser().getUid());
                                data.put(FirebaseID.nickname, nickname);
                                data.put(FirebaseID.contents, et_upload_contents.getText().toString());
                                data.put(FirebaseID.collectionId, uploadID);
                                data.put(FirebaseID.image, imageUri);
                                data.put(FirebaseID.time, FieldValue.serverTimestamp());
                                data.put(FirebaseID.timestamp, current_time);
                                data.put("TOTAL_SCORE", 0);
                                data.put(FirebaseID.ratingcount, 1);
                                data.put(FirebaseID.rating, 0.0F);
                                data.put("url", url);
                                mStore.collection(FirebaseID.upload).document(uploadID).set(data, SetOptions.merge());
                                finish();
                            }
                        }).addOnFailureListener(new OnFailureListener() {
                            @Override
                            public void onFailure(@NonNull Exception e) {

                            }
                        });
                    }
                }).addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                      //업로드 실패 시 동작
                        Log.d(TAG, "onFailure: upload");
                    }
                });

            }
        });

이렇게 onSuccess 안에 코드를 추가해줌으로써 스토리지에도 사진을 저장하고 파이어스토어에도 저장하게 되었다.

 

추가로 이 코드는 별점주기에서 사용하기 위해 초기값을 설정해주었다.

초기의 별점을 0.0F로 설정해주어 별점을 아직 못받았을 경우 표시되지 않게 하였다.

                                data.put("TOTAL_SCORE", 0);
                                data.put(FirebaseID.ratingcount, 1);
                                data.put(FirebaseID.rating, 0.0F);

 

이렇게까지 하면 사진 올리기는 완성이다.

아래는 실제로 내가 어플을 실행한 사진이다.

이렇게 대충 사진을 클릭해서 사진을 올리면 이렇게 뜬다.

그리고 내용을 입력하고 저장을 누르면

이렇게 스토리지에 저장이 되고

스토어에도 저장이 되는 것을 볼 수 있다.

이렇게 저장이 되면 리사이클러뷰를 제대로 연결해주었다면

이렇게 사진이 올라간 것을 확인할 수 있다.

아래는 사진이 커서 별점이 안나왔는데 초기에는 0점이므로 회색으로 표시된다.

 

다음글에서는 아이템에 별점부여하기와 찜하기를 설명하도록 하겠다.

반응형