Skip to content

Commit fb4b680

Browse files
authored
Ensure library init is async (#7243)
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1212198371544796 ### Description Ensure URL prediction library initialization is async. Bumped the library version too. ### Steps to test this PR _Test_ - [ ] Repeat tests from #7126
1 parent a99ab66 commit fb4b680

File tree

4 files changed

+72
-4
lines changed

4 files changed

+72
-4
lines changed

app/src/main/java/com/duckduckgo/app/browser/omnibar/QueryUrlConverter.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ import com.duckduckgo.privacy.config.api.PrivacyConfigCallbackPlugin
3333
import com.duckduckgo.urlpredictor.Decision
3434
import com.squareup.anvil.annotations.ContributesBinding
3535
import com.squareup.anvil.annotations.ContributesMultibinding
36+
import dagger.SingleInstanceIn
3637
import kotlinx.coroutines.CoroutineScope
3738
import kotlinx.coroutines.launch
3839
import javax.inject.Inject
3940

4041
@ContributesBinding(AppScope::class, boundType = OmnibarEntryConverter::class)
4142
@ContributesMultibinding(scope = AppScope::class, boundType = PrivacyConfigCallbackPlugin::class)
43+
@SingleInstanceIn(scope = AppScope::class)
4244
class QueryUrlConverter @Inject constructor(
4345
private val requestRewriter: RequestRewriter,
4446
private val androidBrowserConfigFeature: AndroidBrowserConfigFeature,
@@ -67,7 +69,7 @@ class QueryUrlConverter @Inject constructor(
6769
val isUrl = when (queryOrigin) {
6870
is QueryOrigin.FromAutocomplete -> queryOrigin.isNav
6971
is QueryOrigin.FromUser, QueryOrigin.FromBookmark -> {
70-
if (useUrlPredictor) {
72+
if (useUrlPredictor && queryUrlPredictor.isReady()) {
7173
var queryClassification = queryUrlPredictor.classify(input = searchQuery)
7274

7375
if (extractUrlFromQuery) {

app/src/main/java/com/duckduckgo/app/browser/omnibar/QueryUrlPredictor.kt

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,51 @@
1616

1717
package com.duckduckgo.app.browser.omnibar
1818

19+
import com.duckduckgo.app.di.AppCoroutineScope
20+
import com.duckduckgo.common.utils.DispatcherProvider
1921
import com.duckduckgo.di.scopes.AppScope
2022
import com.duckduckgo.urlpredictor.Decision
2123
import com.duckduckgo.urlpredictor.UrlPredictor
2224
import com.squareup.anvil.annotations.ContributesBinding
25+
import dagger.SingleInstanceIn
26+
import kotlinx.coroutines.CoroutineScope
27+
import kotlinx.coroutines.launch
28+
import logcat.logcat
2329
import javax.inject.Inject
2430

2531
interface QueryUrlPredictor {
2632

33+
/**
34+
* Indicates whether the UrlPredictor native library is ready to be used.
35+
*
36+
* This method is a convenience wrapper around {@link UrlPredictor#isInitialized()},
37+
* exposing the initialization state at the application layer.
38+
*
39+
* Initialization is triggered asynchronously in this class' constructor, via:
40+
*
41+
* <pre>
42+
* coroutineScope.launch(dispatcherProvider.io()) {
43+
* UrlPredictor.init()
44+
* }
45+
* </pre>
46+
*
47+
* Until initialization completes, calls to {@link #classify(String)} will fail
48+
* if invoked directly on the underlying UrlPredictor instance; therefore,
49+
* callers may use {@code isReady()} to determine whether classification is safe.
50+
*
51+
* This method does not block and is safe to call from any thread.
52+
*
53+
* @return {@code true} if the UrlPredictor native library has been loaded and
54+
* the predictor instance is fully initialized; {@code false} otherwise.
55+
*
56+
* @see UrlPredictor#isInitialized()
57+
* @see UrlPredictor#init()
58+
*/
59+
fun isReady(): Boolean
60+
2761
/**
2862
* Classifies the input string as a navigable URL or a search query.
63+
* You should be calling [isReady] before calling this method to make sure the library is properly initialized
2964
*/
3065
fun classify(input: String): Decision
3166
}
@@ -34,6 +69,22 @@ interface QueryUrlPredictor {
3469
* Wrapper around the native implementation of [UrlPredictor] to allow unit testing.
3570
*/
3671
@ContributesBinding(scope = AppScope::class)
37-
class QueryUrlPredictorImpl @Inject constructor() : QueryUrlPredictor {
38-
override fun classify(input: String): Decision = UrlPredictor.classify(input)
72+
@SingleInstanceIn(scope = AppScope::class)
73+
class QueryUrlPredictorImpl @Inject constructor(
74+
@AppCoroutineScope coroutineScope: CoroutineScope,
75+
dispatcherProvider: DispatcherProvider,
76+
) : QueryUrlPredictor {
77+
78+
init {
79+
coroutineScope.launch(dispatcherProvider.io()) {
80+
UrlPredictor.init()
81+
logcat { "UrlPredictor library initialized" }
82+
}
83+
}
84+
85+
override fun isReady(): Boolean {
86+
return UrlPredictor.isInitialized()
87+
}
88+
89+
override fun classify(input: String): Decision = UrlPredictor.get().classify(input)
3990
}

app/src/test/java/com/duckduckgo/app/browser/QueryUrlConverterTest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class QueryUrlConverterTest {
7777
fun setup() {
7878
whenever(variantManager.getVariantKey()).thenReturn("")
7979
whenever(duckChat.isEnabled()).thenReturn(true)
80+
whenever(queryUrlPredictor.isReady()).thenReturn(true)
8081
androidBrowserConfigFeature.hideDuckAiInSerpKillSwitch().setRawStoredState(State(true))
8182
}
8283

@@ -455,6 +456,20 @@ class QueryUrlConverterTest {
455456
verify(queryUrlPredictor).classify("foo")
456457
}
457458

459+
@Test
460+
fun `when url predictor is not ready and config enabled then url predictor not used`() {
461+
val testee = createTestee(useUrlPredictorEnabled = false)
462+
whenever(queryUrlPredictor.classify("foo")).thenReturn(Decision.Search("foo"))
463+
whenever(queryUrlPredictor.isReady()).thenReturn(false)
464+
androidBrowserConfigFeature.useUrlPredictor().setRawStoredState(State(true))
465+
testee.onPrivacyConfigDownloaded()
466+
val input = "foo"
467+
val expected = "foo"
468+
val result = testee.convertQueryToUrl(input, queryOrigin = QueryOrigin.FromUser, extractUrlFromQuery = false)
469+
assertDuckDuckGoSearchQuery(expected, result)
470+
verify(queryUrlPredictor, never()).classify("foo")
471+
}
472+
458473
@Test
459474
fun `when url predictor enabled and extract url enabled but not url then search returned`() {
460475
val testee = createTestee(useUrlPredictorEnabled = true)

versions.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ version.com.duckduckgo.netguard..netguard-android=1.10.2
8686

8787
version.com.duckduckgo.synccrypto..sync-crypto-android=0.7.0
8888

89-
version.com.duckduckgo.urlpredictor..url-predictor-android=0.3.4
89+
version.com.duckduckgo.urlpredictor..url-predictor-android=0.3.10
9090

9191
version.com.frybits.harmony..harmony=1.2.6
9292

0 commit comments

Comments
 (0)