Android Integration Guide
Integrate AppSpike into your Android app and serve your first ad in under 5 minutes.
AppSpike sits on top of your existing ad networks and uses real-time bidding to route every impression to the highest-paying demand source. You keep your current ad network accounts — AppSpike optimizes across all of them.
Prerequisites
- Android Studio Hedgehog (2023.1.1) or later
- Minimum SDK: API 23 (Android 6.0)
- Kotlin 2.0+
- An AppSpike account and API key (sign up)
Step 1: Add the SDK
Once you are approved for early access, AppSpike will send you the SDK along with setup instructions for your project.
Step 2: Initialize the SDK
Initialize AppSpike as early as possible, ideally in your Application class. Pass your API key and the ad network plugins you want to mediate:
import dev.appspike.AppSpike
import dev.appspike.InitResult
import tech.pyde.ads.admob.AdMobPlugin
import tech.pyde.ads.ironsource.IronSourcePlugin
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
AppSpike.initialize(
context = this,
apiKey = "YOUR_API_KEY",
plugins = listOf(AdMobPlugin, IronSourcePlugin),
) { result ->
when (result) {
is InitResult.Success -> {
Log.d("AppSpike", "SDK initialized")
}
is InitResult.Error -> {
Log.e("AppSpike", "Init failed: ${result.message}")
}
}
}
}
}
Step 3: Banner Ads
Banners are loaded asynchronously. After loading, add the banner's view to your layout and manage its lifecycle:
MainActivity.ktimport dev.appspike.AppSpike
import tech.pyde.ads.banner.BannerAd
class MainActivity : AppCompatActivity() {
private var bannerAd: BannerAd? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
bannerAd?.resume()
val adContainer = findViewById<FrameLayout>(R.id.ad_container)
AppSpike.loadBanner("YOUR_PLACEMENT_ID") { result ->
result.onSuccess { ad ->
bannerAd?.let { old ->
adContainer.removeView(old.bannerView)
old.destroy()
}
bannerAd = ad
ad.initialize()
adContainer.addView(ad.bannerView)
}.onFailure { error ->
Log.e("AppSpike", "Banner failed: ${error.message}")
}
}
}
override fun onPause() {
bannerAd?.pause()
super.onPause()
}
override fun onDestroy() {
bannerAd?.destroy()
super.onDestroy()
}
}
Add the container to your layout XML:
activity_main.xml<FrameLayout
android:id="@+id/ad_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
Step 4: Interstitial Ads
GameActivity.ktimport dev.appspike.AppSpike
import tech.pyde.ads.interstitial.InterstitialAd
class GameActivity : AppCompatActivity() {
private var interstitialAd: InterstitialAd? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadInterstitial()
}
private fun loadInterstitial() {
AppSpike.loadInterstitial("YOUR_PLACEMENT_ID") { result ->
result.onSuccess { ad ->
interstitialAd = ad
}.onFailure { error ->
Log.e("AppSpike", "Interstitial failed to load: ${error.message}")
}
}
}
// Call this at a natural break point (e.g., between levels)
private fun showInterstitial() {
interstitialAd?.let { ad ->
ad.show()
interstitialAd = null
loadInterstitial()
} ?: Log.d("AppSpike", "Interstitial not ready yet")
}
}
Step 5: Rewarded Video Ads
Rewarded video ads use a reactive stream to deliver events. Subscribe to the observable returned by show() to track reward delivery:
import com.badoo.reaktive.observable.subscribe
import dev.appspike.AppSpike
import tech.pyde.ads.rewarded.RewardedVideo
import tech.pyde.ads.rewarded.RewardedVideoEvent
class StoreActivity : AppCompatActivity() {
private var rewardedVideo: RewardedVideo? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadRewarded()
}
private fun loadRewarded() {
AppSpike.loadRewardedVideo("YOUR_PLACEMENT_ID") { result ->
result.onSuccess { video ->
rewardedVideo = video
}.onFailure { error ->
Log.e("AppSpike", "Rewarded video failed to load: ${error.message}")
}
}
}
// Call this when the user opts in (e.g., "Watch ad for 50 coins")
private fun showRewarded() {
rewardedVideo?.let { video ->
video.show().subscribe(
onNext = { event ->
when (event) {
is RewardedVideoEvent.RewardReceived -> {
Log.d("AppSpike", "User earned reward")
grantReward()
}
is RewardedVideoEvent.ClosedWithReward -> {
Log.d("AppSpike", "Ad closed with reward")
}
is RewardedVideoEvent.ClosedEarly -> {
Log.d("AppSpike", "User closed ad early")
}
is RewardedVideoEvent.Started -> {
Log.d("AppSpike", "Rewarded video started")
}
is RewardedVideoEvent.DisplayError -> {
Log.e("AppSpike", "Display error: ${event.message}")
}
}
},
onComplete = {
rewardedVideo = null
loadRewarded()
},
onError = { error ->
Log.e("AppSpike", "Rewarded error: ${error.message}")
},
)
} ?: Log.d("AppSpike", "Rewarded video not ready yet")
}
private fun grantReward() {
// Your reward logic here
}
}
Step 6: Native Ads
Native ads use a view binder pattern. Load the ad, configure the binder with your layout and view IDs, then bind it to produce a View:
import dev.appspike.AppSpike
import tech.pyde.ads.nativead.NativeAd
class FeedActivity : AppCompatActivity() {
private var nativeAd: NativeAd? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_feed)
}
override fun onResume() {
super.onResume()
nativeAd?.resume()
val adContainer = findViewById<FrameLayout>(R.id.native_ad_container)
AppSpike.loadNativeAd("YOUR_PLACEMENT_ID") { result ->
result.onSuccess { ad ->
nativeAd?.let { old ->
adContainer.removeView(old.binder().bind(this@FeedActivity))
old.destroy()
}
nativeAd = ad
val binder = ad.binder()
binder.setNativeAdLayoutResId(R.layout.native_ad_layout)
binder.setHeadlineViewId(R.id.ad_headline)
binder.setBodyViewId(R.id.ad_body)
binder.setCallToActionViewId(R.id.ad_call_to_action)
binder.setIconViewId(R.id.ad_icon)
binder.setMediaContentViewId(R.id.ad_media)
binder.setOptionsViewId(R.id.ad_options)
val adView = binder.bind(this@FeedActivity)
ad.initialize()
adContainer.addView(adView)
}.onFailure { error ->
Log.e("AppSpike", "Native ad failed: ${error.message}")
}
}
}
override fun onPause() {
nativeAd?.pause()
super.onPause()
}
override fun onDestroy() {
nativeAd?.destroy()
super.onDestroy()
}
}
Create a custom layout for your native ad:
res/layout/native_ad_layout.xml<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView android:id="@+id/ad_headline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textStyle="bold" />
<TextView android:id="@+id/ad_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView android:id="@+id/ad_icon"
android:layout_width="40dp"
android:layout_height="40dp" />
<FrameLayout android:id="@+id/ad_media"
android:layout_width="match_parent"
android:layout_height="200dp" />
<FrameLayout android:id="@+id/ad_options"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button android:id="@+id/ad_call_to_action"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Step 7: Rewarded Interstitial Ads
Rewarded interstitials combine the full-screen experience of an interstitial with a reward. They follow the same reactive pattern as rewarded video ads:
LevelActivity.ktimport com.badoo.reaktive.observable.subscribe
import dev.appspike.AppSpike
import tech.pyde.ads.rewarded.RewardedInterstitial
import tech.pyde.ads.rewarded.RewardedVideoEvent
class LevelActivity : AppCompatActivity() {
private var rewardedInterstitial: RewardedInterstitial? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadRewardedInterstitial()
}
private fun loadRewardedInterstitial() {
AppSpike.loadRewardedInterstitial("YOUR_PLACEMENT_ID") { result ->
result.onSuccess { ad ->
rewardedInterstitial = ad
}.onFailure { error ->
Log.e("AppSpike", "Rewarded interstitial failed to load: ${error.message}")
}
}
}
private fun showRewardedInterstitial() {
rewardedInterstitial?.let { ad ->
ad.show().subscribe(
onNext = { event ->
when (event) {
is RewardedVideoEvent.RewardReceived -> grantReward()
is RewardedVideoEvent.DisplayError -> {
Log.e("AppSpike", "Display error: ${event.message}")
}
else -> {}
}
},
onComplete = {
rewardedInterstitial = null
loadRewardedInterstitial()
},
onError = { error ->
Log.e("AppSpike", "Error: ${error.message}")
},
)
} ?: Log.d("AppSpike", "Rewarded interstitial not ready yet")
}
private fun grantReward() {
// Your reward logic here
}
}
Jetpack Compose
Use AndroidView to embed banner ads in Compose layouts:
import androidx.compose.runtime.*
import androidx.compose.ui.viewinterop.AndroidView
@Composable
fun BannerAdSection() {
var bannerAd by remember { mutableStateOf<BannerAd?>(null) }
var bannerView by remember { mutableStateOf<View?>(null) }
LaunchedEffect(Unit) {
AppSpike.loadBanner("YOUR_PLACEMENT_ID") { result ->
result.onSuccess { ad ->
ad.initialize()
bannerAd = ad
bannerView = ad.bannerView
}
}
}
DisposableEffect(Unit) {
onDispose { bannerAd?.destroy() }
}
bannerView?.let { view ->
key(view) {
AndroidView(
factory = { view },
modifier = Modifier.fillMaxWidth()
)
}
}
}
Interstitial in Compose
@Composable
fun LevelCompleteScreen() {
var interstitialAd by remember { mutableStateOf<InterstitialAd?>(null) }
LaunchedEffect(Unit) {
AppSpike.loadInterstitial("YOUR_PLACEMENT_ID") { result ->
result.onSuccess { interstitialAd = it }
}
}
Button(onClick = {
interstitialAd?.show()
interstitialAd = null
}) {
Text("Continue")
}
}
Fragment Example
Load and display a banner ad inside a Fragment:
import dev.appspike.AppSpike
import tech.pyde.ads.banner.BannerAd
class AdFragment : Fragment(R.layout.fragment_ad) {
private var bannerAd: BannerAd? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adContainer = view.findViewById<FrameLayout>(R.id.ad_container)
AppSpike.loadBanner("YOUR_PLACEMENT_ID") { result ->
result.onSuccess { ad ->
bannerAd = ad
ad.initialize()
adContainer.addView(ad.bannerView)
}
}
}
override fun onResume() {
super.onResume()
bannerAd?.resume()
}
override fun onPause() {
bannerAd?.pause()
super.onPause()
}
override fun onDestroyView() {
bannerAd?.destroy()
bannerAd = null
super.onDestroyView()
}
}
Testing
Enable test mode during development to avoid serving live ads:
AppSpike.initialize(
context = this,
apiKey = "YOUR_API_KEY",
plugins = listOf(AdMobPlugin, IronSourcePlugin),
testMode = true,
) { result ->
// Test ads will always fill and never charge
}
ProGuard / R8
If you use code shrinking, add to your proguard-rules.pro:
-keep class dev.appspike.** { *; }
-keep class tech.pyde.ads.** { *; }
Best Practices
- Pre-load interstitials and rewarded ads as early as possible, then show them at natural break points. Never interrupt the user mid-action.
- Always reload after showing. Each ad object is single-use — load the next one after the previous one completes.
- Ship with all available ad network plugins. More demand sources means higher competition and higher eCPMs. AppSpike's mediation handles the rest.
- Use test mode during development. Live impressions from test devices waste ad budget and can flag your account.
- Don't gate core functionality behind rewarded ads. Use them for optional bonuses (extra lives, cosmetics, currency). Stores may reject apps that require ads to function.