ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Firestore에 데이터 추가하기 (안드로이드)
    카테고리 없음 2020. 12. 18. 17:52

    (안드로이드 스튜디오 & 코틀린 기준 설명)

    (이번 편에서는 CRUD 중 Create 기능에만 집중하여 설명합니다.)

     

    일단 firebase 프로젝트를 만들고, 앱등록을 하고, 안드로이드 스튜디오와 구글계정 연동 작업도 진행해야 한다.

    여기까지는 검색하면 많이 나오는 부분이니 생략.

    여기까지는 각자 알아서 완료하고 오세요...

     

    이제 firestore를 사용 가능하도록 설정한다. 필자는 아래의 링크를 참조하였다.

    [Firebase] Cloud Firestore 데이터 생성 방법 :: Copy Coding (tistory.com)

     

    프로덕션 모드와 테스트 모드에 대해서 얘기하자면, 프로덕션 모드로 할 경우, Firestore 하나를 쓰기 위해 Authentication 까지 공부해야 하는 사태가 일어난다. 실제로 서비스 할 앱을 만들기 위해서는 Authentication을 공부해서 프로덕션 모드를 사용하는 것이 맞지만, 지금은 Firestore를 알아가는 시간이니까 테스트 모드로 할 것을 적극 권장한다. 이미 프로덕션 모드로 시작해버렸다면, 아래처럼 규칙을 바꿔주면 된다.

    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /{document=**} {
          allow read, write: if
              request.time < timestamp.date(연도, 월, 일);
        }
      }
    }

     

    우측 상단에 보면 '문서로 이동'이라는 글씨가 있는데, 누르면 firebase 사용 방법을 설명한 문서를 볼 수 있다.

    여기서 Android 시작하기(영어로는 Get started for Android)를 누르면 따라할 수 있는 가이드라인을,

    API 참조를 누르면 패키지, 클래스 별로 설명된 문서를 볼 수 있다.

    제대로 사용하려면 둘 다 봐야한다. (개인적으로 firebase의 가이드문서는 불친절한 편이라고 생각한다. 하나의 기능에 대한 설명이 여기저기 흩어져있어 드래곤볼 마냥 모아야 하나의 기능이 완성된다. 그냥 매뉴얼을 순서대로 따라해서는 무슨 일이 일어나는 것인지 알기 힘들다. 추천하는 방법은, 원하는 기능을 공식 문서에서 찾기 보다는 일단 구글링을 하고, 구글링을 통해 얻은 키워드를 문서에서 찾아보는 것이 좋다.)

     

    일단 처음 시작할 때는 뭐가 뭔지 잘 모르기 때문에 Android 시작하기를 들어간다.

    우리가 사용할 것은 Firestore이니, 좌측에서 Cloud Firestore를 찾아 들어간다.

    https://firebase.google.com/docs/firestore/quickstart#set_up_your_development_environment

    (위의 링크는 한글문서의 코드와 영어문서의 코드가 다르기 때문에 꼭 영어 버전으로 확인해야된다.)

    여기에서 dependency를 추가하라는 것을 추가해줘야 한다.

    앱 등록을 할 때 추가했으니 됐다고 생각할지 모르겠지만, 앱 등록할 때 추가한 것은 firebase가 가진 많은 기능 중 어느 기능을 사용하든지 추가해야 하는 공통 항목인 것이고, firestore를 사용하고 싶을 때는 firestore만의 dependency를 또 추가해줘야 한다.

     

    일단 무작정 데이터베이스에 데이터를 넣고 보기 전에 아래의 링크는 꼭 정독하길 권한다.

    firebase.google.com/docs/firestore/data-model

     

    최상위는 컬렉션이고, 컬렉션은 하위에 문서들을 가질 수 있고, 문서는 하위에 컬렉션들 또는 필드들 (컬렉션과 필드를 동시에 가지는 것도 가능)을 가질 수 있다는 것만 알면 사용하는데는 문제 없긴 하다만, 위의 링크를 정독하면 Firestore로 데이터베이스를 설계하는데 큰 도움이 될 것이다.

     

    일단 다시 콘솔로 돌아가서 프로젝트 개요 -> Cloud Firestore에 들어가자. 추가한 데이터가 없다면 아래와 같은 화면이 나올 것이다.

     

    '+ 컬렉션 시작'을 누르고 아이디를 입력하면 컬렉션을 추가할 수 있다.

    추가한 뒤 확인을 누르면 문서를 추가할 수 있다. 자동ID라고 된 것을 누르면 직접 문서 ID를 지정하지 않아도 자동으로 생성된다. 문서의 경우는 자동ID 생성이 가능하다.

    문서 하위에는 대략 이런 것들을 추가할 수 있다.

    하나씩 눌러보면서 직접 데이터를 추가해보면 대충 어떤 것인지 알 수 있다. 자세한 설명은 생략한다.

     

    이렇게 문서 하위에는 필드와 컬렉션 모두 추가할 수 있다.

     

    이런 식으로 가지고 놀다보면 어떤 것이 가능하고 어떤 것이 불가능한지 알게 될 것이다.

    추가한 데이터는 삭제 가능하니 마음껏 추가해도 된다.

     

    저 콘솔 화면은 관리자만 들어갈 수 있는 창이니 일반 사용자들은 저런 방법으로는 데이터를 추가할 수 없다.

    그 다음은 저렇게 콘솔창에서 직접 데이터를 추가하는 것 말고 안드로이드를 통해 추가하는 방법을 알아보자.

    firebase.google.com/docs/firestore/manage-data/add-data

     

    일단은 처음이니까 트랜잭션 같은 것은 모두 제외하고 데이터 추가 자체에만 집중해서 설명하겠다.

     

    https://firebase.google.com/docs/firestore/manage-data/add-data#set_a_document

    첫 번째 예제 코드. 일단 따라해보자.

    val city = hashMapOf(
            "name" to "Los Angeles",
            "state" to "CA",
            "country" to "USA"
    )
    
    db.collection("cities").document("LA")
            .set(city)
            .addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully written!") }
            .addOnFailureListener { e -> Log.w(TAG, "Error writing document", e) }

    참고로 문서의 예제 코드에 나오는 정체불명의 'db' 라는 변수는 바로 이 녀석을 말하는 것이다.

    https://firebase.google.com/docs/firestore/quickstart#initialize

    (이런 식으로 여기저기에 흩어진 드래곤볼을 하나씩 모으는 것이다.)

     

    뭐가 뭔진 모르겠지만 일단 따라하고 보자.

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val db = Firebase.firestore
    
            val city = hashMapOf(
                    "name" to "Los Angeles",
                    "state" to "CA",
                    "country" to "USA"
            )
    
            db.collection("cities").document("LA")
                    .set(city)
                    .addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully written!") }
                    .addOnFailureListener { e -> Log.w(TAG, "Error writing document", e) }
        }
        
        companion object {
            const val TAG = "MyLog"
        }
    }

    이렇게 한 뒤 안드로이드 스튜디오에서 run을 하고 (가상디바이스가 아닌 실제 단말기에 연결해서 실행하는 경우, 데이터든 와이파이든 연결하도록 하자.) Firestore에 가보면 새 데이터가 추가되어있을 것이다. 혹시 안 되어있다면 Firestore 화면을 새로고침해보든지, 안드로이드 스튜디오를 껐다가 다시 켜본 뒤 실행해보자.

    무슨 일이 일어났는지 제대로 이해하려면 이제 공식 문서의 다른 부분을 볼 때가 됐다.

    오른쪽 상단에서 문서로 이동을 클릭한다. 이번엔 API Reference로 간다.

     

     

    이렇게 들어가면 안드로이드&코틀린에서 사용하는 firestore의 모든 클래스들을 볼 수 있다.

     

    이 부분을 분석해보자.

    val db = Firebase.firestore
    
    val city = hashMapOf(
            "name" to "Los Angeles",
            "state" to "CA",
            "country" to "USA"
    )
    
    db.collection("cities").document("LA")
            .set(city)
            .addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully written!") }
            .addOnFailureListener { e -> Log.w(TAG, "Error writing document", e) }

    https://firebase.google.com/docs/reference/kotlin/com/google/firebase/firestore/ktx/package-summary#firestore

    Firebase.firestore로 얻어온 변수 'db'가 FirebaseFirestore타입이라는 것에서 시작하자.

    FirebaseFirestore 클래스를 설명하는 문서에서 함수 collection()을 찾아보면,

    https://firebase.google.com/docs/reference/kotlin/com/google/firebase/firestore/FirebaseFirestore#collection

    CollectionReference를 반환하고,

    CollectionReference#document()라는 함수는 DocumentReference를 반환한다.

     

    여기서 헷갈리기 쉬운 것은, collection()이나 document() 함수를 호출하는 것 만으로는 해당 컬렉션이나 문서를 생성해주지는 않는다는 것이다. 즉, 얻어온 저 Reference들이 실제로는 존재하지 않는 컬렉션이나 문서를 참조하고 있을수도 있다.

     

    그런데 Firestore에 가 보면 cities라는 컬렉션과 LA라는 문서가 생겨있을 것이다. 그것은 어떻게 된 것이냐 하면...

    https://firebase.google.com/docs/reference/kotlin/com/google/firebase/firestore/DocumentReference#set

    바로 DocumentReference#set() 이라는 함수가 DocumentReference가 가리키는 문서가 존재하지 않으면 만들어주기 때문이다.

    (안타깝게도 아직 한글 문서는 제공되지 않는다...)

     

    초록색 밑줄로 표시한 부분을 보니 Map 또는 POJO를 추가할수 있는 모양이다.

     

    set(해쉬맵객체) 로 추가한 부분이 바로 저 주황색 네모 표시가 된 부분이다. 콘솔에서 '+ 필드 추가' 를 눌러 일일이 데이터를 추가했을 때와 동일한 결과가 나온다. 코드와 실행 결과를 비교해보면 해쉬맵의 key가 필드이름으로, value가 값으로 들어간 것을 알 수 있다. 또한 필드의 순서는 임의순서임을 알 수 있다.

    val city = hashMapOf(
            "name" to "Los Angeles",
            "state" to "CA",
            "country" to "USA"
    )

    Map 대신 POJO를 set하면 어떻게 되는지는 아래의 예제를 따라하면 알 수 있다.

    https://firebase.google.com/docs/firestore/manage-data/add-data#custom_objects

    이 부분은 직접 해 보면 알 수 있으니 설명은 생략한다.

     

    다른 예제 코드들도 다 위와 비슷한 방식으로 따라하면 된다. 직접 따라하고, 결과를 눈으로 보면 될테니 설명은 생략하겠다. 코드를 실행했을 때의 결과와 콘솔창에서 직접 조작할 때의 결과를 비교하면 이해가 될 것이다.

     

    그런데 아마 update()라는 함수가 있는 부분을 따라하면 실행이 잘 되지 않을 것이다.

    https://firebase.google.com/docs/firestore/manage-data/add-data#update-data

     

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val db = Firebase.firestore
    
            val washingtonRef = db.collection("cities").document("DC")
    
            // Set the "isCapital" field of the city 'DC'
            washingtonRef
                    .update("capital", true)
                    .addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully updated!") }
                    .addOnFailureListener { e -> Log.w(TAG, "Error updating document", e) }
        }
    
        companion object {
            const val TAG = "MyLog"
        }
    }

    에러 문구는 아마 아래와 같을 것이다:

    com.google.firebase.firestore.FirebaseFirestoreException: NOT_FOUND: No document to update: projects/fir-ex1-17d17/databases/(default)/documents/cities/DC

     

    역시 API Reference를 확인해보자.

    val db = Firebase.firestore
    
    val washingtonRef = db.collection("cities").document("DC")
    
    // Set the "isCapital" field of the city 'DC'
    washingtonRef
            .update("capital", true)
            .addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully updated!") }
            .addOnFailureListener { e -> Log.w(TAG, "Error updating document", e) }

    https://firebase.google.com/docs/reference/kotlin/com/google/firebase/firestore/DocumentReference#update

     

    설명 중에 이런 내용이 있다: If no document exists yet, the update will fail.

    (안타깝게도 아직 한글 설명은 제공되지 않는다.)

    즉, 위의 경우에 기존에 "DC"라는 문서를 만들어두지 않았다면 함수가 fail한다는 것이다.

    set과는 달리 update는 문서가 없다고 해서 직접 만들어주지 않는다.

    응? val washingonRef = db.collection("cities").document("DC") 를 했으니까 만든거 아니냐고?

     

    아래 update 부분을 지우고 위쪽 코드만 실행한 뒤 Firestore에 돌아가서 확인해보자. document() 함수는 DocumentReference를 얻어올 뿐, 새로 만들어주지는 않는다는 것을 알 수 있다. 아래의 document()함수 설명에도 입력한 documentPath의 문서가 없다면 만들어준다는 얘기는 없다.

    https://firebase.google.com/docs/reference/kotlin/com/google/firebase/firestore/CollectionReference#document_1

    그러니 문서가 이미 있는 것이 확실한 경우라면 update()를 바로 써도 되지만, 확실하지 않다면 일단 set()을 통해 문서를 만들어준 뒤 update를 하자.

     

    이렇게 보면 set을 쓰면 되는데 왜 update가 있는가 하는 생각이 들지도 모른다. 하지만 set으로는 할 수 없는 기능을 update로는 할 수 있는 경우가 있다.

    그 부분에 대해서는 추후에 따로 작성하도록 하겠다. (작성 후 링크를 남기겠습니다.)

     

    여기까지 하면 대충 감은 익혔을 것이다.

     

    다음 편에서는 실제 데이터베이스를 설계하고 데이터를 추가하는 코드를 작성해보는 연습을 해보자.

    댓글

Designed by Tistory.