From 97e4b27d918e91ca58659f7d27fe70f79b479077 Mon Sep 17 00:00:00 2001 From: Matthias Urhahn <matthias.urhahn@sap.com> Date: Tue, 23 Feb 2021 14:15:01 +0100 Subject: [PATCH] Fix comment field not saving on app moving into the background (EXPOSUREAPP-5108) #2424 * Generalize the lifecycle callback extension on views and extract it. * Use `addOnWindowFocusChangeListener` to detect when the user puts the app into the background. Co-authored-by: Ralf Gehrer <ralfgehrer@users.noreply.github.com> --- .../tabs/common/DiaryCircumstancesTextView.kt | 4 ++ .../util/lists/modular/mods/SavedStateMod.kt | 38 +++++----------- .../coronawarnapp/util/ui/ViewExtensions.kt | 44 +++++++++++++++++++ 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/common/DiaryCircumstancesTextView.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/common/DiaryCircumstancesTextView.kt index 11db3cab1..012c1a42d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/common/DiaryCircumstancesTextView.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/tabs/common/DiaryCircumstancesTextView.kt @@ -36,6 +36,10 @@ class DiaryCircumstancesTextView @JvmOverloads constructor( } imeOptions = EditorInfo.IME_ACTION_DONE setRawInputType(InputType.TYPE_CLASS_TEXT) + // When the user entered something and puts the app into the background + viewTreeObserver.addOnWindowFocusChangeListener { + notifyTextChanged(text.toString()) + } } infoButton = findViewById(R.id.info_button) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/lists/modular/mods/SavedStateMod.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/lists/modular/mods/SavedStateMod.kt index 9356a9ad7..a102f8890 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/lists/modular/mods/SavedStateMod.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/lists/modular/mods/SavedStateMod.kt @@ -2,13 +2,10 @@ package de.rki.coronawarnapp.util.lists.modular.mods import android.os.Parcelable import androidx.annotation.Keep -import androidx.fragment.app.Fragment -import androidx.fragment.app.findFragment import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver -import androidx.lifecycle.OnLifecycleEvent import androidx.recyclerview.widget.RecyclerView import de.rki.coronawarnapp.util.lists.modular.ModularAdapter +import de.rki.coronawarnapp.util.ui.addLifecycleEventCallback import timber.log.Timber @Keep @@ -27,38 +24,25 @@ class SavedStateMod<T : ModularAdapter.VH> : } override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { - val hostLifecycle = recyclerView.hostLifecycle ?: return - - val observer = object : LifecycleObserver { - @OnLifecycleEvent(Lifecycle.Event.ON_STOP) - fun onStop() { - savedStates.clear() - - getAllViewHolders(recyclerView).filterIsInstance<StateSavingVH>().forEach { vh -> - val key = vh.savedStateKey - val state = vh.onSaveState() - if (key != null && state != null) { - savedStates[key] = state - } + recyclerView.addLifecycleEventCallback(type = Lifecycle.Event.ON_STOP) { + savedStates.clear() + + getAllViewHolders(recyclerView).filterIsInstance<StateSavingVH>().forEach { vh -> + val key = vh.savedStateKey + val state = vh.onSaveState() + if (key != null && state != null) { + savedStates[key] = state } - - hostLifecycle.removeObserver(this) } + + true } - hostLifecycle.addObserver(observer) } override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { // NOOP } - private val RecyclerView.hostLifecycle: Lifecycle? - get() = try { - findFragment<Fragment>().viewLifecycleOwner.lifecycle - } catch (e: Exception) { - null - } - private fun getAllViewHolders(recyclerView: RecyclerView): List<RecyclerView.ViewHolder> = try { (0..recyclerView.childCount) .mapNotNull { recyclerView.getChildAt(it) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/ViewExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/ViewExtensions.kt index 285a6d9a1..9acd45a08 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/ViewExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/ViewExtensions.kt @@ -2,7 +2,14 @@ package de.rki.coronawarnapp.util.ui import android.view.View import androidx.databinding.BindingAdapter +import androidx.fragment.app.Fragment +import androidx.fragment.app.findFragment +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.OnLifecycleEvent import de.rki.coronawarnapp.util.recyclerview.ThrottledClickListener +import timber.log.Timber @BindingAdapter("gone") fun View.setGone(gone: Boolean) { @@ -16,3 +23,40 @@ fun View.setInvisible(invisible: Boolean) { fun View.setOnClickListenerThrottled(interval: Long = 300L, listenerBlock: (View) -> Unit) = setOnClickListener(ThrottledClickListener(interval, listenerBlock)) + +val View.hostLifecycle: Lifecycle? + get() = try { + findFragment<Fragment>().viewLifecycleOwner.lifecycle + } catch (e: Exception) { + Timber.v("Couldn't find viewLifecycleOwner for %s", this) + null + } + +/** + * Allows your view to receive callbacks from the host Fragment's lifecycle + * Your callback is invoked when the owning Fragment/Activity receives the specified event state. + * + * @param callback returns true if it should be consumed (one-time callback), or false if it was to stay registered. + * + * @return true if the callback has been added. Otherwise returns false, + * i.e. if the view doesn't have a viewLifecycleOwner due to not being attached. + */ +fun View.addLifecycleEventCallback( + type: Lifecycle.Event, + callback: () -> Boolean +): Boolean { + val hostLifecycle = hostLifecycle ?: return false + + val observer = object : LifecycleObserver { + @OnLifecycleEvent(Lifecycle.Event.ON_ANY) + fun onLifecycleEvent(owner: LifecycleOwner, event: Lifecycle.Event) { + if (event != type) return + Timber.v("%s triggered %s for %s", owner, event, this@addLifecycleEventCallback) + val consumed = callback() + if (consumed) hostLifecycle.removeObserver(this) + } + } + + hostLifecycle.addObserver(observer) + return true +} -- GitLab