From 4e578322d3a7722964acc193ab992a3e39ea0d65 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Wed, 27 Nov 2024 09:47:12 -0500 Subject: [PATCH] fix: attach custom study dialog fragment factory in superclass to avoid crash There were two Activities that attached the factory in onCreate, but there were *three* that needed it (SinglePageActivity hosting CongratsPage fragment hosting study options was missing) If you didn't attach in onCreate then you would crash if the fragment/activity lifecycle had done a cycle on you due to background kill or don't keep activities on --- .../main/java/com/ichi2/anki/AnkiActivity.kt | 43 ++++++++++++++++--- .../main/java/com/ichi2/anki/DeckPicker.kt | 5 --- .../com/ichi2/anki/StudyOptionsActivity.kt | 6 --- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt index 41163bf34d56..f8e4c0407c51 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt @@ -59,6 +59,8 @@ import com.ichi2.anki.dialogs.AsyncDialogFragment import com.ichi2.anki.dialogs.DialogHandler import com.ichi2.anki.dialogs.SimpleMessageDialog import com.ichi2.anki.dialogs.SimpleMessageDialog.SimpleMessageDialogListener +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog.CustomStudyListener +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialogFactory import com.ichi2.anki.preferences.Preferences import com.ichi2.anki.preferences.Preferences.Companion.MINIMUM_CARDS_DUE_FOR_NOTIFICATION import com.ichi2.anki.preferences.sharedPrefs @@ -73,13 +75,19 @@ import com.ichi2.compat.customtabs.CustomTabsHelper import com.ichi2.libanki.Collection import com.ichi2.themes.Themes import com.ichi2.utils.AdaptionUtil +import com.ichi2.utils.ExtendedFragmentFactory import com.ichi2.utils.KotlinCleanup import timber.log.Timber import androidx.browser.customtabs.CustomTabsIntent.Builder as CustomTabsIntentBuilder @UiThread @KotlinCleanup("set activityName") -open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener, ShortcutGroupProvider, AnkiActivityProvider { +open class AnkiActivity : + AppCompatActivity, + CustomStudyListener, + SimpleMessageDialogListener, + ShortcutGroupProvider, + AnkiActivityProvider { /** * Receiver that informs us when a broadcast listen in [broadcastsActions] is received. @@ -111,6 +119,16 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener, Shortc // Set the theme Themes.setTheme(this) Themes.disableXiaomiForceDarkMode(this) + + // We may use the CustomStudyDialog in: + // - SinglePageActivity (CongratsPage fragment..) + // - StudyOptionsActivity + // - DeckPicker + // - anyone could add it, and it will crash when restoring from background unless + // our custom fragment factory is added first, so add it in superclass + val customStudyDialogFactory = CustomStudyDialogFactory({ this.getColUnsafe }, this) + customStudyDialogFactory.attachToActivity(this) + super.onCreate(savedInstanceState) // Disable the notifications bar if running under the test monkey. if (AdaptionUtil.isUserATestClient) { @@ -375,20 +393,35 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener, Shortc startActivity(deckPicker) } - fun showProgressBar() { + override fun showProgressBar() { val progressBar = findViewById(R.id.progress_bar) if (progressBar != null) { progressBar.visibility = View.VISIBLE } } - open fun hideProgressBar() { + override fun hideProgressBar() { val progressBar = findViewById(R.id.progress_bar) if (progressBar != null) { progressBar.visibility = View.GONE } } + override fun onCreateCustomStudySession() { + // Activities that host a StudyDialogFragment should implement this + // guide developers to the solution with helpful information on where + Timber.w("onCreateCustomStudySession unimplemented - almost certainly not what you want") + Timber.w("if your Activity hosts CustomStudyDialog you need to override this method") + Timber.w(Exception("stack trace for reference")) + } + override fun onExtendStudyLimits() { + // Activities that host a StudyDialogFragment should implement this + // guide developers to the solution with helpful information on where + Timber.w("onExtendStudyLimits unimplemented - almost certainly not what you want") + Timber.w("if your Activity hosts CustomStudyDialog you need to override this method") + Timber.w(Exception("stack trace for reference")) + } + internal fun mayOpenUrl(url: Uri) { val success = customTabActivityHelper.mayLaunchUrl(url, null, null) if (!success) { @@ -457,7 +490,7 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener, Shortc * * @param newFragment the DialogFragment you want to show */ - open fun showDialogFragment(newFragment: DialogFragment) { + override fun showDialogFragment(newFragment: DialogFragment) { runOnUiThread { showDialogFragment(this, newFragment) } @@ -579,7 +612,7 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener, Shortc } // Dismiss whatever dialog is showing - fun dismissAllDialogFragments() { + override fun dismissAllDialogFragments() { // trying to pop fragment manager back state crashes if state already saved if (!supportFragmentManager.isStateSaved) { supportFragmentManager.popBackStack( diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt index 1b3ec8821bc6..5da2125f09a4 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt @@ -127,8 +127,6 @@ import com.ichi2.anki.dialogs.SyncErrorDialog import com.ichi2.anki.dialogs.SyncErrorDialog.Companion.newInstance import com.ichi2.anki.dialogs.SyncErrorDialog.SyncErrorDialogListener import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog -import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog.CustomStudyListener -import com.ichi2.anki.dialogs.customstudy.CustomStudyDialogFactory import com.ichi2.anki.export.ActivityExportingDelegate import com.ichi2.anki.export.ExportDialogFragment import com.ichi2.anki.export.ExportDialogsFactory @@ -239,7 +237,6 @@ open class DeckPicker : ImportDialogListener, MediaCheckDialogListener, OnRequestPermissionsResultCallback, - CustomStudyListener, ChangeManager.Subscriber, SyncCompletionListener, ImportColpkgListener, @@ -326,7 +323,6 @@ open class DeckPicker : private var toolbarSearchItem: MenuItem? = null private var toolbarSearchView: AccessibleSearchView? = null - private lateinit var customStudyDialogFactory: CustomStudyDialogFactory override val permissionScreenLauncher = recreateActivityResultLauncher() @@ -471,7 +467,6 @@ open class DeckPicker : return } exportingDelegate = ActivityExportingDelegate(this) { getColUnsafe } - customStudyDialogFactory = CustomStudyDialogFactory({ getColUnsafe }, this).attachToActivity(this) // Then set theme and content view super.onCreate(savedInstanceState) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.kt index d4c6bde2106a..67943594920d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.kt @@ -25,11 +25,8 @@ import androidx.lifecycle.lifecycleScope import anki.collection.OpChanges import com.ichi2.anki.CollectionManager.withCol import com.ichi2.anki.StudyOptionsFragment.StudyOptionsListener -import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog.CustomStudyListener -import com.ichi2.anki.dialogs.customstudy.CustomStudyDialogFactory import com.ichi2.libanki.ChangeManager import com.ichi2.ui.RtlCompliantActionProvider -import com.ichi2.utils.ExtendedFragmentFactory import com.ichi2.widget.WidgetStatus import kotlinx.coroutines.launch import timber.log.Timber @@ -37,7 +34,6 @@ import timber.log.Timber class StudyOptionsActivity : AnkiActivity(), StudyOptionsListener, - CustomStudyListener, ChangeManager.Subscriber { private var undoState = UndoState() @@ -46,8 +42,6 @@ class StudyOptionsActivity : if (showedActivityFailedScreen(savedInstanceState)) { return } - val customStudyDialogFactory = CustomStudyDialogFactory({ this.getColUnsafe }, this) - customStudyDialogFactory.attachToActivity(this) super.onCreate(savedInstanceState) setContentView(R.layout.studyoptions) enableToolbar().apply { title = "" }