관리 메뉴

막내의 막무가내 프로그래밍 & 일상

[안드로이드] 파이어베이스 리얼타임데이터베이스, 스토리지 중간정리 본문

안드로이드/자바 & Previous

[안드로이드] 파이어베이스 리얼타임데이터베이스, 스토리지 중간정리

막무가내막내 2019. 5. 11. 19:46
728x90

 

 

프로젝트에 파이어베이스 리얼타임데이터베이스를 사용중인데 나중에 또 사용될떄 까먹을 수 있어서 정리포스팅을 하겠습니다.

 

저를 위한 정리이므로 타인분들이 얻을 정보는 거의 없으실거라고봅니다...

 

디비는 다음과 같이 되있습니다.

 

 

데이터베이스 참조 및 자식생성 예제

   DatabaseReference mRootDatabaseReference = FirebaseDatabase.getInstance().getReference(); 
    DatabaseReference mProfieDatabaseReference = mRootDatabaseReference.child("profile"); //profile이란 이름의 하위 데이터베이스
    DatabaseReference mNickNameDatabaseReference = mRootDatabaseReference.child("nickNameList"); //닉네임 담아놀 하위 데이터베이스

 

디비에 값 추가 예제

 //객체 값 데이터베이스에서 넣어줌  
   profile = new Profile(mEmail, mNickName, mSex, mAge, mTmpDownloadImageUri + "");
  //사용자토큰을 루트로 사용자 정보 저장 ==>객체저장
  mProfieDatabaseReference.child(mUid).setValue(profile);
 //닉네임리스트에 닉네임저장 ==>String값 저장
 mNickNameDatabaseReference.child(profile.getNickName()).setValue(mEmail);

 

 

디비 값 삭제 예

 mNickNameDatabaseReference.child(removeNickName).setValue(null); //child는 하위값이 없으면 자동으로 삭제되는점 이용

 

 

값 읽어오기 (중복의 여러가지면을 고려하다보니 코드가 좀 드럽게 되었습니다. 디비구조를 잘못짜서 그런거같지만...)

리스너를 사용해서 값을 읽어올 수 있고 dataSnapshot에서 값을 꺼내올 수 있다는것만 알면됩니다. 그리고 저는 계속해서 해당 디비의 하위값들이 변화가있을때마다 이 리스너가 호출되는것이 아닌 처음 앱을 실행하고 버튼을 눌렀을 때만 값을 읽어오기위해 addListenerForSingleValueEvent를 사용했습니다. 만약 수시로 해당디비의 하위값들이 변화를 감지하고 그떄마다 값을 불러오기 위해서는 addValueEventListener를 사용하면 됩니다.

mRootDatabaseReference.addListenerForSingleValueEvent(new ValueEventListener() {
                        @Override
                        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                            if(dataSnapshot.hasChild("nickNameList") || dataSnapshot.child("nickNameList").hasChild(tmpNickName)) {
                                if (!dataSnapshot.child("nickNameList").hasChild(tmpNickName) || dataSnapshot.child("nickNameList").child(tmpNickName).getValue().equals(mEmail)) { //사용가능닉네임
                                    Toast.makeText(getApplicationContext(), "사용가능한 닉네임입니다.", Toast.LENGTH_LONG).show();
                                    Log.d(TAG, "1");
                                    if ( dataSnapshot.child("nickNameList").hasChild(tmpNickName) && !dataSnapshot.child("nickNameList").child(tmpNickName).getValue().equals(mEmail)) { //원래랑 같은 닉네임 (지울필요 X)
                                        isHasRemovedNickName = false;
                                        Log.d(TAG, "2");
                                    } else if(dataSnapshot.child("nickNameList").hasChild(tmpNickName)){ //원래랑 다른닉네임 원래닉네임은 지워줘야함
                                        //removeNickName = (String) dataSnapshot.child(tmpNickName).getValue(); //지워야할닉네임

                                        removeNickName = (String) dataSnapshot.child("profile").child(mUid).child("nickName").getValue(); //기존에 갖고있던 닉네임( nickNameList에서 지워줘야함)
                                        isHasRemovedNickName = true; //지울닉네임 존재 (기존닉네임
                                        Log.d(TAG, "3");
                                    }else { //처음 닉네임 짓는경우
                                        if ( dataSnapshot.hasChild("profile") && dataSnapshot.child("profile").hasChild(mUid)){
                                            isHasRemovedNickName = true;
                                            removeNickName = (String) dataSnapshot.child("profile").child(mUid).child("nickName").getValue(); //기존에 갖고있던 닉네임( nickNameList에서 지워줘야함)
                                            Log.d(TAG, "4");
                                        }else {
                                            isHasRemovedNickName = false;
                                            Log.d(TAG, "5");
                                        }
                                    }
                                    isNickExisted1 = false;
                                } else { //중복된 닉네임 사용불가
                                    Toast.makeText(getApplicationContext(), "중복된 닉네임이 존재합니다.", Toast.LENGTH_LONG).show();
                                    isNickExisted1 = true;
                                    isHasRemovedNickName = false;
                                    Log.d(TAG, "6");
                                }
                            }else{
                                Toast.makeText(getApplicationContext(), "사용가능한 닉네임입니다.", Toast.LENGTH_LONG).show();
                                isNickExisted1 = false;
                                isHasRemovedNickName = false;
                                Log.d(TAG, "7");
                            }
                        }

                        @Override
                        public void onCancelled(@NonNull DatabaseError databaseError) {
                            Toast.makeText(getApplicationContext(), "통신오류", Toast.LENGTH_LONG).show();
                        }
                    });

                }
            }
        });

 

 

 

추가로 리사이클러뷰에서 사용할 모델을 디비에 저장해놓은 경우 해당 모델리스트들을 불러오는 코드입니다. getKey()를 사용하면 키값을 for문에 getChildren()를 사용하면 이건 Iterator인데 해당 디비의 자식값들을 모두 순차적으로 불러올 수 있습니다.  그래서 이 예시는 seoulStudy을 루트로한 리스너인데 예시를들면 getKey는 -LeX-cF1c5cCNS1d65cb 를 가져올 수 있고 getValue로는 제가 생성했던 모델인 StudyMessage클래스를 받아올 수가 있습니다.

값을 넣을때 child("profile).push().setValue(StuddyMessage) 이렇게 추가했는데 프로필 하위노드들이 타임스탬프값(해쉬)로 되있습니다.

처음에 StuddyMessage를 가져올려면 child(해쉬값)에 접근하고 getValue()해야하는데 Profile하위노드를 보면 타임스탬프값이라 처음에 제가 특정 게시물을 가져오기 까다롭습니다. 그래서 for문을 돌려서 dataSnapshot2에 처음부터 끝까지 dataSnapshot.getChildren() 즉, profile노드의 하위노드인 타임스탬프를 하나씩 참조하면서 타임스탬프 밑의 하위값들을 가져올 수 있는겁니다.

  mMessageList = new ArrayList<>();
        mMessageAdapter = new MessageAdapter(mMessageList, getApplicationContext());

        //아래구분선 세팅
        mMessageRecyclerView.addItemDecoration(new DividerItemDecoration(getApplicationContext(), DividerItemDecoration.VERTICAL));
        // 리사이클러뷰에 레이아웃 매니저와 어댑터를 설정한다.
        LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true); //레이아웃매니저 생성
        mMessageRecyclerView.setLayoutManager(layoutManager); ////만든 레이아웃매니저 객체를(설정을) 리사이클러 뷰에 설정해줌
        mMessageRecyclerView.setAdapter(mMessageAdapter); //어댑터 셋 ( 파이어베이스 어댑터는 액티비티 생명주기에 따라서 상태를 모니터링하게하고 멈추게하고 그런 코드를 작성하도록 되있다.==> 밑에 onStart()와 onStop에 구현해놨다)

        mSeoulDatabaseReference.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                for (DataSnapshot dataSnapshot2 : dataSnapshot.getChildren()) { //하위노드가 없을 떄까지 반복

                    StudyMessage studyMessage = dataSnapshot2.getValue(StudyMessage.class);
                    studyMessage.setId(dataSnapshot2.getKey());
                    // [START_EXCLUDE]
                    // Update RecyclerView
                    mMessageList.add(studyMessage);
                    mMessageAdapter.notifyItemInserted(mMessageList.size() - 1);
                }
            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {

            }
        });

 

 

리얼타임디비는 이정도까지하고  인증도 블로그에 쓰기는 좀 시간이 걸릴것같아 제 프로젝트소스를 보거나 파베 문서를 읽어보면 될 것 같습니다.


마지막으로는 파이어베이스 스토리지 입니다. 사진을 저장하는데 사용했습니다. 리얼타임디비에는 사진을 넣을 수 없기에 스토리지에 저장하고 저장된 URL을 받아서 Glide로 사진을 뿌려주는식으로 사용합니다.

 

먼저 디비구조 사진인데 basic은 제가 지정한 기본사진들이 들어있는 디렉토리고 profile은 프로필사진이 들어가는 디렉토리입니다. 그리고 나머지 사진들은 게시물 사진들인데 아직 디렉토리를 생성안한 상태입니다.

  private StorageReference mStorageRef; //파이어베이스 스토리지
  mStorageRef = FirebaseStorage.getInstance().getReference(); //스토리지
StorageReference mProfileRef = mStorageRef.child("profileImage").child(mUid); //프로필 스토리지 저장이름은 사용자 고유토큰과 스트링섞어서 만들었다.
//파이어베이스 스토리지에 업로드
                            Toast.makeText(ProfileActivity.this, "업로드중입니다. 잠시만 기다려주세요", Toast.LENGTH_SHORT).show();
                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
                            img.compress(Bitmap.CompressFormat.JPEG, 100, baos);
                            byte[] datas = baos.toByteArray();
                            UploadTask uploadTask = mProfileRef.putBytes(datas);
                            Task<Uri> urlTask = uploadTask.continueWithTask(new Continuation<UploadTask.TaskSnapshot, Task<Uri>>() {
                                @Override
                                public Task<Uri> then(@NonNull Task<UploadTask.TaskSnapshot> task) throws Exception {
                                    if (!task.isSuccessful()) {
                                        throw task.getException();
                                    }

                                    // Continue with the task to get the download URL
                                    return mProfileRef.getDownloadUrl();
                                }
                            }).addOnCompleteListener(new OnCompleteListener<Uri>() {
                                @Override
                                public void onComplete(@NonNull Task<Uri> task) {
                                    if (task.isSuccessful()) {
                                        mDownloadImageUri = task.getResult();
                                        Log.d(TAG + "DOWN", mDownloadImageUri + "");
                                        //디비에넣기전 이전 아이디는 디비에서삭제
                                        if (isHasRemovedNickName) {
                                            mNickNameDatabaseReference.child(removeNickName).setValue(null); //child는 하위값이 없으면 자동으로 삭제되는점 이용
                                        }
                                        //값 데이터베이스에서 넣어줌
                                        profile = new Profile(mEmail, mNickName, mSex, mAge, mDownloadImageUri + "");
                                        //사용자토큰을 루트로 사용자 정보 저장
                                        mProfieDatabaseReference.child(mUid).setValue(profile);
                                        //닉네임리스트에 닉네임저장
                                        mNickNameDatabaseReference.child(profile.getNickName()).setValue(mEmail);
                                        //SharedPReference에도 저장해줌 (쉽게 갖다쓰기위해)
                                        saveProfileSharedPreferences(profile);
                                        Intent intent = new Intent(ProfileActivity.this, MainActivity.class);
                                        startActivity(intent);
                                        loadingEnd();//로딩종료
                                    } else {
                                        // Handle failures
                                        Toast.makeText(ProfileActivity.this, "이미지 업로드에 실패했습니다.", Toast.LENGTH_SHORT).show();
                                        loadingEnd();//로딩종료
                                    }
                                }
                            });
                        }

업로드하고 task가 성공적으로 마치면 사진의 업로드된 URL을 받아오게 짯습니다. 그 URL로 사진을 띄워줘야하기떄문입니다. 그리고 이 URL은 리얼타임디비 프로필노드쪽에 저장합니다. 그리고 이 역시 리얼타임디비와 마찬가지로 리스너를 사용해야합니다.

 

이상 포스팅마치겠습니다.

저를 위한 정리라 타인분들이 보시기에는 이해가안가시는게 많을 수 있으니 양해부탁드립니다.

 

댓글과 공감은 큰 힘이됩니다.!

728x90
Comments