Skip to content

Commit dc286b1

Browse files
david-allisonlukstbit
authored andcommitted
feat: launchCatching - handle StorageNotConfiguredException
Assisted-by: Claude Fable 5
1 parent f765157 commit dc286b1

2 files changed

Lines changed: 43 additions & 0 deletions

File tree

AnkiDroid/src/main/java/com/ichi2/anki/CoroutineHelpers.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ import com.ichi2.anki.common.destinations.DeckOptionsDestination
3434
import com.ichi2.anki.dialogs.DatabaseErrorDialog
3535
import com.ichi2.anki.dialogs.DatabaseErrorDialog.DatabaseErrorDialogType
3636
import com.ichi2.anki.exception.StorageAccessException
37+
import com.ichi2.anki.exception.StorageNotConfiguredException
3738
import com.ichi2.anki.libanki.exception.InvalidSearchException
3839
import com.ichi2.anki.pages.fromCurrentDeck
3940
import com.ichi2.anki.pages.toIntent
4041
import com.ichi2.anki.snackbar.showSnackbar
42+
import com.ichi2.anki.startup.redirectToMainEntryPoint
4143
import com.ichi2.anki.ui.internationalization.sentenceCase
4244
import com.ichi2.anki.utils.openUrl
4345
import com.ichi2.utils.create
@@ -190,6 +192,12 @@ suspend fun <T> FragmentActivity.runCatching(
190192
Timber.w(exc, errorMessage)
191193
exc.localizedMessage?.let { showSnackbar(it) }
192194
}
195+
is StorageNotConfiguredException -> {
196+
// expected before first-run setup completes: no crash report
197+
// edge case when 'ensureStorageIsReady' was insufficient
198+
Timber.w(exc, errorMessage)
199+
if (!isFinishing) redirectToMainEntryPoint()
200+
}
193201
is BackendNetworkException, is BackendSyncException, is StorageAccessException, is BackendCardTypeException -> {
194202
// these exceptions do not generate worthwhile crash reports
195203
Timber.i("Showing error dialog but not sending a crash report.")
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
package com.ichi2.anki
4+
5+
import androidx.fragment.app.FragmentActivity
6+
import androidx.test.ext.junit.runners.AndroidJUnit4
7+
import com.ichi2.anki.exception.StorageNotConfiguredException
8+
import org.junit.Test
9+
import org.junit.runner.RunWith
10+
import org.robolectric.Robolectric
11+
import org.robolectric.Shadows.shadowOf
12+
import kotlin.test.assertEquals
13+
import kotlin.test.assertNotNull
14+
import kotlin.test.assertTrue
15+
16+
@RunWith(AndroidJUnit4::class)
17+
class CoroutineHelpersRobolectricTest : RobolectricTest() {
18+
/**
19+
* A [StorageNotConfiguredException] escaping a coroutine means an activity raced or
20+
* outlived its [ensureStorageIsReady][com.ichi2.anki.startup.ensureStorageIsReady] check:
21+
* the user should be sent to the main entry point, which handles storage setup.
22+
*/
23+
@Test
24+
fun `launchCatchingTask redirects to main entry point when storage is not configured`() {
25+
val activity = Robolectric.buildActivity(FragmentActivity::class.java).create().get()
26+
27+
activity.launchCatchingTask { throw StorageNotConfiguredException() }
28+
advanceRobolectricLooper()
29+
30+
assertTrue(activity.isFinishing, "activity should finish")
31+
val redirect = shadowOf(activity).nextStartedActivity
32+
assertNotNull(redirect, "the main entry point should be opened")
33+
assertEquals(IntentHandler::class.qualifiedName, redirect.component?.className)
34+
}
35+
}

0 commit comments

Comments
 (0)