Skip to content

refactor: migrate kit infra leaf interfaces to Kotlin (Lane E) - #777

Merged
thomson-t merged 5 commits into
workstation/kotlin-migrationfrom
kotlin/lane-e-kit-infra-leaf
Sep 10, 2026
Merged

refactor: migrate kit infra leaf interfaces to Kotlin (Lane E)#777
thomson-t merged 5 commits into
workstation/kotlin-migrationfrom
kotlin/lane-e-kit-infra-leaf

Conversation

@nickolas-dimitrakas

Copy link
Copy Markdown
Contributor

Lane E — kit infrastructure leaf classes (5 files, 193 LOC)

Part of the Java→Kotlin migration (PLAN.md). Converts:

  • internal/KitsLoadedListener
  • internal/JsonReportingMessage
  • internal/ReportingManager
  • internal/KitContext
  • internal/KitManager — on the frozen-internals list; 4 kit files implement or depend on it

API surface

bash scripts/api-guard.sh check passes. One intentional baseline update: KitManager$KitStatus gains a synthetic getEntries() accessor, which every Kotlin 1.9+ enum conversion adds automatically (kotlin.enums.EnumEntries). Purely additive — same pattern already accepted for WrapperSdk and ApplicationContextWrapper$MethodType from earlier conversions.

Two non-mechanical fixes were needed to keep this a true no-op for callers:

  • KitManager's six Activity/Bundle lifecycle params go nullable (onActivityCreated, onActivityStarted, onActivityResumed, onActivityStopped, onActivitySaveInstanceState, onActivityDestroyed). AppStateManager.kt already calls these with nullable arguments — Java's unannotated platform types were silently permissive here, and a straight non-null translation broke that existing caller.
  • getSurveyUrl's Map<String, List<String>> parameter becomes MutableMap<String, List<String>>. Kotlin auto-wildcards a covariant Map value type when used in parameter position, which broke the existing Java overrides in KitManagerImpl and KitFrameworkWrapper (name clash... have the same erasure, yet neither overrides the other) even though the erased javap signature the guard checks is unaffected either way.

Both are worth folding into PLAN.md's hazard table — filing as a follow-up.

Validation

  • ./gradlew :android-core:compileDebugKotlin :android-core:compileDebugJavaWithJavac — clean
  • ./gradlew :android-kit-base:compileDebugKotlin :android-kit-base:compileDebugJavaWithJavac — clean (verifies KitManagerImpl still overrides correctly)
  • bash scripts/api-guard.sh check — passed after the one intentional baseline update above
  • bash scripts/kotlin-migration-progress.sh — 22.4% → 23.3%
  • Local ktlintCheck/unit tests could not run in this environment — Maven Central is rate-limiting dependency resolution (429) for powermock, kotlinx-coroutines-test, and ktlint-cli. The pre-commit hook's own ktlint run (cached deps) passed clean. CI will run the full suite.

Draft while CI runs — flip to ready once green.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Kotlin migration progress

This PR migrates 193 lines of Java to Kotlin (+0.9 pp).

Module Kotlin LOC Java LOC Java (facade, staying) Java left to migrate Kotlin %
android-core 3400 22264 9241 13023 13.2%
android-kit-base 557 5380 1651 3729 9.4%
kits/rokt/rokt 1122 0 0 0 100.0%
Total 5079 27644 10892 16752 15.5%

Migration goal progress: 23.3% (Kotlin LOC as a share of everything that is not a designated public-API facade.)

127 Java files / 48 Kotlin files in scope.

Largest Java files still to migrate
LOC File
1514 android-core/src/main/java/com/mparticle/internal/ConfigManager.java
1441 android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java
1089 android-core/src/main/java/com/mparticle/internal/MessageManager.java
1069 android-kit-base/src/main/java/com/mparticle/kits/KitConfiguration.java
858 android-core/src/main/java/com/mparticle/internal/MPUtility.java
840 android-core/src/main/java/com/mparticle/internal/MParticleJSInterface.java
809 android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java
786 android-core/src/main/java/com/mparticle/internal/database/services/MParticleDBManager.java
490 android-kit-base/src/main/java/com/mparticle/kits/mappings/CustomMapping.java
482 android-core/src/main/java/com/mparticle/internal/MParticleApiClientImpl.java
431 android-core/src/main/java/com/mparticle/identity/MParticleIdentityClientImpl.java
375 android-core/src/main/java/com/mparticle/internal/UploadHandler.java
350 android-core/src/main/java/com/mparticle/internal/DeviceAttributes.java
322 android-core/src/main/java/com/mparticle/networking/MParticleBaseClientImpl.java
319 android-core/src/main/java/com/mparticle/internal/MessageHandler.java

thomson-t
thomson-t previously approved these changes Sep 10, 2026
@thomson-t

Copy link
Copy Markdown
Collaborator

Rebased onto latest main via the merge in #789. Until #789 is merge-committed into workstation/kotlin-migration, this PR's GitHub diff will temporarily also show the main sync (workflows, AGENTS.md, 6.0.4, junit 1.3.0). The Lane E commits themselves are unchanged.

@thomson-t
thomson-t marked this pull request as ready for review September 10, 2026 17:11
@thomson-t
thomson-t requested a review from a team as a code owner September 10, 2026 17:11
@cursor

cursor Bot commented Sep 10, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches the frozen KitManager contract and nullability/map typing that all kit implementations override; changes are intended to be behavior-preserving but warrant careful review of Java/Kotlin interop at call sites.

Overview
Continues the Java→Kotlin migration by replacing five kit-related leaf types under com.mparticle.internal (KitsLoadedListener, JsonReportingMessage, ReportingManager, KitContext, and KitManager) with Kotlin equivalents, with matching updates in android-core.api.

KitManager is the meaningful surface change: identity completion hooks (onIdentifyCompleted, login/logout/modify) and several activity lifecycle methods now take nullable parameters so existing callers (e.g. AppStateManager) keep compiling; getSurveyUrl uses a MutableMap parameter so Java overrides in kit-base still match. Many former Java getters become Kotlin properties, and logEvent / related kit logging signatures accept nullable events where the Kotlin interface declares them.

JsonReportingMessage moves timestamp/moduleId/sessionId to Kotlin property style (var sessionId, read-only timestamp/moduleId). KitContext is an open class with the same application-context wrapping behavior.

The public API baseline picks up one additive enum helper: KitManager.KitStatus.getEntries() (standard Kotlin 1.9+ enum entries). Android tests (KitFrameworkWrapperTest, KitManagerImplTest) adjust stubs/overrides for the new nullable identity and event types.

Reviewed by Cursor Bugbot for commit edab2c4. Bugbot is set up for automated code reviews on this repo. Configure here.

thomson-t
thomson-t previously approved these changes Sep 10, 2026
@thomson-t
thomson-t dismissed their stale review September 10, 2026 17:25

The merge-base changed after approval.

thomson-t added a commit that referenced this pull request Sep 10, 2026
Brings `workstation/kotlin-migration` up to date with `main`.

## ⚠️ Merge this with "Create a merge commit" — not squash, not rebase

The branch already contains a real merge commit with two parents
(`7907bbe4` from the integration branch, `a1377d39` from `main`).
Squashing would flatten the upstream commits into one synthetic commit
on the integration branch and lose the branch point; rebasing would
rewrite `main`'s commits with new SHAs. Either one makes the eventual
`workstation/kotlin-migration` → `main` merge harder to read and harder
to bisect.

## What it picks up

| | |
| --- | --- |
| [#616](#616) |
chore: bump androidx.test.ext:junit from 1.1.4 to 1.3.0 |
| [#769](#769) |
docs: trim AGENTS.md to the non-derivable core and add CLAUDE.md |
| [#784](#784) |
chore: bump actions/setup-java from 5 to 6 |
| [#765](#765) |
chore: bump gradle/actions/setup-gradle from 5.0.2 to 6.3.0 |
| [#764](#764) |
chore: bump trunk-io/trunk-action from 1.3.1 to 2.0.0 |
| [#775](#775) |
chore: prepare release 6.0.4 |

14 files, +194 / −211. **No conflicts.**

## Why the migration tooling is unaffected

Nothing on `main` touched `android-core` or `android-kit-base`
`src/main`, so the committed `api/*.api` files are still exact. The
androidTest manifest and `testutils/build.gradle` junit bump do not
affect the public API guard or the Kotlin migration tracker.

Stacked PRs #777 and #778 will rebase onto this once it lands.


Made with [Cursor](https://cursor.com)
nickolas-dimitrakas and others added 4 commits September 10, 2026 13:25
Converts KitsLoadedListener, JsonReportingMessage, ReportingManager,
KitContext, and KitManager from Java to Kotlin. No public API change:
verified with `scripts/api-guard.sh check` and a full android-core +
android-kit-base compile against the new interfaces.

KitManager needed two adjustments beyond a literal translation:
- Six Activity/Bundle lifecycle params go nullable (AppStateManager.kt
  already calls them with nullable arguments; Java's unannotated
  platform types had been silently permissive here).
- getSurveyUrl's Map<String, List<String>> param becomes
  MutableMap<String, List<String>> — Kotlin auto-wildcards a covariant
  Map value type in parameter position, which broke the existing Java
  overrides in KitManagerImpl and KitFrameworkWrapper even though the
  erased javap signature is unaffected.

The API baseline picks up one new line: KitStatus gets a synthetic
getEntries() accessor, which every Kotlin 1.9+ enum conversion adds
automatically (kotlin.enums.EnumEntries). Purely additive, already
present in the baseline for WrapperSdk and
ApplicationContextWrapper$MethodType from earlier conversions.

Local ktlintCheck and unit tests could not run in this environment —
Maven Central is rate-limiting dependency resolution (429) for
powermock, kotlinx-coroutines-test, and ktlint-cli. Compile and
api-guard both ran clean against the real build. CI will run the full
suite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI's Unit Tests / Kit Compatibility Test / build-kits::rokt jobs all
failed on real regressions in the KitManager conversion that my local
compile (main sources only) didn't exercise:

1. Five zero-arg getters/isEnabled became Kotlin properties
   (currentActivity, isEnabled, supportedKits, roktOptions,
   kitStatus, attributionResults). Kotlin's Java-interop sugar that
   lets Kotlin code call a Java getter as `.propertyName` only
   applies to Java-declared methods — once KitManager became a
   Kotlin interface, every existing Kotlin call site using that sugar
   (KitFrameworkWrapperTest.kt's `mockKitManager.supportedKits`,
   Rokt.kt's `mKitManager.isEnabled`, RoktKit.kt's
   `kitManager?.roktOptions`, etc.) stopped compiling. Declaring
   these as real Kotlin properties restores the sugar for Kotlin
   callers while producing the identical getXxx()/isXxx() bytecode
   Java callers and the API guard already expect — confirmed with
   api-guard.sh check (zero diff) after the change.

2. Six more parameters go nullable (logScreen, setLocation,
   setUserAttribute both params, removeUserAttribute's key,
   setUserIdentity both params, updateKits), on top of the six
   Activity/Bundle lifecycle params from the previous commit.
   KitManagerTest.kt and KitFrameworkWrapperTest.kt pass null to all
   of these on purpose, to exercise KitManagerImpl's existing
   null-tolerant behavior — proven by reading the actual test bodies,
   not inferred.

Both are a single hazard, really: an unannotated Java parameter or
getter carries no nullability information, and Java platform-type
looseness let existing callers (production and test) rely on
behavior a naive non-null Kotlin translation silently forecloses.
Worth its own line in PLAN.md's hazard table, distinct from the
already-documented "tightens nullability" entry — this one is about
finding every existing caller (including tests) before choosing a
type, not about the direction of the mismatch.

Local verification: main-source compile and api-guard.sh both pass
for android-core and android-kit-base. Could not compile the test
sources locally — Maven Central is rate-limiting this network for
powermock/kotlinx-coroutines-test dependency resolution (429), and
the one cached kotlinx-coroutines-test-jvm version (1.10.2) doesn't
match what's pinned (1.9.0). Every call site was instead verified by
reading the actual failing test files and grepping the full repo for
other usages of the same KitManager methods. CI will confirm the
actual test compile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous fix only covered parameters where an explicit `null`
literal appeared in test source, found by grep. CI's rerun surfaced a
broader pattern that grep can't find: `Mockito.any()` (and the
parameterless `any()` import) return null under the hood, and Kotlin
inserts a call-site null-check ("<expr> must not be null") whenever
that platform-typed null is passed into a genuinely non-null Kotlin
parameter. KitManager is mocked and verified extensively across the
kit test suite with exactly this pattern
(KitFrameworkWrapperTest.testLogBaseEvent/testLogCommerceEvent/
testReplayEvents/testSetWrapperSdkVersion_*, etc.), so grepping for
literal `null` was never going to find all of them.

Rather than keep discovering these one CI run at a time, every
non-primitive parameter in KitManager is now nullable, matching what
the original unannotated Java signature always permitted. Primitives
(Long/Int/Boolean) are unaffected — Mockito's anyLong()/anyInt()/
anyBoolean() return real default values, not null, so they were never
at risk.

Also fixes the same "Kotlin interop sugar only applies to Java
methods" hazard from the previous commit, this time in
JsonReportingMessage (Lane E, not KitManager): timestamp, moduleId,
and sessionId become real Kotlin properties. android-core's
androidTest suite (ReportingServiceTest.kt) calls
`jsonReportingMessage.sessionId` property-style on the interface type,
which broke the same way KitFrameworkWrapperTest's `.supportedKits`
did. Confirmed via full-repo grep that no Kotlin caller uses explicit
`.getSessionId()`/`.getTimestamp()` call syntax on this interface, so
the property conversion is unambiguously safe. setDevMode and toJson
stay functions — no matching getter/property shape in the original.

Local verification: main-source compile and api-guard.sh both pass
for android-core and android-kit-base (zero API diff — confirms
properties compile to the identical getXxx()/isXxx() bytecode).
Cannot compile android-core's test or androidTest source sets locally
at all — both need kotlinx-coroutines-test/powermock, and Maven
Central is still rate-limiting this network (429) for those
specific artifacts. Every change here is instead grounded in an
actual CI failure message or a full-repo grep for the call pattern,
not inference. CI will confirm.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
KitManager.logEvent took BaseEvent? after the CI nullability pass, but the
kit-base test subclass still overrode the old non-null Java shape, so unit
test compile failed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@thomson-t
thomson-t force-pushed the kotlin/lane-e-kit-infra-leaf branch from 5d10d03 to 95400e6 Compare September 10, 2026 17:25
thomson-t
thomson-t previously approved these changes Sep 10, 2026
KitManager's onIdentify/Login/Logout/ModifyCompleted parameters are
nullable after the interface conversion; the androidTest stubs still used
the old non-null Java shapes.

Co-authored-by: Cursor <cursoragent@cursor.com>
@thomson-t
thomson-t merged commit 7bf6cd6 into workstation/kotlin-migration Sep 10, 2026
45 checks passed
@thomson-t
thomson-t deleted the kotlin/lane-e-kit-infra-leaf branch September 10, 2026 18:08
thomson-t added a commit that referenced this pull request Sep 10, 2026
…778)

## Lane C — identity leaf classes, part 1 (1 of 2 files, 13 of 170 LOC)

Stacked on #777 (Lane E) — merge that one first. Part of the Java→Kotlin
migration ([PLAN.md](docs/kotlin-migration/PLAN.md)).

Converts `identity/MParticleIdentityClient`. Zero public API diff (`bash
scripts/api-guard.sh check` passes) — this file lives outside
`com.mparticle.internal`, so any diff here is a hard gate failure, not a
reviewable one. Each method keeps `@Throws(Exception::class)` to
preserve the `throws` clause Java callers compile against.

## `MParticleUserImpl` is deliberately NOT in this PR

Lane C's other easy-tier file (157 LOC) turned out to have a real
hazard: it has three package-private members (`setUserIdentities`,
`setUserIdentity`, `setUserDelegate`/`mUserDelegate`) that
`testutils/AccessUtils.java` calls directly — from a **separate Gradle
module**.

Kotlin has no package-private visibility. The closest equivalent,
`internal`, is module-scoped and name-mangled on the JVM, so converting
these would break that cross-module Java call site outright. Working
around the mangling with `@JvmName` compiles, but makes those three
members permanently public in the ABI — a real (if obscure) addition to
a frozen-by-default file, which is exactly what this migration is
supposed to avoid.

This needs either a coordinated PR that also converts
`AccessUtils.java`/`IdentityApi.java`, or a restructured test hook.
Filing as a follow-up rather than folding it in here or quietly
accepting the API growth. Worth adding to `PLAN.md`'s hazard table —
package-private Java members with cross-module Java callers are a
distinct case from the ones already documented.

## Validation

- `./gradlew :android-core:compileDebugKotlin
:android-core:compileDebugJavaWithJavac` — clean
- `./gradlew :android-kit-base:compileDebugKotlin
:android-kit-base:compileDebugJavaWithJavac` — clean
- `bash scripts/api-guard.sh check` — passed, zero diff
- `bash scripts/kotlin-migration-progress.sh` — 23.3% → 23.3% (rounds
the same at one decimal; LOC moved 16,752 → 16,739)
- Local `ktlintCheck`/unit tests could not run in this environment —
Maven Central is rate-limiting dependency resolution (429). The commit
hook's own ktlint pass (cached deps) was clean. CI will run the full
suite.

Draft while CI runs — flip to ready once green, and once #777 merges so
this rebases onto `workstation/kotlin-migration` directly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants