Android Studio - Firebase(Realtime Database)

Minjae Lee·2024년 3월 3일
0

Android Studio

목록 보기
7/11

1. 사전 작업

데이터들을 사용자별로 구분지어 저정해야 하기 때문에, 로그인 기능을 구현해야 한다. 따라서 Firebase Login & Register의 작업을 먼저 진행한 후 데이터베이스 작업을 진행해야 한다.

또한 Realtime Database를 사용하기 위해선 데이터베이스를 만들어야 한다. Firebase 콘솔 창에서 Realtime Database를 찾아 데이터베이스 만들기 버튼을 클릭한다.

데이터베이스 위치는 미국으로 하고, 보안 규칙은 테스트 모드로 설정한다. 이후 사용설정 버튼을 누르면 Realtime Database를 사용할 수 있다.

Realtime Database 공식문서를 살펴보면, Realtime Database 사용에 대한 설명이 나와있다. Android Studio에서 사용하기 위해선, 앱 수준의 build.gradle 파일의 아래의 코드를 추가해야 한다.

dependencies {
    ...
    
    implementation 'com.google.firebase:firebase-database'
}

implementation을 추가한 후, Sync now 버튼을 누르면 Android Studio에서도 Realtime Database를 사용할 수 있다.

2. Database 구조

Realtime Database를 사용하기 위해선, Database의 구조를 알아야 한다. 예를 들어, 아래의 Data들을 보자.

위의 사진은 유저 a와 b의 id와 name을 저장한 결과이다. 데이터 구조를 폴더라고 생각하면 편하다.
User라는 큰 폴더 아래에 사용자들의 정보를 저장하고자 한다면, a의 세부 정보(id, name)과 b의 세부정보를 구분하기 위해 사용자 a 폴더 아래에 a의 id와 name을, b 폴더 아래에 b의 id와 name을 저장한다.

즉, 저장하고자 하는 데이터들의 공통적인 부분들을 생각하여 큰 폴더로 묶고, 세부 내용들을 작게 묶어 저장할 수 있도록 데이터 구조를 생각해야 한다. 필요한 경우, 위의 사진보다 더 세분화하여 저장할 수도 있다.
a 사용자의 친구 목록을 추가할 경우를 예시로 들어보면, 아래와 같다.

3. Database Write

앱에서 Realtime Database에 데이터들을 작성해보자. 필자의 경우 새로 생성한 Project들을 묶어 저장할 수 있도록 데이터 구조를 생각해보았다. 즉, 아래와 같이 저장해보고자 한다.

3-0. WorkFlow

  1. 사용자가 데이터를 입력할 수 있는 페이지 제작
  2. Realtime Database에 write

3-1. 사용자가 데이터를 입력할 수 있는 페이지 제작

사용자 입력을 바탕으로 저장하기 위해, 데이터를 입력할 수 있는 xml 페이지를 제작하였다.

  • activity_datawrite.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=".DatawriteActivity">

    <EditText
        android:id="@+id/editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="write here"
        android:inputType="text"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.379" />

    <Button
        android:id="@+id/submitbtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Submit"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.641" />
</androidx.constraintlayout.widget.ConstraintLayout>

3-2. Realtime Database에 write

Realtime Database에 데이터를 쓰기 위해 아래와 같은 인스턴스를 생성해줘야 한다.

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

database는 Realtime Database를 사용하기 위함이며, myRef는 Realtime Database에서 어떤 폴더에 쓸지 지정하는 역할이다. 위의 사진에서 Data 폴더에 쓰기로 했으므로, child를 Data로 지정해줬다.

submit 버튼을 눌렀을 때 데이터를 작성하는 코드는 아래와 같다.

submitBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        String data = editText.getText().toString().trim();

        String s = myRef.push().getKey();
        myRef.child(s).setValue(data);
    }
});

위의 코드에서 myRef에 push()해주는 부분은 랜덤한 key 값을 서버에 쓰는 역할을 하는데, 위의 사진에서 data1, data2와 같이 각 데이터들마다 유일한 값을 지정해주어 겹치지 않게 해준다. 랜덤한 값을 지정해주고 해당 값을 s 변수에 저장한 다음, 경로상으로 "/Data/(s의 값)/"에 setValue를 통해 editText의 값을 저장하는 코드이다.

전체 코드는 아래와 같다.

  • DatawriteActivity.java
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import androidx.appcompat.app.AppCompatActivity;

import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;

public class DatawriteActivity extends AppCompatActivity {

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

    EditText editText;
    Button submitBtn;

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

        editText = findViewById(R.id.editText);
        submitBtn = findViewById(R.id.submitbtn);

        submitBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String data = editText.getText().toString().trim();

                String s = myRef.push().getKey();
                myRef.child(s).setValue(data);
            }
        });
    }
}

실행해보면 아래와 같이 데이터들이 써진다는 것을 알 수 있다.

4. Database Read

앞서 Realtime Database에 데이터들을 작성해보았는데, 앱에서는 데이터를 가져오는 작업도 필요하다. 필자의 경우 아래와 같이 저장되어있는 데이터를 읽어오도록 코딩해보고자 한다.

4-0. WorkFlow

  1. 서버에서 읽어온 데이터를 띄울 수 있는 페이지 제작
  2. Realtime Database에서 읽어와 화면에 출력

4-1. 서버에서 읽어온 데이터를 띄울 수 있는 페이지 제작

간단하게 TextView만을 사용하여 서버에서 읽어온 데이터를 띄우는 페이지를 제작했다.

  • activity_dataread.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=".DatareadActivity">

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

4-2. Realtime Database에서 읽어와 화면에 출력

Realtime Database에서 값을 읽어오는 방법에는 동기식 방법, 비동기식 방법 두 가지가 있다.

  • 동기식 방법 : 먼저 시작된 작업이 끝날 때까지 다른 작업을 시작하지 않고 기다렸다가 작업이 다 끝나면 새로운 작업을 시작
  • 비동기식 방법 : 먼저 시작된 작업의 완료 여부와는 상관없이 새로운 작업을 시작

Firebase에서 기본적으로 제공하는 방법은 비동기식 방법인데, 해당 방법을 사용하면 데이터를 미처 받아오기도 전에 다음 작업을 실행하여 원하지 않는 결과를 얻어온다. 따라서 우리는 동기식 방법으로 데이터를 가져와야 한다.

아래 코드는 데이터를 가져오는 코드의 큰 틀이다. onDataChange 함수는 Realtime Database의 데이터 값이 바뀔 때마다 자동으로 실행되어 바뀐 값을 가져오는 함수이다.

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

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

    readData(new FirebaseCallback() {
        public void onCallback(저장된 data) {
            // 저장된 data를 바탕으로 진행할 작업
        }
    });
}
private void readData(FirebaseCallback firebaseCallback) {
    ValueEventListener valueEventListener = new ValueEventListener() {
        // 저장할 data 자료형(ArrayList, HashMap 등) 선언 및 초기화
    
        @Override
        public void onDataChange(@NonNull DataSnapshot snapshot) {
            // snapshot으로부터 데이터를 저장
            firebaseCallback.onCallback(저장된 data);
        }

        @Override
        public void onCancelled(@NonNull DatabaseError error) {
        }
    };

    myRef.addListenerForSingleValueEvent(valueEventListener);
}

private interface FirebaseCallback {
    void onCallback(저장된 data);
}

readData 함수를 실행하면서 Realtime Database에서 읽어온 데이터를 어떤 자료형(ArrayList, HashMap 등)에 저장하고, 저장한 데이터들을 onCallback 함수 안에서 이용하여 각종 작업을 한다.

필자의 경우 (랜덤한 값, 저장된 값)을 모두 띄우고자 <String, String> HashMap 자료형을 사용했다. 데이터를 읽어오는 전체 코드는 아래와 같다.

  • DatareadActivity.java
import android.os.Bundle;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;

import java.util.HashMap;
import java.util.Map;

public class DatareadActivity extends AppCompatActivity {

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

    TextView textView;

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

        textView = findViewById(R.id.textView3);

        textView.setText("");

        readData(new FirebaseCallback() {
            public void onCallback(HashMap<String, String> datas) {
                for (Map.Entry<String, String> data : datas.entrySet()) {
                    String key = data.getKey();
                    String value = data.getValue();

                    String showText = textView.getText().toString() + key + " : " + value + "\n";
                    textView.setText(showText);
                }
            }
        });
    }
    private void readData(FirebaseCallback firebaseCallback) {
        ValueEventListener valueEventListener = new ValueEventListener() {

            HashMap<String, String> datas = new HashMap<String, String>();
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                for(DataSnapshot data : snapshot.getChildren()) {  //User 1, User 2
                    datas.put(data.getKey(), data.getValue().toString());
                }
                firebaseCallback.onCallback(datas);
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {
            }
        };

        myRef.addListenerForSingleValueEvent(valueEventListener);
    }

    private interface FirebaseCallback {
        void onCallback(HashMap<String, String> datas);
    }
}

실행해보면 저장된 데이터들이 아래와 같이 화면에 뜬다는 것을 알 수 있다.

저장된 데이터가 아래와 같으므로, 제대로 데이터가 가져와졌음을 알 수 있다.

5. 여러 데이터 한번에 쓰기

위에서 살펴봤던 onDataChange의 경우, 해당 Ref(가장 큰 폴더)의 하위 항목 중 하나라도 바뀌면 서버에서 값을 전부 가져온다. 따라서 "3. Database Write"에서 했던 것처럼 비슷한 데이터를 여러번 쓰면 데이터가 써질 때마다 서버에서 값을 가져와 원하지 않는 방향으로 작동할 수 있다. 따라서 Firebase의 updateChildren을 이용하여 서버에 값을 한번에 써야한다.

updateChildren 함수를 사용하기 위해, Java의 HashMap을 이용해야 한다. 이때 Map<String, Object> 객체를 이용하여 서버에 값을 써야 한다. 대략적인 코드는 아래와 같다.

private DatabaseReference mDatabase;
mDatabase = FirebaseDatabase.getInstance().getReference();

Map<String, Object> childUpdates = new HashMap<>();
// add data in childUpdates

mDatabase.updateChildren(childUpdates);

따라서, 아래와 같은 데이터들을 한번에 넣는 코드를 작성하면 다음과 같다. (sub_Student에는 Student와 동일한 데이터들이 저장되어 있다.)

  • DatamultiwriteActivity.java
import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;

import java.util.HashMap;
import java.util.Map;

public class DatamultiwriteActivity extends AppCompatActivity {

    private DatabaseReference mDatabase;

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

        mDatabase = FirebaseDatabase.getInstance().getReference();
        Map<String, Object> friends, user;
        Map<String, Object> students = new HashMap<String, Object>();
        Map<String, Object> childUpdates = new HashMap<String, Object>();

        // set A data
        friends = Map.of(
                "C", true,
                "D", true
        );
        user = Map.of(
                "height", 170,
                "weight", 75,
                "score", "A+",
                "friends", friends
        );
        students.put("A", user);

        // set B data
        friends = Map.of(
                "E", true,
                "F", true
        );
        user = Map.of(
                "height", 180,
                "weight", 90,
                "score", "B+",
                "friends", friends
        );
        students.put("B", user);

        // set student data and sub_student data
        childUpdates.put("Student", students);
        childUpdates.put("sub_Student", students);

        mDatabase.updateChildren(childUpdates);
    }
}

0개의 댓글