Skip to content

Commit 76c4d73

Browse files
authored
Defer tooltip experiment history until render confirmation (#35)
1 parent 1baac8c commit 76c4d73

3 files changed

Lines changed: 48 additions & 11 deletions

File tree

nubrick/src/main/kotlin/io/nubrick/nubrick/component/trigger.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import kotlinx.serialization.json.Json
2929
internal class TriggerViewModel(
3030
internal val container: Container,
3131
internal val user: NubrickUser,
32-
private val onTooltip: ((data: String) -> Unit)? = null,
32+
private val onTooltip: ((data: String, experimentId: String) -> Unit)? = null,
3333
) : ViewModel() {
3434
private val ignoreFirstUserEventToForegroundEvent = mutableStateOf(true)
3535
internal val modalStacks = mutableStateListOf<UIRootBlock>()
@@ -75,13 +75,15 @@ internal class TriggerViewModel(
7575
}
7676
GlobalScope.launch(Dispatchers.IO) {
7777
self.container.handleNubrickEvent(event)
78-
self.container.fetchTriggerContent(event.name, kinds).onSuccess { (kind, block) ->
78+
self.container.fetchTriggerContent(event.name, kinds).onSuccess { triggerContent ->
79+
val kind = triggerContent.kind
80+
val block = triggerContent.block
7981
if (kind == ExperimentKind.TOOLTIP) {
8082
self.onTooltip?.let { callback ->
8183
val jsonString = Json.encodeToString(UIBlock.encode(block))
8284
// Flutter MethodChannel requires calls on the main thread
8385
GlobalScope.launch(Dispatchers.Main) {
84-
callback(jsonString)
86+
callback(jsonString, triggerContent.experimentId)
8587
}
8688
}
8789
} else {

nubrick/src/main/kotlin/io/nubrick/nubrick/data/container.kt

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ internal data class ExtractedVariant(
2727
val variant: ExperimentVariant,
2828
)
2929

30+
internal data class TriggerContent(
31+
val experimentId: String,
32+
val kind: ExperimentKind,
33+
val block: UIBlock,
34+
)
35+
3036
class NotFoundException : Exception("Not found")
3137
class FailedToDecodeException : Exception("Failed to decode")
3238
class SkipHttpRequestException : Exception("Skip http request")
@@ -53,8 +59,9 @@ internal interface Container {
5359
): Result<JsonElement>
5460

5561
suspend fun fetchEmbedding(experimentId: String, componentId: String? = null): Result<UIBlock>
56-
suspend fun fetchTriggerContent(trigger: String, kinds: List<ExperimentKind>): Result<Pair<ExperimentKind, UIBlock>>
62+
suspend fun fetchTriggerContent(trigger: String, kinds: List<ExperimentKind>): Result<TriggerContent>
5763
suspend fun fetchRemoteConfig(experimentId: String): Result<ExperimentVariant>
64+
fun appendExperimentHistory(experimentId: String)
5865

5966
fun storeNativeCrash(throwable: Throwable)
6067
fun sendFlutterCrash(crashEvent: TrackCrashEvent)
@@ -182,7 +189,11 @@ internal class ContainerImpl(
182189
variantId = variantId
183190
)
184191
)
185-
this.databaseRepository.appendExperimentHistory(extracted.experimentId)
192+
// Tooltip is a Flutter-only flow. Persist tooltip history only after
193+
// Flutter confirms the tooltip actually started rendering.
194+
if (extracted.kind != ExperimentKind.TOOLTIP) {
195+
this.databaseRepository.appendExperimentHistory(extracted.experimentId)
196+
}
186197
val componentId = extractComponentId(extracted.variant) ?: return Result.failure(NotFoundException())
187198
val component =
188199
this.componentRepository.fetchComponent(extracted.experimentId, componentId).getOrElse {
@@ -191,7 +202,7 @@ internal class ContainerImpl(
191202
return Result.success(component)
192203
}
193204

194-
override suspend fun fetchTriggerContent(trigger: String, kinds: List<ExperimentKind>): Result<Pair<ExperimentKind, UIBlock>> {
205+
override suspend fun fetchTriggerContent(trigger: String, kinds: List<ExperimentKind>): Result<TriggerContent> {
195206
// send the user track event and save it to database
196207
this.trackRepository.trackEvent(TrackUserEvent(trigger))
197208
this.databaseRepository.appendUserEvent(trigger)
@@ -213,13 +224,23 @@ internal class ContainerImpl(
213224
variantId = variantId
214225
)
215226
)
216-
this.databaseRepository.appendExperimentHistory(extracted.experimentId)
227+
// Tooltip is a Flutter-only flow. Persist tooltip history only after
228+
// Flutter confirms the tooltip actually started rendering.
229+
if (extracted.kind != ExperimentKind.TOOLTIP) {
230+
this.databaseRepository.appendExperimentHistory(extracted.experimentId)
231+
}
217232
val componentId = extractComponentId(extracted.variant) ?: return Result.failure(NotFoundException())
218233
val component =
219234
this.componentRepository.fetchComponent(extracted.experimentId, componentId).getOrElse {
220235
return Result.failure(it)
221236
}
222-
return Result.success(extracted.kind to component)
237+
return Result.success(
238+
TriggerContent(
239+
experimentId = extracted.experimentId,
240+
kind = extracted.kind,
241+
block = component,
242+
)
243+
)
223244
}
224245

225246
override suspend fun fetchRemoteConfig(experimentId: String): Result<ExperimentVariant> {
@@ -241,6 +262,10 @@ internal class ContainerImpl(
241262
return Result.success(extracted.variant)
242263
}
243264

265+
override fun appendExperimentHistory(experimentId: String) {
266+
this.databaseRepository.appendExperimentHistory(experimentId)
267+
}
268+
244269
private fun extractVariant(
245270
configs: ExperimentConfigs,
246271
kinds: List<ExperimentKind>,

nubrick/src/main/kotlin/io/nubrick/nubrick/sdk.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ fun NubrickProvider(
135135
class NubrickClient private constructor(
136136
private val config: Config,
137137
context: Context,
138-
onTooltip: ((data: String) -> Unit)? = null,
138+
onTooltip: ((data: String, experimentId: String) -> Unit)? = null,
139139
) {
140140
private val db: SQLiteDatabase
141141
val user: NubrickUser
@@ -145,7 +145,11 @@ class NubrickClient private constructor(
145145

146146
companion object {
147147
@FlutterBridgeApi
148-
fun create(config: Config, context: Context, onTooltip: (data: String) -> Unit): NubrickClient {
148+
fun create(
149+
config: Config,
150+
context: Context,
151+
onTooltip: (data: String, experimentId: String) -> Unit
152+
): NubrickClient {
149153
return NubrickClient(config, context, onTooltip)
150154
}
151155
}
@@ -188,7 +192,7 @@ class NubrickExperiment {
188192
user: NubrickUser,
189193
db: SQLiteDatabase,
190194
context: Context,
191-
onTooltip: ((data: String) -> Unit)? = null,
195+
onTooltip: ((data: String, experimentId: String) -> Unit)? = null,
192196
) {
193197
this.container = ContainerImpl(
194198
config = config.copy(onEvent = { event ->
@@ -225,6 +229,12 @@ class NubrickExperiment {
225229
this.container.sendFlutterCrash(crashEvent)
226230
}
227231

232+
@FlutterBridgeApi
233+
fun appendTooltipExperimentHistory(experimentId: String) {
234+
if (experimentId.isEmpty()) return
235+
this.container.appendExperimentHistory(experimentId)
236+
}
237+
228238
@Composable
229239
fun Overlay() {
230240
Trigger(trigger = this.trigger)

0 commit comments

Comments
 (0)