기본 콘텐츠로 건너뛰기

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

[수경재배] 원룸 실내 상추 키우기 - 상추 파종

날이 조금 풀린 것도 같고, 실내 온도가 18도 정도는 되는 것 같아서 다시 실내 상추 키우기를 시작해보려고 합니다. 처음 시도했을 때에는 아는 것도 없고 따로 관심을 갖지 않고 될대로 되라는 식으로 키우는 바람에 큰 성과가 없었어요. 그래서 이번 두 번째 시도는 좀더 관심을 갖고 진행해보려고 합니다. 1. 먼저 상추 씨앗을 스폰지 위에 가지런히 올려줍니다. 씨앗을 2개 이상 올려준 이유는 혹여나 씨앗이 발아하지 못할 경우를 대비해서 입니다. 저 스폰지는 ' 수경재배 스펀지 '로 검색하면 쉽게 구입할 수 있습니다. 사진으로는 잘 안보이지만 십자가로 홈이 있어서 나중에 작물이 뿌리를 내릴 수 있어요.  2. 그리고 미리 준비한 그릇에 스펀지들을 넣어줍니다. 스펀지들이 물을 충분히 흡수 할 수 있도록 해주세요. 3. 그 위에 휴지를 얇게 얹고 다시 분무기로 물을 충분히 뿌려 줍니다. 씨앗이 바람에 날아가거나, 씨앗의 수분이 증발하거나, 등의 방해 요소로부터 보호해줄 수 있습니다. *그렇지만 너무 두껍게 휴지를 올려주지는 마세요. 새싹이 휴지를 뚫고 올라오지 못할 수도 있습니다! 일단 최초 파종은 이렇게 간단하게 끝이 납니다. 이제 몇일 지나게 되면 싹이 나올 겁니다! 이후 과정에 대해서 계속 포스팅하도록 하겠습니다. :)

[Android] Fragment 위에 Dialog 띄우기

배경:  - Fragment에 Google Maps를 올려 사용자에게 보여주고 있다.  - 사용자가 Dialog 창을 열어 검색을 할 수 있게 하고 싶다. 1. Dialog layout xml 생성 Dialog 창에는 하나의 EditText를 추가하여 사용자로 하여금 String을 입력받게 한다. 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 29 30 31 32 < LinearLayout   xmlns:android = "http://schemas.android.com/apk/res/android"         android:orientation = "vertical"         android:layout_width = "wrap_content"         android:layout_height = "wrap_content" >     < LinearLayout                 android:orientation = "horizontal"                 android:layout_width = "match_parent"                 android:layout_height = "wrap_content" >         < TextView                         android:layout_width = "match_parent"                         android:layout_height = "match_parent&quo