기본 콘텐츠로 건너뛰기

[Android] Firebase Realtime Database 소개 - CRUD 예제

Firebase는 여러 막강한 기능들을 제공하고 있습니다.
너무도 막강해서 마치 마법같이 느껴지고
이러한 기능을 제공하는 구글에 감탄하게 됩니다.

그 첫 번째 감동으로 Realtime Database에 대해 간단히 소개합니다.

Firebase Realtime Database를 간단히 소개하자면
Cloud Database라고 할 수 있습니다.

Cloud Database이기 때문에 플랫폼 종속성이 없습니다.
실제 Firebase의 다른 모든 기능들이 Android, IOS, Web, Server 플랫폼에서 자유롭게 사용 가능하죠.

그럼 Cloud이기 때문에 온라인이 아닌 상황(인터넷 접속이 안되는 상황)에서는 사용이 불가하냐?

그것도 아닙니다.

데이터를 디스크에 유지하므로 오프라인일 때에도 문제없이 사용가능합니다.
이후 네트워크에 연결되면 데이터를 자동으로 동기화시켜주기 때문에 네트워크에 대한 걱정은 접으시기 바랍니다.

개인적으로 저 역시 Android/Firebase를 이제 입문하여 학습/적용하는 단계이기 때문에
자세한 내용은 알지 못합니다만

한가지 의문점이 생깁니다.
과연 SQLite를 사용한 내부 데이터 저장소를 앞으로도 사용할 필요가 있을까?
특히나 요즘 같이
빵빵한 네트워크 환경,
고성능의 디바이스,
무엇보다 빅데이터 분석을 통한 차별화된 서비스 제공

등을 고려하면 서버로의 데이터 취합은 필수로 보입니다.
그리고 빠른 트렌드 변화에 유연하게 대응하기 위해서는 실제 물리 서버 구축보다는 Cloud를 이용하는 것이 강점이 될 것이고요.

그런 점에서 구글에서 제공하는 Firebase Realtime Database는 개발자들에게 큰 무기가 되지 않나 싶습니다.

다만 한가지 염두에 두어야 할 점은 Realtime Database는 익히 알고 있는 관계형 데이터베이스(RDBMS)는 아니라는 것입니다.

Realtime Database에서는 데이터들이 아래와 같이 json 형식으로 저장되기 때문에
관계형 데이터베이스와는 다른 데이터 구조를 생각해야 합니다.
{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      "contacts": { "ghopper": true },
    },
    "ghopper": { ... },
    "eclarke": { ... }
  }
}
(출처: https://firebase.google.com/docs/database/android/structure-data)

성능과 데이터 중복, 그리고 트랜잭션 관리 등 생각해야할 요소들이 좀 있을 것 같습니다.
이런 자세한 이야기는 우선 나중으로 미루고,
당장 그럼 어떻게 데이터를 쓰고/읽고/수정하고/삭제하는지.
기본적인 CRUD에 대해 소개하겠습니다.

그 구현 난이도에 따라 다음 순서로 이야기를 시작하도록 하겠습니다.


1. Firebase Realtime Database 접근을 위한 DataSource 얻기

2. 데이터 Write 하기

3. 데이터 Delete 하기

4. 데이터 Read 하기


본 포스팅은 Google Firebase 공식 문서를 참조하여 작성되었습니다.
https://firebase.google.com/docs/database/android/start/


1. Firebase Realtime Database 접근을 위한 DataSource 얻기

Google에서 제공하는 Firebase Realtime Database 를 Android 환경에서 어떻게 사용하는지,
그 첫 단계로 Android Project에 환경 구축하는 방법에 대해 소개합니다.

app 수준의 build.gradle에 아래와 같이 firebase database를 추가합니다.
compile 'com.google.firebase:firebase-database:10.0.1'
Firebase Realtime Database에 접근하기 위한 DataSource는 아래와 같이 얻습니다.


FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("users");



실제 Firebase Realtime Database에 저장된 데이터들은 다음과 같습니다.




최상위 mandal-art 데이터베이스 안에 mandalarts, plans, users 와 같이 3개의 항목이 존재하고
users 안에 제 이메일 주소를 베이스로한 데이터가 저장되어 있습니다.
(저는 개인적으로 이 mandalarts, plans, users를 각각의 테이블로 이해를 하고 접근을 하였습니다.)

위 코드는 해당 프로젝트의 데이터베이스에서 users 영역에 대한 DatabaseReference를 얻는 코드라고 보면 됩니다.

개발자는 이 DatabaseReference (myRef)를 통해 CRUD 작업을 진행할 수 있습니다.



2. 데이터 Write 하기

위 데이터베이스에 신규 사용자를 추가한다고 하면
위에서 얻은 myRef에 User 객체를 하나 상승하여 저장해주면 끝입니다.



1
2
3
User user = new User();
user.setId("abc");
myRef.child("abc").setValue(user);

그럼 아래와 같이 사용자가 추가된 것을 firebase console에서 확인할 수 있습니다.


한가지 주의해야 할 점은,
저장하고자 하는 객체의 클래스에는 기본 생성자가 정의되어 있어야 합니다.
기본 생성자가 없다면 Exception이 발생하게 되니 참고하시기 바랍니다.



3. 데이터 Delete 하기


데이터를 삭제하는 것은 훨씬 더 간단합니다.
다음 코드에서 위에서 추가한 사용자 "abc"를 삭제합니다.



1
myRef.child("abc").removeValue();



4. 데이터 Read 하기


제일 간단할 것 같지만, Read가 제일 까다롭습니다.
처음에 write, delete 예제만 봤을 땐 다음과 같이 하면 될 줄 알았습니다.


1
myRef.child("abc").getValue();

하지만 어떻게 해봐도 객체를 Read하는 메서드는 존재하지 않았습니다.

다시 문서를 확인한 결과,
Read를 위해서는 ValueEventListener를 DatabaseReference에 추가해주어야 하며,
읽어 온 데이터를 사용하기 위해서는 callback 구조를 사용하는 것이 자연스럽습니다.

우선 Read하는 것은 다음과 같습니다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Read from the database
myRef.child("abc").addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // This method is called once with the initial value and again
        // whenever data at this location is updated.
        User user = dataSnapshot.getValue(User.class);
        Log.d(TAG, "user name is: " + user.getName());
    }

    @Override
    public void onCancelled(DatabaseError error) {

    }
});

위 코드를 통해 "abc" 사용자를 객체 형태로 읽어올 수 있습니다.
그런데 제가 말씀드린 callback 구조가 어떤 의미인가 싶을텐데요.

간단히 다음과 같은 로직이 있다고 생각해보죠.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public User getUser(String userId) {
    User user = null;
    
    myRef.child("abc").addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            user = dataSnapshot.getValue(User.class);
        }

        @Override
        public void onCancelled(DatabaseError error) {
        }

    });

    return user;
}

userId 를 인자로 넘겨주어 Firebase Realtime Database에서 해당하는 User 객체를 받아 return 하는 메서드입니다.
그러나 이를 실행해보면 항상 null이 반환됩니다.
myRef에서 Listener를 걸어두기만 하고 바로 null 값의 user를 return 한 뒤,
이 ValueEventListener는 이후에 동작하기 때문입니다.

그렇기 때문에 보통의 경우는 다음과 같이 callback 구조로 사용합니다.



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 아래 코드는 User 객체를 얻어와 TextView에 사용자 이름을 표시하는 메서드입니다.
public void showUserName() {
 getUser("abc", new LoadUserCallback() {
  @Override
  public void onUserLoaded(User user) {
   textView.setText(user.getName());
  }
 });
}

// 실제로 Firebase Realtime Database에서 User 객체를 조회한 뒤 callback 메서드를 호출해줍니다.
public void getUser(@NonNull final String userId, @NonNull final LoadUserCallback callback) {
 ref.child("users").addListenerForSingleValueEvent(new ValueEventListener() {
  
  @Override
  public void onDataChange(DataSnapshot dataSnapshot) {
   if (dataSnapshot.hasChild(userId)) {
    User user = dataSnapshot.child(userId).getValue(User.class);
    callback.onUserLoaded(user);
   }
  }

  @Override
  public void onCancelled(DatabaseError databaseError) {

  }
 });
}

이러한 callback 방식이 android app 개발에 자주 쓰이는 패턴인지는 모르겠습니다만,
Firebase Realtime Database 를 사용한다면 익숙해져야 할 것 같습니다.

그리고 이러한 방식이 Android에서 MVP 패턴을 사용하는데 대표적인 예제가 되지 싶습니다.

댓글

이 블로그의 인기 게시물

미니 메추리 키우기 - 사육장 만들기

미니 메추리는 우리가 알고 있는 일반 메추리보다 조금 작은 개체입니다. 버튼퀼(버튼퀘일)이라고도 불리죠. 일반 메추리보다 작기도 하고 짝이 맞는 암수가 같이 있으면 그리 시끄럽지도 않습니다. 여러 모로 키우기가 좀더 수월하죠. 첫 번째 단계로 먼저 아이들이 지낼 집을 만들어 주었습니다. 사실 여러 고민을 많이 했어요 지금 소개하는 집을 만들기 전에는 120L 짜리 대형 리빙 박스로 집을 만들어 주었었죠. 값이 저렴하고 개량하는 것이 크게 어렵지 않기 때문에 많은 분들이 리빙 박스를 개조하여 집을 만들어 주고 있어요. 저 같은 경우는 보온을 생각해서 안쪽에는 단열재를 덧대기도 했죠. 하지만 사실 리빙 박스로 집을 만드는게 아주 쉽지만은 않아요. 물론 있는 그대로를 사용하신다면 어려울 건 전혀 없죠. 그런데 만약 전구를 달기 위해 구멍을 뚫거나, 환기 구멍을 뚫거나 기타 여러 필요에 의해 리빙 박스를 뜯어 고쳐야 한다면 이야기가 달라지죠. 저도 사실 이런 불편함에 고민고민을 하다가 오늘 소개해 드릴 두 번째 집과 같은 것을 생각하게 되었어요. 바로 시중에서 쉽게 구할 수 있는 종이 박스를 활용한 것인데요. 위쪽 뚜껑에는 구멍을 두 개를 뚫었어요. 작은 구멍은 온도 조절을 위한 전구 바로 위쪽으로 온도가 너무 올라갈 경우 온도 조절을 위해 뚫어 놓았고요.  아래 좀더 큰 구멍은 물, 먹이 등을 교체해주기 위한 구멍이에요.  정면에는 창을 내어 관찰할 수 있게 했어요. 지금은 저 가운데도 잘라내서 크게 창 하나로 만들었어요. 안쪽에는 온도계를 비치하여 내부 온도를 확인할 수 있게 해두었습니다. (지금 생각해보니 전구 바로 아래쪽에 위치한 탓에 제대로 된 온도 측정이 될지 모르겠네요;;;) 그리고 보셔서 아시겠지만, 내부 ...

[메추리 키우기] 스티로폼으로 정말 쉽게 부화기 만들기

미니 메추리 한 쌍을 기르고 나서는  거의 4일에 3번 정도 알을 낳고 있습니다. 가끔 휴란기를 짧게 짧게 갖는 경우도 있고요. 그 알 들이 처치 곤란이기도 하고, 부화시키는 것도 해보고 싶어서 부화기를 만들어야지.. 했는데  그놈의 스티로폼 박스를 구하지 못해 차일피일 미루고 있었습니다. 그러다 요 근래  같이 사는 순둥이와 오전 산책 중에 집 근처에 버려져 있는 박스를 발견했어요 ㅎㅎ (요놈이 순둥이 입니다 ㅎㅎ) 바로 들고 왔죠! 일단 물과 솔로 박박 닦아줬어요. 그리고 온도 조절을 위한 전구! 10w 정도로 샀는데, 일단 지금 쓰기에는 충분한 것 같아요. 관련 카페에서는 10w 2개를 쓰라고도 하는데요. 혹시 사용하다가 1개가 나가버리는 사고를 미연에 방지하기 위함입니다. 일단 저는 1개만 달았어요. 간단히 그냥 스티로폼 옆에 칼 집을 약간 내어서 선을 끼웠습니다. 참 대충 했죠 -0- 그리고 또 중요한 것. 바로 전란인데요. 간단히 알을 굴려주는 거라고 보면 될 것 같습니다. 알을 굴려주지 않으면  사람으로 치면 가만히 요지부동으로 누워있는 거라고 보시면 되겠는데요. 이럴 경우 알 속의 배아가 튼튼하게 정상적으로 자라지 못한다고 합니다. 그래서 실제 야생에서도 어미 새가 굴리기도 하고 뒤척인다고 해요. 나무젓가락과 돌아다니는 박스를 조금 잘라 어설프게 만들었습니다;;; 정말 대충 만들었죠;;; 처음에는 알들을 몇개 넣어둔 뒤 뚜껑을 완전히 덮었는데요. 온도를 측정해보니 40도 까지 올라가더군요. 적정 온도는 37~39도 정도라고 해서 일단 살짝 열어두니 온도 조절이 조금 되는 상황입니다. 물통은 습도 조절을 위해 넣어두었...

미니 메추리 키우기 - 사육장 확장

커다란 리빙 박스에서 상당히 작은 종이 박스로 집을 이사하고는 괜히 미안하더라고요. 터무늬 없이 좁아진 집... 제대로 날지도 자연을 느끼지도 못하는 불쌍한 녀석들인데 집까지 강제로 좁은 집으로...... 그래서 확장 공사를 진행하였습니다. 지난 번 종이 박스를 혹시 몰라 2개를 구입했었는데요. 때마침 이렇게 확장 공사를 할 수 있게 되었네요. 기존 종이 박스 사육장 옆에 바로 붙여서 2배 사이즈 Up을 하였습니다. 중간에 넘나들 수 있도록 잘라주었어요. 사실 처음 의도했던 것은 먹이가 있는 곳에는 흙을 넣지 않으려고 했어요. 흙이 있고 그 위에 사료통을 넣어두니 녀석들이 흙 위에서 난리 부르스를 떨면 사료가 흙으로 뒤덮여서 먹을 수가 없게 되더라고요. 그런데 막상 이어 붙여 만들다 보니 양쪽 흙을 안 놓을 수가 없더군요. 대신 위에 보시는 것처럼 사료통을 살짝 공중에 띄워놔서 흙이 덮여지지 않게끔 했어요.    못보던 공간을 발견하고는 과감한 암컷이 먼저 기웃댑니다. 암컷이 먼저 들어간 것 확인하고는 수컷이 뒤따라 들어가네요. 종이 박스로 하다 보니 확실히 사육장의 개조/가공은 쉽습니다. 일반 커터칼로 창을 내고, 종이 박스를 서로 붙이거나 할 수 있죠. 대신 방수가 되지 않는 단점이 있어요. 저 같은 경우는 안쪽에 단열재?같은 것을 붙이고 흙을 깔아둬서 물을 들이 붓지 않는 이상은 쓰는데 큰 지장은 없습니다.