Fix crash caused by immediately removed TextWatchers
This commit is contained in:
@@ -13,7 +13,13 @@ import android.widget.Toast
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
@@ -21,7 +27,9 @@ import kotlinx.coroutines.channels.ChannelResult
|
|||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.channels.onClosed
|
import kotlinx.coroutines.channels.onClosed
|
||||||
import kotlinx.coroutines.channels.onFailure
|
import kotlinx.coroutines.channels.onFailure
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
fun SwipeRefreshLayout.refreshRequestFlow(logger: Logger): Flow<Unit> = callbackFlow {
|
fun SwipeRefreshLayout.refreshRequestFlow(logger: Logger): Flow<Unit> = callbackFlow {
|
||||||
@@ -37,16 +45,16 @@ fun SwipeRefreshLayout.refreshRequestFlow(logger: Logger): Flow<Unit> = callback
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TextView.textChangesFlow(logger: Logger): Flow<CharSequence?> = callbackFlow {
|
fun TextView.textChangesLiveData(logger: Logger): LiveData<CharSequence?> = callbackFlow {
|
||||||
logger.v { "textChangesFlow() called" }
|
logger.v { "textChangesLiveData() called" }
|
||||||
val textWatcher = doAfterTextChanged {
|
val textWatcher = doAfterTextChanged {
|
||||||
trySend(it).logErrors("textChangesFlow", logger)
|
trySend(it).logErrors("textChangesFlow", logger)
|
||||||
}
|
}
|
||||||
awaitClose {
|
awaitClose {
|
||||||
logger.d { "textChangesFlow: flow is closing" }
|
logger.d { "textChangesLiveData: flow is closing" }
|
||||||
removeTextChangedListener(textWatcher)
|
removeTextChangedListener(textWatcher)
|
||||||
}
|
}
|
||||||
}
|
}.asLiveData() // Use asLiveData() to make sure close() is called with a delay to avoid IndexOutOfBoundsException
|
||||||
|
|
||||||
fun <T> ChannelResult<T>.logErrors(methodName: String, logger: Logger): ChannelResult<T> {
|
fun <T> ChannelResult<T>.logErrors(methodName: String, logger: Logger): ChannelResult<T> {
|
||||||
onFailure { logger.e(it) { "$methodName: can't send event" } }
|
onFailure { logger.e(it) { "$methodName: can't send event" } }
|
||||||
@@ -66,19 +74,19 @@ fun EditText.checkIfInputIsEmpty(
|
|||||||
logger.d { "Input text is \"$text\"" }
|
logger.d { "Input text is \"$text\"" }
|
||||||
return text.ifEmpty {
|
return text.ifEmpty {
|
||||||
inputLayout.error = resources.getString(stringId)
|
inputLayout.error = resources.getString(stringId)
|
||||||
lifecycleOwner.lifecycleScope.launch {
|
val textChangesLiveData = textChangesLiveData(logger)
|
||||||
waitUntilNotEmpty(logger)
|
textChangesLiveData.observe(lifecycleOwner, object : Observer<CharSequence?> {
|
||||||
inputLayout.error = null
|
override fun onChanged(value: CharSequence?) {
|
||||||
}
|
if (value.isNullOrBlank().not()) {
|
||||||
|
inputLayout.error = null
|
||||||
|
textChangesLiveData.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun EditText.waitUntilNotEmpty(logger: Logger) {
|
|
||||||
textChangesFlow(logger).filterNotNull().first { it.isNotEmpty() }
|
|
||||||
logger.v { "waitUntilNotEmpty() returned" }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> SharedPreferences.prefsChangeFlow(
|
fun <T> SharedPreferences.prefsChangeFlow(
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
valueReader: SharedPreferences.() -> T,
|
valueReader: SharedPreferences.() -> T,
|
||||||
@@ -114,10 +122,8 @@ fun View.hideKeyboard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Context.isDarkThemeOn(): Boolean {
|
fun Context.isDarkThemeOn(): Boolean {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) resources.configuration.isNightModeActive
|
||||||
resources.configuration.isNightModeActive
|
else resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
|
||||||
else
|
|
||||||
resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> LifecycleOwner.collectWhenResumed(flow: Flow<T>, collector: FlowCollector<T>) {
|
fun <T> LifecycleOwner.collectWhenResumed(flow: Flow<T>, collector: FlowCollector<T>) {
|
||||||
|
|||||||
Reference in New Issue
Block a user