본문 바로가기
⛏️ | 개발 기록/⏰ | Schedule Planner

[Android, Kotlin] Firebase RTDB에서 데이터 순차적으로 삽입하기 (10)

by immgga 2023. 11. 12.

 

이번 포스팅에서는 Firebase RTDB을 활용해 일정을 생성하는 기능을 만들어 보겠다.

드디어 일정을 생성하는 기능까지 왔다.

이번에 일정 생성 기능은 간단하게 만들어 보겠다.

일정 데이터는 schedule -> user uid -> 일정 리스트 순서대로 데이터를 저장할 것이고,

데이터 형태는 title, description, complete(완료 여부)로 저장하겠다.

일정 데이터가 저장된 모습

위의 사진과 같은 형태로 저장을 할 것이고, 일정을 사용자가 여러 번 생성할 수 있기 때문에 기존 일정이 지워지지 않게 하면서 아래에 새로운 데이터를 넣는 방식으로 만들어볼까 한다.

킹림판

대충 위와 같은 형태로 데이터가 저장되도록 하겠다.

설명은 끝났으니 바로 만들어 보겠다.

 

1. Firebase Database 라이브러리 추가

firebase database 라이브러리를 추가한다.(2023.11.12 기준)

implementation("com.google.firebase:firebase-database:20.3.0")

 

 

2. 생성하기 로직 작성

먼저 일정 정보를 나타내기 위한 data class를 만들어 주겠다.

data class PlanData(
    val title: String = "",
    val description: String = "",
    val isComplete: Boolean = false
)

데이터를 만들어 주었는데, 생성한 모든 데이터가 기본값이 있는 모습이다.

기본값을 넣는 이유는 나중에 일정 정보를 가져오는 코드를 작성할 때, 데이터들의 기본값을 정해주지 않으면 오류가 발생하기 때문이다.

 

data class를 만들어 주었으면 바로 일정을 생성하는 코드를 작성해 주겠다.

먼저 일정이 생성되었는지 확인할 State를 만들어 주고,

private val _savePlan = MutableStateFlow(false)
val savePlan: StateFlow<Boolean> = _savePlan.asStateFlow()

일정 생성 코드를 작성해 준다.

val saveFirebaseRef = FirebaseDatabase.getInstance().reference
    .child("schedule")
    .child(uid)
val objectMap = mutableMapOf<String, PlanData>()

saveFirebaseRef.addListenerForSingleValueEvent(object: ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        snapshot.children.forEach { dataSnapShot ->
            val data = dataSnapShot.getValue(PlanData::class.java)
            objectMap[objectMap.size.toString()] = data!!
        }

        // 새로운 데이터를 add object에 추가하기
        plans.forEach { objectMap[objectMap.size.toString()] = it }

        // 추가한 데이터로 업데이트
        saveFirebaseRef.updateChildren(objectMap as Map<String, PlanData>).addOnCompleteListener { task ->
            if (task.isSuccessful) _savePlan.value = true
        }
    }

    override fun onCancelled(error: DatabaseError) {}
})

필자는 데이터를 저장한 경로에 데이터를 모두 불러와서 Map에 저장해 준 후, 새로운 데이터를 기존 데이터를 저장한 Map에 추가로 저장해서 데이터를 updateChildren으로 업데이트를 하는 방법으로 코드를 작성했다.

 

addListenerForSingleValueEvent()는 비동기 함수이기 때문에 저장하는 코드를 밖으로 빼놓으면 저장하는 로직과 데이터를 불러오는 작업이 동시에 수행되기 때문에 새로 데이터가 생기는 것이 아닌 기존의 데이터가 덮어 씌워지는 문제가 발생했기 때문에, onDataChange() 안에 데이터 저장 로직과 새로운 데이터를 추가해 주는 로직을 같이 넣어주었다.

그리고 Map의 Key를 map의 size를 이용해 저장하도록 하였는데, 저렇게 한 이유는 데이터베이스의 uid 하위의 key의 값이 변경되게 하기 위해서이다.

설명하기 쉽게 그림을 그려서 알려주자면,

기존의 3까지 들어차 있는 데이터가 있다.

기존 데이터 1, 2, 3

 

위의 데이터에서 key가 2인 데이터가 지워진다고 생각해 보자.

2 rip

 

그러면 기존의 1과 3만 남게 되는데, 새로운 데이터 4를 추가한다고 하면 다음과 같은 문제가 발생하게 될 것이다.

key 2 어디감?

 

저렇게 데이터베이스가 채워지게 되면, 데이터를 삭제할 때마다 위의 사진과 같은 공백이 남게 될 것이다.

그래서 필자는 Map을 사용해서 데이터를 업데이트하는 방법을 선택했다.

기존의 데이터베이스의 데이터를 모두 가져온 다음 key를 map의 size를 string으로 바꾼 것으로 넣고 value만 가져와서 넣어준다.

snapshot.children.forEach { dataSnapShot ->
    val data = dataSnapShot.getValue(PlanData::class.java)
    objectMap[objectMap.size.toString()] = data!!
}

위의 코드의 결과 Map을 보여주자면(데이터가 위의 사진처럼 value 가 1, 3인 데이터가 있다고 가정)

0 : 1, 1 : 3와 같은 Map 형태가 될 것이다.

그다음 이제 새로 생성한 데이터를 다시 Map에 넣어준다.

// 새로운 데이터를 add object에 추가하기
plans.forEach { objectMap[objectMap.size.toString()] = it }

위의 코드를 사용해 4라는 데이터를 넣었다면

0 : 1, 1 : 3, 2 : 4와 같은 Map 형태가 될 것이고, 이걸 updateChildren()으로 적용을 시키면

// 추가한 데이터로 업데이트
saveFirebaseRef.updateChildren(objectMap as Map<String, PlanData>).addOnCompleteListener { task ->
    if (task.isSuccessful) _savePlan.value = true
}

데이터를 삭제해도, key의 순서가 꼬이지 않고 유지되는 것을 확인할 수 있다.

저장된 데이터 형식

 

사진을 보면, plan 2가 사라졌지만, key의 순서는 올바르게 유지(0, 1, 2)되고 있는 것을 확인할 수 있다.

 

데이터 삽입 로직을 만들었다면, 마지막으로 삽입 로직을 적용해 주면 끝이다.

createPlanViewModel.savePlan(
    plans = planListState.toList(),
    uid = FirebaseAuth.getInstance().uid!!
)

Compose에서 ViewModel을 불러온 다음, 생성한 일정 정보와 사용자의 uid를 넘겨주고,

val savePlanState = createPlanViewModel.savePlan.collectAsState()

LaunchedEffect(savePlanState.value) {
    if (savePlanState.value) navigateToPlan()
}

일정을 저장하면 true로 변하는 State를 불러와서 savePlanState가 true이면 plan screen으로 이동하는 로직을 추가해 주면..

일정 생성 기능 완성이다.

 

3. 테스트

일정 추가하기

 

데이터베이스 변경 화면

 

 

정리

  • 데이터베이스에서 key number의 순서를 유지하는 방법(Map과 updateChildren()을 이용)은 빠르게 생각해 냈지만 데이터가 계속 덮어씌워지는 문제를 해결하느라 구현이 오래 걸렸다 >:(
  • 데이터 저장 로직에 대해서 test code도 작성해 볼까 생각했지만, Firebase Server를 initialize 하는 과정이 복잡했기에 일단 포기했고 다시 조사해서 적용해 보겠다 :(
    (자료도 많이 없었다)
728x90

댓글