この記事は
業務で少しつまづいたのでまとめます。
今更AndroidView&LiveDataの記事なんて誰も読まないかもしれませんが、これは私の知見としての備忘録というか、バグと戦った証というか。バグ日記(?)です。
概要
[仕様]
とある画面にEditTextが配置されています。
その画面に遷移するとRoomから永続化されたテキスト情報を取得し、EditText上に表示させます。
EditText上でテキストを編集し、保存ボタンを押すと、書き換えられた情報が永続化されます。
以上の仕様を踏まえて大体こんな感じのコードを書きました。
// === Fragment ===
override fun onViewCreated() {
viewModel.currentInputText.observe(viewLifecycleOwner) {
binding.editText.setText(it)
}
binding.editText.addTextChangedListener(
afterTextChanged = {
// readEditText()は現在EditText上に表示されているテキスト情報を取得する関数です
viewModel.changeInput(readEditText())
}
)
}
// === ViewModel ===
// currentInputTextはEditTextに入力中のテキスト情報を保持しています
private val _currentInputText = MutableLiveData<String>()
val currentInputText: LiveData<String> = _currentInputText
private var savedText: String? = null
init {
viewModelScope.launch {
savedText = getTextData()
_currentInputText.value = savedStateHandle[ArgsKey.HOGE] ?: savedText
}
}
private suspend fun getTextData(){ // Roomから取得したテキスト情報を取得するUseCaseを呼び出す }
fun changeInput(text: String) {
// 入力情報を更新する
_currentInputText.value = text
savedStateHandle[ArgsKey.HOGE] = _currentInputText.value
}
バグ内容
observeの中が無限に実行されちゃう
viewModel.currentInputText.observe(viewLifecycleOwner) {
binding.editText.setText(it)
}
ここが無限に実行されちゃっていつまで経っても画面遷移が完了しません。
原因
_currentInputText
が変化する → Fragmentのobserve
が呼ばれる → observe
内でeditTextの値が変化する → editTextのListenerに登録されているchangeInput()
が呼ばれる → changeInput()
内で_currentInputText
が変化する → 以下無限ループ
LiveDataとListenerの組み合わせには気をつけた方がよい のかなと思いました。
: Fix
fix: 保存済みのテキストデータと入力中のデータが同じ場合は値を更新しない
fun changeInput(text: String) {
// 入力情報を更新する
if(_currentInputText.value != text) {
_currentInputText.value = text
savedStateHandle[ArgsKey.HOGE] = _currentInputText.value
changeSaveButtonEnable()
}
}
if文で条件を追加しました。
EditTextのカーソル位置が先頭にきちゃう
これはどういうことかというと、EditTextに表示されている文字列を編集するたびにカーソルの位置が先頭にいっちゃう、という問題です。本来なら、編集した地点にカーソルがいてほしいです。
原因
これは、setText()
が問題のようでした。
現在EditTextに表示されているテキスト情報と、_currentInputText.observe
で反映しようとしているテキスト情報が同じである場合、そもそもsetText()
する必要がありませんが、してしまっていることでカーソル位置がおかしくなっているようです。
Fix
fix: 現在EditTextに表示されているテキスト情報と、_currentInputText.observe
で反映しようとしているテキスト情報が同じ場合はsetText()
しない
viewModel.currentInputText.observe(viewLifecycleOwner) {
val currentShownText = readEditText()
setTextIgnoreSameValue(currentShownText, it)
}
private fun setTextIgnoreSameValue(
currentShown: String,
currentInput: String,
) {
if (currentShown != currentInput) {
binding.editText.setText(currentInput)
}
}