8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【バグ日記】EditTextをLiveDataで持つむずかしさと立ち向かった

Last updated at Posted at 2023-11-13

この記事は

業務で少しつまづいたのでまとめます。
今更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)
}

ここが無限に実行されちゃっていつまで経っても画面遷移が完了しません。

:bug: 原因

_currentInputTextが変化する → Fragmentのobserveが呼ばれる → observe内でeditTextの値が変化する → editTextのListenerに登録されているchangeInput()が呼ばれる → changeInput()内で_currentInputTextが変化する → 以下無限ループ

LiveDataとListenerの組み合わせには気をつけた方がよい のかなと思いました。

:bulb: : Fix

fix: 保存済みのテキストデータと入力中のデータが同じ場合は値を更新しない

fun changeInput(text: String) {
    // 入力情報を更新する
    if(_currentInputText.value != text) {
        _currentInputText.value = text
        savedStateHandle[ArgsKey.HOGE] = _currentInputText.value
        changeSaveButtonEnable()
    }
}

if文で条件を追加しました。

EditTextのカーソル位置が先頭にきちゃう

これはどういうことかというと、EditTextに表示されている文字列を編集するたびにカーソルの位置が先頭にいっちゃう、という問題です。本来なら、編集した地点にカーソルがいてほしいです。

:bug: 原因

これは、setText()が問題のようでした。
現在EditTextに表示されているテキスト情報と、_currentInputText.observeで反映しようとしているテキスト情報が同じである場合、そもそもsetText()する必要がありませんが、してしまっていることでカーソル位置がおかしくなっているようです。

:bulb: 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)
    }
}
8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?