[Kotlin Android Jetpack] Room을 활용하여 자동저장 구현하기

l2hyunwoo·2021년 1월 18일
0

Android 기능 구현

목록 보기
10/12
post-thumbnail

발단

진행하고 있는 프로젝트에서 화면이 갑자기 꺼지거나 사용자가 잠시 글 Activity를 떠나도 다시 들어왔을 때 글이 자동으로 복구되는 자동 임시저장 기능을 구현할 것을 요구하였다.

문제점 정의

답변을 작성하다 홈 화면으로 나가거나 앱을 강제 종료해도 현재 작성하던 글은 임시저장이 되어야 한다.

서버통신으로 해결할까?

임시저장을 하는데 굳이 서버에다 저장할 필요까지는 없을 것 같았다. 통신 Cost도 너무 많이 들고 내 기기에서 데이터를 직접 꺼내올 수 있어야 임시저장 기능의 본질에서 많이 벗어난 것 같았다.

Shared Preference(DataStore)는?

게시글의 갯수는 유동적으로 변하므로 적은 수(정해진 양의 객체)의 객체를 관리하기에 좋은 Shared Preference로 구현하기에는 역부족이라 생각했다.

결론은?

결국에는 글의 데이터를 구조화해서 DB형식으로 저장할 수밖에 없을 것 같아, 앱 내 DB인 Room을 활용하기로 했다.

그래서 Room으로 어떻게 구현할건데?

글자가 입력할 때마다 저장하게 할까?

  • 정말 장점은 확실하다, 어떠한 경우에도 저장이 가능할 것이다
  • 그러나 Room 역시 데이터 통신인 만큼 시간이 걸린다는 점을 생각해보면 이 역시 시스템 리소스를 많이 잡아먹는 요인 중 하나가 될 것이다

그러면?

  • 자동저장이 되는 순간을 생각해보자. 자동저장은 글의 제출 버튼을 누르지 않고 Back Press 키를 눌렀을 때 혹은 Activity에서 떠났을 때 실행되어야 한다
  • 이때 공통적으로 Activity는 pause 상태가 되므로 onPause에서 자동저장 로직을 호출하면 될 것 같다는 생각을 하게되었다.

자동 저장 로직

작성 중인 글 내용 받아오기

-> 텍스트를 답변 아이디와 함께 DB에 저장

-> 다시 앱에 들어가서 해당 답변 창을 띄울 때 답변 id에 해당하는 텍스트를 DB에서 가져와 Intent로 넘겨

-> 답변 Acitivty에 붙여주기

그렇다면 작성중인 글의 내용은 어떻게 가져오지?

InverseBindingAdapter

Inverse Binding Adapter는 기존 Binding Adapter와 비슷하게 View의 속성에 Data Binding을 활용하여 변수를 집어넣어 동적으로 데이터를 XML에 전달할 수 있게하는 기능이다.
다만, Binding Adapter이 변수의 값을 View의 속성에 전달하는 것이었다면, Inverse Binding Adapter는 View가 가지고 있는 값을 변수에 바인딩 시키는 개념이다.
이를 활용하여 EditText의 android:text 속성에 변수를 바인딩시키면 실시간으로 EditText의 값이 변수로 전달되게 된다

@InverseBindingAdapter(attribute = "android:text", event = "textAttrChanged")
    @JvmStatic
    fun getEditTextString(view: EditText): String = view.text.toString()

    @BindingAdapter("android:text")
    @JvmStatic
    fun setEditTextString(view: EditText, text: MutableLiveData<String>) {
        if (text == null) {
            view.setText("")
        } else {
            if (view.text.toString() != text.value) view.setText(text.value)
        }
    }

    @BindingAdapter("textAttrChanged")
    @JvmStatic
    fun setEditTextTextWatcher(view: EditText, textAttrChanged: InverseBindingListener) {
        view.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}

            override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                textAttrChanged.onChange()
            }

            override fun afterTextChanged(p0: Editable?) {}
        })
    }

ViewModel에서 DB 입출력

ViewModel에서 DB와 통신할 수 있는 DAO를 넣어주고 이를 활용하여 DB에서 데이터를 가져오고 저장한다. 이 때 DB에서 데이터를 메인 스레드에서 입출력을 할 때 스레드가 블럭되어 ANR이 발생될 수 있으므로 Coroutine을 활용하여 DB 통신을 한다.

class AnswerViewModel(
    private val dataBase: AnswerDao
) : ViewModel() {
    var answer: String = ""
    private var questionId = -1

    suspend fun initEditText(id: Int): String {
        return viewModelScope.async {
            answer = getStoredAnswer(id.toLong())?.answer ?: ""
            questionId = id
            answer
        }.await()
    }

    private suspend fun getStoredAnswer(id: Long): AnswerData? {
        return withContext(Dispatchers.IO) { dataBase.get(id) }
    }

    fun storeAnswer() {
        viewModelScope.launch {
            dataBase.insert(
                AnswerData(
                    questionId = questionId.toLong(),
                    answer = answer
                )
            )
        }
    }
}

onPause에 저장

override fun onPause() {
    super.onPause()
    answerViewModel.storeAnswer()
}

구현화면

출처

profile
이현우의 개발 브이로그

0개의 댓글