@mbdayo/react-native-kickstart-exchange
v0.1.0
Published
Unofficial React Native (bare / CLI) wrapper for the Kickstart Exchange banner ad SDK. No Expo modules required.
Maintainers
Readme
@mbdayo/react-native-kickstart-exchange
Unofficial React Native wrapper for the Kickstart Exchange banner ad SDK, for bare React Native (CLI) projects. No Expo modules required.
Kickstart Exchange is a privacy-preserving cross-promotion network for independent Apple-platform apps: you earn points by showing other developers' ads, and spend points advertising your own. This package wraps twostraws/KickstartSDK — Paul Hudson's official SwiftUI SDK — as a Fabric native component.
Not affiliated with or endorsed by Kickstart Exchange or Paul Hudson.
import {KickstartExchangeBanner} from '@mbdayo/react-native-kickstart-exchange';
<KickstartExchangeBanner apiKey="ks_live_YOUR_KEY" />;That's the whole integration. The banner sizes itself, so you don't pass a height.
Why this package
There is an excellent Expo module, @tomyail/react-native-kickstart-exchange. If your app uses Expo, use that one. This package exists for bare RN apps that don't want to adopt Expo Modules just to show a banner, and it differs in two ways that matter:
- The banner measures itself. The ad card's height depends on Dynamic Type and ad copy. Measured on an iPhone 17: 84pt at the default text size, 391.5pt at
accessibility-extra-large. A fixed height either clips the "Get" button and the "Ad" disclosure badge at large text sizes, or leaves dead space at small ones. - Impressions follow real on-screen visibility. See Viewability — a banner scrolled off screen stops counting as viewed.
It also takes standard React Native ColorValue props (named colours, rgba(), DynamicColorIOS) rather than hex strings.
Requirements
| | |
|---|---|
| Platform | iOS 18+ only. Android, web, and iOS < 18 render null. |
| Architecture | New Architecture (Fabric). No legacy-arch ViewManager. |
| React Native | 0.80 or later |
| Xcode | 26 or later (the SDK is Swift 6 / SwiftUI) |
| App deployment target | iOS 18.0 — see below |
The iOS 18 deployment target
The upstream SDK requires iOS 18, so this pod does too, and CocoaPods will refuse to install into an app targeting anything lower. You must raise your app's deployment target in both places:
ios/Podfile:
platform :ios, '18.0'And in Xcode, set iOS Deployment Target to 18.0 for your app target (or edit IPHONEOS_DEPLOYMENT_TARGET in the .pbxproj).
If iOS 18 is too high a floor for your app, this SDK is not usable at all yet — the requirement comes from upstream, not from this wrapper.
Installation
npm install @mbdayo/react-native-kickstart-exchange
cd ios && pod installThere is no Swift Package to add and no Podfile edit beyond the platform line: the MIT-licensed SDK sources are vendored into this package and compiled by its pod. See docs/VENDORING.md.
No Android code ships at all — autolinking skips the platform entirely.
API keys
Register your app at exchange.kickstart.tools to get a ks_live_… key. Approval requires an App Store link and developer-identity verification.
For development, pass the literal string 'preview':
<KickstartExchangeBanner apiKey={__DEV__ ? 'preview' : 'ks_live_YOUR_KEY'} />The upstream SDK honours 'preview' only in Debug builds and on the Simulator. It renders the server's sample advertisement, creates no session, and records no impression. Note that the preview response carries no impression token, so preview mode cannot be used to verify impression reporting.
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
| apiKey | string | — | Required. ks_live_… or 'preview'. |
| style | StyleProp<ViewStyle> | — | Leave the height out unless autoHeight is false. |
| autoHeight | boolean | true | Size to the measured card height. |
| cornerStyle | 'rounded' \| 'square' | 'rounded' | |
| strokeColor | ColorValue | SDK default (.quaternary) | Card border. |
| actionTextColor | ColorValue | blue | "Get" button text. |
| disclosureBackgroundColor | ColorValue | blue | The "Ad" badge. |
| colorScheme | 'system' \| 'light' \| 'dark' | 'system' | Force the card's appearance. |
| onSizeChange | (size: {width, height}) => void | — | Fires on every measurement, including the initial height: 0. |
| onAdVisibilityChange | (isVisible: boolean) => void | — | Derived from measured height; see below. |
| testID | string | — | |
Colour props map to the SDK's exchangeAd… modifiers. Omitting one leaves the SDK's own default untouched, which is not always a plain colour — a missing strokeColor draws .quaternary, not "no border".
Sizing
The component measures the hosted SwiftUI card and applies the result as its own height. Concretely: native measures the card at its ideal height, emits onSizeChange, and the component stores that as its style height. Until the first ad loads the card occupies no space, so the banner's height is 0 and nothing is reserved in your layout.
Set autoHeight={false} to take over — to animate the height yourself, or to reserve a fixed slot. You must then supply a height via style, or nothing will be visible.
onAdVisibilityChange is derived from the measured height, not reported by the SDK, which exposes no load callback. height > 0 means an ad is on screen. Use it to collapse placeholders or surrounding padding when no ad is available:
const [hasAd, setHasAd] = useState(false);
<View style={hasAd ? styles.adSlot : undefined}>
<KickstartExchangeBanner apiKey={key} onAdVisibilityChange={setHasAd} />
</View>;Theming
The card's background uses SwiftUI's .windowBackground, which resolves against the system appearance. If your app has its own theme toggle that can diverge from the system setting, pass colorScheme so the card matches:
<KickstartExchangeBanner apiKey={key} colorScheme={myTheme === 'dark' ? 'dark' : 'light'} />Viewability and impressions
The SDK records an impression once the banner has been continuously visible for one second, gating on SwiftUI's onScrollVisibilityChange. That signal describes visibility within an enclosing SwiftUI scroll view — a notion that doesn't exist when the banner is hosted inside a UIKit/React Native hierarchy, where SwiftUI cannot see that the view sits below the fold of a React Native ScrollView.
This wrapper hosts the banner in a non-scrolling SwiftUI ScrollView and drives its height from real on-screen visibility, computed natively from window membership and intersection with any clipping or scrolling ancestors, and refreshed as those ancestors scroll. The result: a banner scrolled out of view reports as not visible and stops accruing an impression, and one scrolled into view starts.
Two honest caveats:
- Visibility is binary: any visible portion counts as visible, rather than the SDK's internal 50% threshold. The only lever available over SwiftUI's own visibility is the hosted scroll view's height, and collapsing it while the card is still partly on screen would make a visible ad vanish mid-scroll. So a partly-visible banner may begin its impression slightly earlier than a pure SwiftUI host would.
- Without this handling,
onScrollVisibilityChangestill fires — it reports the card visible as soon as it has a non-zero size, regardless of where it actually sits. Impressions are then recorded for ads nobody saw.
App backgrounding is handled by the SDK itself, through scenePhase.
App Store privacy
This is an ad SDK, so shipping it has App Store Connect consequences. See docs/PRIVACY.md for what to declare and why. In short: declare Product Interaction and Advertising Data as collected, not linked to identity, and not used for tracking; and mention Kickstart Exchange in your privacy policy.
The SDK's PrivacyInfo.xcprivacy ships in a named resource bundle (RNKickstartExchangeResources) so it stays visible to Apple's privacy tooling without colliding with your app's own manifest.
Per Apple's review guidelines and upstream's instructions, link the banner only into a main app binary — never an app extension, App Clip, or widget.
Known limitations
- No card background override. Upstream hard-codes
.windowBackgroundfor the card surface. Overriding it would require patching the vendored source, which this package deliberately avoids so that upstream updates stay a clean copy. UsecolorSchemeto control how that background resolves. - iOS only. The SDK supports macOS, tvOS, watchOS, and visionOS, but this wrapper targets iOS;
react-native-macos/-tvosare not wired up. - New Architecture only. There is no legacy
RCTViewManager. - One banner per screen region is the intended use. The SDK serves one ad per app run and dedupes impressions across banners itself.
Example app
npm install
npm run build
cd example && npm install
cd ios && pod install && cd ..
npx react-native run-iosThe harness renders two banners — one above the fold and one 500pt down — plus live measurement readout, a style toggle, and a mount toggle.
Upstream SDK version
Vendored from KickstartSDK 0.5.0 (36f475be). npm run check-upstream verifies the vendored tree still matches the pin and reports newer upstream tags; npm run sync-upstream -- --tag <version> moves it. Details in docs/VENDORING.md.
License
MIT for this wrapper. The vendored SDK is MIT, © 2026 Paul Hudson — see THIRD_PARTY_NOTICES.md.
