반응형

'크리스마스에 할 것도 없는데 앱이나 하나 만들자'라고 크리스마스 한 주 전부터 생각하고 있었다. 그 와중에 친구가 문제적 남자에서 육목을 하는 영상을 보여줬는데, 한 번 만들어보면 재밌을 것 같다고 생각했다. 그리고 생각이 드는 순간 계획을 세우고 바로 개발에 돌입했다. 

 처음에는 만만하게 생각하고 3일이면 앱을 다 만들 수 있을 것이라고 생각했다. 또한 계획을 길게 잡으면 쳐지기 때문에 빠른 시일 내에 앱을 임팩트 있게 개발하고 싶어서 최대한 집중해서 개발했다. 그러나 결국 계절학기가 시작됐고 앱을 다 만들지 못했다. '계절학기를 수강하면서 앱을 개발하면 되지 않나?'라고 할 수 있지만, 사실 계절학기를 두 과목 들었기 때문에 많은 시간을 투자할 수는 없었다. 또한 앱 개발이 잘 되어 오다가, 잘 풀리지 않는 오류에 직면했다. 그래서 위 사진에서 보는 바와 같이 오류를 고치느라 github에서 commit을 하지 못한 것을 확인할 수 있다. 

 

 그 오류는 계절학기가 모두 끝나고 나서, 다시 오류의 원인과 해결방법을 찾아보면서 결국 해결할 수 있었다. 그 오류는 Coroutine과 관련된 것이었고, Coroutine을 통해 해결되었다.

 

suspendCoroutine

*문제 원인

    fun temp(database: DatabaseReference, roomNum: String): Boolean{
        var rValue = true
        database.addListenerForSingleValueEvent(object : ValueEventListener {
            override fun onDataChange(snapshot: DataSnapshot) {
                for (i in snapshot.children) {
                    if (roomNum == i.child("roomId").value.toString()) {
                        rValue = false
                    }
                }
            }
            override fun onCancelled(error: DatabaseError) {}
        })
        return rValue
    }

 Firebase database에서 값이 있는지 유무를 확인한 후 값이 있다면 false를 반환하고, 값이 없다면 기존에 선언된 true를 반환하는 코드다. 위 코드가 필요했던 이유는 사용자가 방 번호를 입력하면 그 순간 데이터베이스에서 사용자가 입력했던 방 번호가 있는지 조회를 한 후, 사용자가 입력했던 방 번호가 있으면 방을 만들지 못하도록 하고, 방 번호가 없다면 방을 만들 수 있게 해주기 위해서 필요했다. 그러므로 미리 불러올 수 있는 값도 아니었기에 비동기식으로 처리하지 못했다.

 정상적으로 구동은 되지만 위 temp function은 항상 true를 반환한다. 그 이유는 firebase의 addListenerForSingleValueEvent가 비동기식으로 돌기 때문에 결국 rValue를 true로 return한 후 firebase database 값을 조회한다. 즉 이 비동기 방식을 동기로 바꿔줘야 내가 원하는 코드가 될 수 있는 것이다. 

 

*해결방법 : suspendCoroutine

 - Kotlin 공식문서에서는 위와 같이 설명하고 있다. suspendCoroutine은 resume을 이용해서 반환할 수 있고, synchronously(동기식)으로 사용할 수 있다고 한다. 위에서 문제가 되었던 코드를 아래와 같이 수정했다.

 

    private suspend fun isRoomAvailable(roomNum: String) =
        suspendCoroutine<Boolean> {
            Handler(Looper.getMainLooper()).postDelayed({
                val database = FirebaseDatabase.getInstance().reference.child("roomId")
                var rValue = true
                database.orderByChild("roomId").equalTo(roomNum)
                    .addListenerForSingleValueEvent(object :
                        ValueEventListener {
                        override fun onDataChange(snapshot: DataSnapshot) {
                            rValue = false
                            it.resume(rValue)
                        }
                        override fun onCancelled(error: DatabaseError) {}
                    })
            }, 500)
        }

 - equalTo를 이용해서 사용자가 입력한 roomNum 값과 같은 값이 Firebase Database에 있는지 조회한다. 그래서 같은 값이 있다면 addListenerForSingleValueEvent가 호출되는데 이 때, rValue를 false로 바꾼다. 그 후 같은 값이 있으므로 더 조회해볼 필요 없이 바로 rValue 값과 함께 resume한다. 

 

 - it.resume(반환값)을 이용해서 코드 중 원하는 위치에서 return할 수 있다. 정확히 위 오류에서 발생했던 문제점을 해결할 수 있는 코드였다. 또한 it.resumewith(Result.success(반환값)), it.resumewith(Result.failure(AssertionError()))와 같이 반환 성공, 실패로 나누어서 반환할 수도 있다.

 

 - 또한 suspendCoroutine을 받는 부분에서는 Coroutine이나 또 다른 suspend function에서 호출해야 한다. 그냥 oncreate 안에서 호출하거나 다른 함수에서 호출할 수 없다.

 

반응형

+ Recent posts