MOBILE-78: Refactor LifecycleManager with startup initializer and fix TrackVisit#714
Closed
enotniy wants to merge 1 commit into
Closed
MOBILE-78: Refactor LifecycleManager with startup initializer and fix TrackVisit#714enotniy wants to merge 1 commit into
enotniy wants to merge 1 commit into
Conversation
… TrackVisit - Replace `lateinit var lifecycleManager` with nullable singleton via `LifecycleManager.instance` (eliminates UninitializedPropertyAccessException race in `invokeOnCompletion` callback on Dispatchers.Default) - Add `MindboxLifecycleInitializer` (androidx.startup) so the lifecycle manager is registered in ContentProvider phase, before Application.onCreate - Guard `wasReinitialized()` with `!firstInitCall.get()` so TrackVisit is not suppressed on the very first `Mindbox.init()` call - Send TrackVisit in the NOT_UPDATED branch when initialised from an Activity (fallback when MindboxLifecycleInitializer is absent from the manifest) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Refactors SDK lifecycle tracking by introducing an androidx.startup initializer and reworking LifecycleManager to be a singleton with callback-based wiring, aiming to prevent init-time races and ensure TrackVisit dispatch reliability.
Changes:
- Replace
Mindbox’slateinit lifecycleManagerwithLifecycleManager.instanceaccess and split init flow into helper methods. - Add
MindboxLifecycleInitializer+ manifest/provider metadata to register lifecycle tracking during the Startup/ContentProvider phase. - Refactor
LifecycleManagerto support “pending” TrackVisit dispatch until callbacks are attached; minor WebView in-app threading changes.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk/src/main/java/cloud/mindbox/mobile_sdk/Mindbox.kt | Refactors init flow; uses singleton lifecycle manager; attaches lifecycle callbacks. |
| sdk/src/main/java/cloud/mindbox/mobile_sdk/managers/MindboxLifecycleInitializer.kt | New Startup initializer to register LifecycleManager early. |
| sdk/src/main/java/cloud/mindbox/mobile_sdk/managers/LifecycleManager.kt | Converts to singleton + callback interface; implements pending TrackVisit behavior. |
| sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/presentation/view/WebViewInappViewHolder.kt | Adjusts close calls to run on the view thread; modifies payload models. |
| sdk/src/main/AndroidManifest.xml | Adds Startup provider metadata for MindboxLifecycleInitializer. |
| sdk/build.gradle | Adds androidx.startup dependency. |
| modulesCommon.gradle | Removes Kover plugin application. |
| gradle/libs.versions.toml | Adds Startup version; removes Kover entries. |
| build.gradle | Removes Kover plugin/task configuration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+600
to
+608
| if (checkResult == ConfigUpdate.UPDATED) { | ||
| setPushServiceHandler(context, pushServices) | ||
| firstInitialization(context.applicationContext, validatedConfiguration) | ||
| } else { | ||
| mindboxScope.launch { | ||
| setPushServiceHandler(context, pushServices) | ||
| } | ||
| MindboxPreferences.uuidDebugEnabled = configuration.uuidDebugEnabled | ||
| }.initState(InitializeLock.State.SAVE_MINDBOX_CONFIG) | ||
| .invokeOnCompletion { throwable -> | ||
| if (throwable == null) { | ||
| if (firstInitCall.get()) { | ||
| val activity = context as? Activity | ||
| if (activity != null && lifecycleManager.isCurrentActivityResumed) { | ||
| inAppMessageManager.registerCurrentActivity(activity) | ||
| mindboxScope.launch { | ||
| inAppMutex.withLock { | ||
| logI("Start inapp manager after init. firstInitCall: ${firstInitCall.get()}") | ||
| if (!firstInitCall.getAndSet(false)) return@launch | ||
| inAppMessageManager.listenEventAndInApp() | ||
| inAppMessageManager.initLogs() | ||
| MindboxEventManager.eventFlow.emit(MindboxEventManager.appStarted()) | ||
| inAppMessageManager.requestConfig().join() | ||
| } | ||
| } | ||
| MindboxEventManager.sendEventsIfExist(context.applicationContext) | ||
| } |
Comment on lines
784
to
790
| private data class NavigationInterceptedPayload( | ||
| @SerializedName("url") | ||
| val url: String | ||
| ) | ||
|
|
||
| private data class ErrorPayload( | ||
| @SerializedName("error") | ||
| val error: String | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
UninitializedPropertyAccessException): replacedlateinit var lifecycleManagerwith a nullable singleton (LifecycleManager.instance) so thatinvokeOnCompletioncallbacks onDispatchers.Defaultcan never access an uninitialised reference.MindboxLifecycleInitializer(androidx.startup): registers the lifecycle manager during the ContentProvider phase, beforeApplication.onCreate, ensuring the instance is always set before any Activity lifecycle events fire.wasReinitialized()(which setsskipNextTrackVisit = true) is now guarded by!firstInitCall.get(), so it is only called on re-initialisation — not on the very firstMindbox.init().MindboxLifecycleInitializeris absent from the manifest and init is called from an Activity, theNOT_UPDATEDbranch now checksisTrackVisitSent()and sends TrackVisit directly if it was missed.Test plan
Application.onCreate— TrackVisit sent once, no crashActivity.onCreate(noMindboxLifecycleInitializerin manifest) — TrackVisit still sentMindbox.inita second time) — TrackVisit not sent again (skipNextTrackVisitrespected)invokeOnCompletionbody —lifecycleManageris never null, noUninitializedPropertyAccessExceptionfeature/MOBILE-78-tests) pass🤖 Generated with Claude Code