One Kotlin core, four mediation hosts, 99.9% crash-free
A mediation SDK is a binary you hand to someone who will never read it, to run inside an app whose crash reports you cannot see. Publishers do not file bugs, they remove the dependency. One Kotlin core of 12,311 lines sits behind four host adapters and Flutter and React Native wrappers, so four mediation platforms and three publisher-facing surfaces share one behavioural contract.
🚀 Impact
🛠️ What I Built
- A single Kotlin core owning the waterfall, timeout budget, callback contract, error taxonomy and telemetry, with four thin host adapters translating for AdMob, Google Ad Manager, AppLovin MAX and IronSource
- Flutter and React Native wrappers that normalise delivery at the core boundary, so a load callback reaches the publisher on the same thread regardless of which surface they integrated through
- A client-side OpenRTB bid response path: seat flattening, highest-price selection, media-type gating and creative expiry, with six distinct no-fill reasons instead of a silent failure
- Creative rendering in a locked-down WebView, with clicks handed to the OS so the ad view never navigates away from the creative
- A 20-code error taxonomy where each code carries a severity and an explicit decision about whether a publisher should ever see it
⚠️ Engineering Challenges
- Duplicate impression callbacks inflate a publisher reporting figure, which later surfaces as a discrepancy against the network and turns into a conversation about whether our SDK is honest
- The original code raced a per-loader timeout against the network callback and hoped, and a dropped callback leaked a fully loaded interstitial with its listeners still attached
- Our timeout budget has to be strictly tighter than the host mediation platform, because the host is the one being kept waiting
- Flutter and React Native each have their own lifecycle and threading model, and neither matches Android, so an unnormalised contract stops being a contract
- A crash in our SDK is not our crash, it lands in the publisher store rating
✅ Solutions
- Replaced the timeout race with a compare-and-set guard so exactly one of timeout or callback wins, and made the losing path release the ad it was carrying rather than leak it
- Stacked three further guarantees: a separate CAS on the placement deadline, an attempt-sequence counter so a superseded waterfall cannot advance the current one, and single-owner load analytics
- Moved the per-tag timeout from adapter level to placement level, deleting around 263 lines of duplicated timeout handling across the loaders
- Made the waterfall refuse work it cannot finish, computing remaining budget minus a 500ms minimum viable window and returning a no-fill rather than starting a request that will be killed mid-flight
- Built remote kill switches at package, version and placement scope, plus an automatic crash-loop kill at three crashes in a five minute window
- A single Kotlin core owning the waterfall, timeout budget, callback contract, error taxonomy and telemetry, with four thin host adapters translating for AdMob, Google Ad Manager, AppLovin MAX and IronSource
- Flutter and React Native wrappers that normalise delivery at the core boundary, so a load callback reaches the publisher on the same thread regardless of which surface they integrated through
- A client-side OpenRTB bid response path: seat flattening, highest-price selection, media-type gating and creative expiry, with six distinct no-fill reasons instead of a silent failure
- Creative rendering in a locked-down WebView, with clicks handed to the OS so the ad view never navigates away from the creative
- A 20-code error taxonomy where each code carries a severity and an explicit decision about whether a publisher should ever see it