진행하고 있는 프로젝트에서 화면이 갑자기 꺼지거나 사용자가 잠시 글 Activity를 떠나도 다시 들어왔을 때 글이 자동으로 복구되는 자동 임시저장 기능을 구현할 것을 요구하였다.
답변을 작성하다 홈 화면으로 나가거나 앱을 강제 종료해도 현재 작성하던 글은 임시저장이 되어야 한다.
임시저장을 하는데 굳이 서버에다 저장할 필요까지는 없을 것 같았다. 통신 Cost도 너무 많이 들고 내 기기에서 데이터를 직접 꺼내올 수 있어야 임시저장 기능의 본질에서 많이 벗어난 것 같았다.
게시글의 갯수는 유동적으로 변하므로 적은 수(정해진 양의 객체)의 객체를 관리하기에 좋은 Shared Preference로 구현하기에는 역부족이라 생각했다.
결국에는 글의 데이터를 구조화해서 DB형식으로 저장할 수밖에 없을 것 같아, 앱 내 DB인 Room을 활용하기로 했다.
작성 중인 글 내용 받아오기
-> 텍스트를 답변 아이디와 함께 DB에 저장
-> 다시 앱에 들어가서 해당 답변 창을 띄울 때 답변 id에 해당하는 텍스트를 DB에서 가져와 Intent로 넘겨
-> 답변 Acitivty에 붙여주기
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와 통신할 수 있는 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
)
)
}
}
}
override fun onPause() {
super.onPause()
answerViewModel.storeAnswer()
}