There are few feelings in game development more frustrating than the "It works on my machine" syndrome. You integrate IronSource LevelPlay, hit play in the Unity Editor, and see the test ads load perfectly. You confidently build your APK or Xcode project, deploy it to a physical device, and stare at a blank screen. No ads, no errors visible on the UI, just silence.
This is the most common hurdle for developers integrating mobile ads. The discrepancy exists because the Unity Editor and the mobile runtime operate on fundamentally different architectures.
This guide moves beyond generic "check your internet connection" advice. We will debug the Native Bridge, fix dependency resolution issues, and implement a production-ready initialization script that handles the specific quirks of iOS and Android builds.
The Root Cause: The Mock Wrapper vs. The Native Bridge
To fix this, you must understand why the Editor lies to you.
When you run IronSource in the Unity Editor, you are not running the actual IronSource SDK. You are running a C# Mock Wrapper. This wrapper simulates ad behavior so you don't crash the editor. It does not make network calls to ad exchanges, nor does it initialize mediation adapters (like AdMob, UnityAds, or AppLovin).
When you build for a device, Unity relies on the Native Bridge.
- Android: Unity uses Gradle to pull in
.aarfiles and communicates via JNI (Java Native Interface). - iOS: Unity uses CocoaPods (or manual linking) to pull in static libraries/frameworks and communicates via
[DllImport].
The Failure Point: The connection usually breaks because the Native Dependencies (the actual .aar or .framework files) were not resolved correctly during the build process, or the initialization code is firing before the native bridge is established.
Step 1: Fix The "Invisible" Dependency Errors
Before touching code, we must ensure the native libraries are actually inside your build. The Editor doesn't use them, but the device requires them.
The Mobile Dependency Resolver (EDM4U)
Unity usually handles dependencies automatically, but it often fails silently.
- In Unity, go to Assets > Mobile Dependency Resolver > Android Resolver > Settings.
- Ensure "Patch mainTemplate.gradle" and "Use Jetifier" are checked.
- Go to Assets > Mobile Dependency Resolver > Android Resolver > Force Resolve.
Critical Check: Watch the console. If this process throws any error, your ads will never load on Android. A successful resolution ends with "Resolution Succeeded."
Validating the Adapters
You cannot just install the IronSource SDK; you must install the adapters for every network you intend to use.
- Go to Ads Mediation > Integration Manager.
- Ensure the SDK versions listed here match the versions in your IronSource Dashboard.
- If a network (e.g., AdMob) shows "Upgrade," do it.
- Crucial: After upgrading any adapter, run Force Resolve again.
Step 2: The Production-Ready Initialization Code
The most common coding error is initializing the SDK too late or using the wrong App Key for the platform.
Do not put your initialization logic in Awake of a random object that might be destroyed. Use a dedicated AdsManager singleton.
Create a script named AdsManager.cs and implement the following. This code handles platform switching and strictly enforces initialization event listening.
using UnityEngine;
// vital: this namespace contains the LevelPlay logic
using com.unity3d.mediation;
public class AdsManager : MonoBehaviour
{
public static AdsManager Instance { get; private set; }
[Header("LevelPlay App Keys")]
[Tooltip("Found in IronSource Dashboard > Android App")]
public string androidAppKey = "YOUR_ANDROID_KEY_HERE";
[Tooltip("Found in IronSource Dashboard > iOS App")]
public string iosAppKey = "YOUR_IOS_KEY_HERE";
private void Awake()
{
// Singleton Pattern to ensure only one AdsManager exists
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
private void Start()
{
// 1. Define the App Key based on platform
string appKey = GetAppKey();
// 2. Validate Key
if (string.IsNullOrEmpty(appKey) || appKey.Equals("YOUR_ANDROID_KEY_HERE"))
{
Debug.LogError("[AdsManager] Invalid App Key. Please set the correct App Key in the Inspector.");
return;
}
// 3. Enable validation integration (Only for development builds!)
IronSource.Agent.validateIntegration();
// 4. Initialize the SDK
Debug.Log("[AdsManager] Initializing IronSource SDK...");
// Listen for initialization success
IronSourceEvents.onSdkInitializationCompletedEvent += SdkInitializationCompletedEvent;
IronSource.Agent.init(appKey);
}
private string GetAppKey()
{
#if UNITY_ANDROID
return androidAppKey;
#elif UNITY_IPHONE
return iosAppKey;
#else
return "unsupported_platform";
#endif
}
private void SdkInitializationCompletedEvent()
{
Debug.Log("[AdsManager] SDK Initialization Completed Successfully.");
// Now it is safe to load ads.
// If you try to load ads before this event fires, they will fail.
LoadBanner();
LoadInterstitial();
}
public void LoadInterstitial()
{
IronSource.Agent.loadInterstitial();
}
public void ShowInterstitial()
{
if (IronSource.Agent.isInterstitialReady())
{
IronSource.Agent.showInterstitial();
}
else
{
Debug.Log("[AdsManager] Interstitial not ready yet.");
}
}
public void LoadBanner()
{
// Standard Banner size
IronSource.Agent.loadBanner(IronSourceBannerSize.BANNER, IronSourceBannerPosition.BOTTOM);
}
// Handle App Pausing (Critical for Android lifecycle)
void OnApplicationPause(bool isPaused)
{
IronSource.Agent.onApplicationPause(isPaused);
}
}
Why This Code Works
- Platform Preprocessors: It strictly separates
UNITY_ANDROIDandUNITY_IPHONE. Using an Android key on an iOS build (or vice versa) triggers a silent failure where the server rejects the request. - Event-Driven Loading: We subscribe to
onSdkInitializationCompletedEvent. Many tutorials teach you to call.init()and immediately.loadInterstitial(). This is wrong. The SDK needs milliseconds to seconds to spin up. Loading immediately often results in a generic "Internal Error." - Lifecycle Management: The
OnApplicationPausemethod is mandatory for accurate impression tracking and background reloading on Android.
Step 3: Platform-Specific Configuration
Even with perfect code, OS-level security settings can block ads.
Android: Cleartext Traffic
By default, modern Android versions block HTTP traffic. While most ads are HTTPS, some creative assets or mediation partners still use HTTP handshakes.
- Go to Edit > Project Settings > Player > Android > Other Settings.
- Scroll to Configuration.
- Set "Allow downloads over HTTP" to "Always allowed".
- Alternatively, create a network security config file, but this toggle is the quickest fix for "No Fill" errors on valid requests.
iOS: SKAdNetwork & Info.plist
Apple requires specific entries in your Info.plist to attribute ad installs. If these are missing, networks like Meta (Facebook) and AdMob may refuse to bid on your inventory.
- In Unity, verify you are using the IronSource generated PostProcess.
- When you build the Xcode project, check your
Info.plist. - You should see a key named
SKAdNetworkItems. It should contain dozens of dictionaries (IDs for UnityAds, Vungle, AdMob, etc.). - If this list is empty: You did not run the dependency resolver correctly. IronSource includes a script that auto-populates this, but it requires the Adapters to be present in the project
PackagesorPluginsfolder.
Step 4: Troubleshooting "No Fill"
If the SDK initializes (you see "SDK Initialization Completed Successfully" in Logcat/Xcode Console) but ads still don't show, you have a Fill Rate issue, not a code issue.
1. The Test Device Issue
Live production ads rarely serve to development devices because ad networks flag them as low-value or fraudulent traffic.
- Solution: Go to the IronSource Dashboard > Settings > Testing. Add your device's Advertising ID (GAID for Android, IDFA for iOS).
- Once added as a test device, you will always receive fill (usually a specific test ad), confirming your integration works.
2. The IDFA/ATT Consent (iOS 14+)
On iOS, if you do not request permission to track (App Tracking Transparency), IronSource cannot access the IDFA. Without IDFA, eCPM plummets, and fill rates drop significantly.
You must implement the App Tracking Transparency request before initializing IronSource on iOS.
#if UNITY_IPHONE
using Unity.Advertisement.IosSupport;
public void RequestTracking() {
if(ATTrackingStatusBinding.GetAuthorizationTrackingStatus() == ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED) {
ATTrackingStatusBinding.RequestAuthorizationTracking();
}
}
#endif
Summary Checklist
If your ads are still failing, check this exact sequence:
- Dependencies: Did you run "Force Resolve" in the Mobile Dependency Resolver?
- Keys: Are you 100% sure you pasted the Android App Key into the Android slot in your script?
- Listeners: Are you waiting for the Init Complete event before loading?
- Test Mode: Is your physical device registered as a Test Device in the dashboard?
The Unity Editor is a simulator, but the device is the source of truth. By respecting the native dependency chain and handling asynchronous initialization events, you bridge the gap between "It works on my machine" and "It works on the store."