The Flutter Offerwall SDK: Complete Dart Integration Guide (2026)
Target keywords: offerwall flutter sdk, flutter offerwall integration, perkox flutter sdk
Flutter has become the cross-platform framework of choice for a growing segment of mobile developers — and for good reason. A single Dart codebase, hot reload, and a rich widget library make it possible to ship polished apps to both Android and iOS with remarkable speed. But when it comes to offerwall monetization, Flutter developers have historically faced a painful gap: most offerwall platforms provide only native Android and iOS SDKs, leaving Flutter teams to build fragile method-channel bridges by hand. Perkox is the only offerwall platform that ships a native Flutter SDK, written in Dart with typed stream-based reward handling and automatic platform-channel management. This guide walks through the complete flutter offerwall integration from pubspec.yaml through production deployment.
If you’re new to offerwalls and how they monetize apps, start with our primer: What Is an Offerwall? A Developer’s Guide. For platform-specific details, you may also want to reference our Android SDK Guide and iOS SDK Guide, since the Flutter SDK wraps the native SDKs under the hood.
1. Prerequisites
Before starting the offerwall flutter sdk integration, make sure your Flutter project meets these requirements:
| Requirement | Minimum | Recommended |
|---|---|---|
| Flutter version | 3.16.0 | 3.22.0+ |
| Dart version | 3.2.0 | 3.4.0+ |
| Android minSdkVersion | 21 | 24 |
| iOS deployment target | 13.0 | 16.0+ |
| Xcode version | 15.0 | 16.0+ |
| Perkox publisher account | Required | — |
Why a Native Flutter SDK Matters
Other offerwall platforms expect Flutter developers to write their own platform channels — manual bridges between Dart and native Kotlin/Swift code. This approach has serious drawbacks:
- Fragility: Every native SDK update can break your hand-written channel code
- Type unsafety: Method channels use dynamic types, losing Dart’s compile-time guarantees
- No reward streams: You must implement your own event-based reward delivery
- Maintenance burden: You own and maintain bridge code that should be the platform’s job
The perkox flutter sdk eliminates all of these problems. It provides typed Dart classes, stream-based reward handling, and automatic native dependency management — all maintained by Perkox as part of the SDK package.
2. Installation
Pubspec Dependency
Add the Perkox Flutter SDK to your pubspec.yaml:
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
perkox_sdk: ^3.4.0
Then run:
$ flutter pub get
The SDK is published on pub.dev. Verify the latest version at pub.dev/packages/perkox_sdk.
Platform-Specific Setup: Android
After flutter pub get, configure your Android project. The SDK automatically adds its Gradle dependency, but you need to add the required permissions and adjust the minimum SDK version.
Step 1: Update android/app/build.gradle (or build.gradle.kts):
// android/app/build.gradle
android {
defaultConfig {
minSdkVersion 21 // Required by Perkox SDK
// ...
}
compileSdkVersion 34 // Required by Perkox SDK
}
Step 2: Add permissions to android/app/src/main/AndroidManifest.xml:
<!-- Required for offerwall ad delivery -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Required for advertising ID (Google Play policy compliant) -->
<uses-permission android:name="android.permission.ACCESS_ADVERTISING_ID" />
Step 3: Add ProGuard rules if you enable minification:
# android/app/proguard-rules.pro
-keep class com.perkox.sdk.** { *; }
-keepclassmembers class com.perkox.sdk.** { *; }
-keep class com.perkox.sdk.models.** { *; }
For a detailed walkthrough of the native Android configuration, see our Android SDK Complete Guide.
Platform-Specific Setup: iOS
Step 1: Update ios/Podfile minimum deployment target:
# ios/Podfile
platform :ios, '13.0'
Step 2: Add ATT usage description and SKAdNetwork to ios/Runner/Info.plist:
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized offers and rewards.</string>
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>perkox.skadnetwork</string>
</dict>
</array>
Step 3: Install CocoaPods dependencies:
$ cd ios && pod install && cd ..
The SDK handles ATS automatically — no ATS exceptions are needed since all Perkox network traffic uses HTTPS. For a detailed walkthrough of the native iOS configuration, see our iOS SDK Complete Guide.
pod install to ensure all pods are built for the correct iOS version. If you encounter “deployment target” warnings, run pod install --repo-update.
3. Initialization
Initialize the Perkox SDK early in your app’s lifecycle. The best place is in your main() function before runApp(), or in an initialization widget. Early initialization allows the SDK to prefetch the offer catalog.
// main.dart
import 'package:flutter/material.dart';
import 'package:perkox_sdk/perkox_sdk.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final config = PerkoxConfig(
publisherId: 'YOUR_PUBLISHER_ID',
appId: 'YOUR_APP_ID',
testMode: true, // Set to false in production
);
try {
await Perkox.initialize(config);
debugPrint('[Perkox] SDK initialized successfully');
} on PerkoxException catch (e) {
debugPrint('[Perkox] Init failed: ${e.message} (code: ${e.code})');
}
runApp(const MyApp());
}
Initialization with User ID
If you have a user ID from your authentication system, pass it during initialization so Perkox can include it in postback requests:
final config = PerkoxConfig(
publisherId: 'YOUR_PUBLISHER_ID',
appId: 'YOUR_APP_ID',
userId: 'user_12345',
testMode: true,
);
await Perkox.initialize(config);
You can also update the user ID later (e.g., after login):
await Perkox.setUserId('user_12345');
4. Showing the Offerwall
Once initialized, present the offerwall from any widget. The SDK provides a simple async method that returns when the user closes the offerwall:
// earn_rewards_screen.dart
import 'package:flutter/material.dart';
import 'package:perkox_sdk/perkox_sdk.dart';
class EarnRewardsScreen extends StatelessWidget {
const EarnRewardsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Earn Rewards')),
body: Center(
child: ElevatedButton(
onPressed: () => _showOfferwall(context),
child: const Text('Earn Free Coins'),
),
),
);
}
Future<void> _showOfferwall(BuildContext context) async {
try {
await Perkox.showOfferwall(
context: context,
onOpened: () {
debugPrint('[Perkox] Offerwall opened');
},
onClosed: () {
debugPrint('[Perkox] Offerwall closed');
// Refresh balance if using client-side rewards
},
);
} on PerkoxException catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Offers unavailable: ${e.message}')),
);
}
}
}
}
Showing Offerwall Without Context
If you need to show the offerwall from a non-widget context (e.g., a notification handler or background service), use the navigator-based API:
await Perkox.showOfferwall(
onOpened: () => debugPrint('Opened'),
onClosed: () => debugPrint('Closed'),
);
This uses the root navigator and does not require a BuildContext.
Offerwall as a Bottom Sheet
For a more integrated feel, the SDK supports presenting the offerwall as a bottom sheet instead of a full-screen modal:
await Perkox.showOfferwall(
context: context,
presentationStyle: PerkoxPresentationStyle.bottomSheet,
onClosed: () => debugPrint('Closed'),
);
For UX best practices on when and how to present the offerwall, see our Offerwall UX Design Principles guide (referenced from our platform guides).
5. Handling Rewards
The perkox flutter sdk uses Dart streams for reward delivery — a natural fit for Flutter’s reactive programming model. Unlike method-channel-based approaches that require manual event parsing, the SDK emits fully typed Reward objects that you can subscribe to from any widget.
Stream-Based Reward Handling
// reward_service.dart
import 'dart:async';
import 'package:perkox_sdk/perkox_sdk.dart';
class RewardService {
static final RewardService _instance = RewardService._internal();
factory RewardService() => _instance;
RewardService._internal();
late final PerkoxRewardController _controller;
StreamSubscription<Reward>? _subscription;
void initialize() {
_controller = Perkox.rewardController;
_subscription = _controller.rewardStream.listen(
_onRewardReceived,
onError: _onRewardError,
);
}
void _onRewardReceived(Reward reward) {
debugPrint('[Perkox] Reward: ${reward.amount} ${reward.currencyName}');
debugPrint('[Perkox] Transaction: ${reward.transactionId}');
debugPrint('[Perkox] Offer: ${reward.offerName}');
// Update app state — e.g., via a ChangeNotifier, Riverpod, or Bloc
// balanceNotifier.add(reward.amount);
}
void _onRewardError(Object error) {
debugPrint('[Perkox] Reward error: $error');
}
void dispose() {
_subscription?.cancel();
}
}
Integrating with Riverpod
If you use Riverpod for state management, expose rewards as a provider:
// reward_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:perkox_sdk/perkox_sdk.dart';
final rewardControllerProvider = Provider<PerkoxRewardController>((ref) {
return Perkox.rewardController;
});
final rewardStreamProvider = StreamProvider<Reward>((ref) {
final controller = ref.watch(rewardControllerProvider);
return controller.rewardStream;
});
// In a widget:
Consumer(
builder: (context, ref, child) {
final rewards = ref.watch(rewardStreamProvider);
return rewards.when(
data: (reward) => Text('Last reward: ${reward.amount} ${reward.currencyName}'),
loading: () => const SizedBox(),
error: (e, _) => Text('Error: $e'),
);
},
)
Integrating with Bloc
// reward_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:perkox_sdk/perkox_sdk.dart';
class RewardBloc extends Bloc<RewardEvent, RewardState> {
final PerkoxRewardController _controller;
late final StreamSubscription _sub;
RewardBloc(this._controller) : super(RewardInitial()) {
_sub = _controller.rewardStream.listen((reward) {
add(RewardReceivedEvent(reward));
});
on<RewardReceivedEvent>((event, emit) {
emit(RewardEarned(event.reward));
});
}
@override
Future<void> close() {
_sub.cancel();
return super.close();
}
}
Server-Side Postback (S2S) Verification
For production reward delivery, configure a postback URL in the Perkox dashboard. When a user completes an offer, Perkox sends an HTTP request to your backend with reward details and an HMAC-SHA256 signature.
Configure your postback URL in the Publisher Dashboard under App Settings → Postback. Make sure you’ve set the user ID via Perkox.setUserId() so it’s included in the postback.
Postback parameters:
| Parameter | Description | Example |
|---|---|---|
transaction_id |
Unique reward identifier (idempotency key) | tx_8f3a2b9c |
user_id |
User ID passed to the SDK | user_12345 |
amount |
Reward amount in your virtual currency | 250 |
currency |
Currency name | coins |
offer_id |
Identifier of the completed offer | off_4821 |
offer_name |
Human-readable offer name | Reach Level 10 in Raid |
signature |
HMAC-SHA256 signature for verification | a1b2c3d4... |
Example server-side verification (Dart/Shelf):
// postback_handler.dart (Dart backend)
import 'dart:io';
import 'dart:convert';
import 'package:crypto/crypto.dart';
Future<void> handlePostback(HttpRequest request) async {
final params = request.uri.queryParameters;
final txId = params['transaction_id'];
final userId = params['user_id'];
final amount = params['amount'];
final signature = params['signature'];
if (txId == null || userId == null || amount == null || signature == null) {
request.response
..statusCode = HttpStatus.badRequest
..write('ERROR: Missing parameters');
await request.response.close();
return;
}
// 1. Verify HMAC signature
final payload = '$txId|$userId|$amount';
final key = utf8.encode(SECRET_KEY);
final data = utf8.encode(payload);
final hmac = Hmac(sha256, key);
final digest = hmac.convert(data);
final expectedSig = digest.toString();
if (signature != expectedSig) {
request.response
..statusCode = HttpStatus.forbidden
..write('ERROR: Invalid signature');
await request.response.close();
return;
}
// 2. Check idempotency
if (await rewardRepository.exists(txId)) {
request.response.write('OK: Duplicate (already processed)');
await request.response.close();
return;
}
// 3. Credit the user
await rewardRepository.save(txId, userId, int.parse(amount));
request.response.write('OK');
await request.response.close();
}
For the complete postback specification including retry logic, signature algorithms, and testing tools, see our Complete Offerwall Postback Guide.
6. Configuration Options
The PerkoxConfig class supports a wide range of customization options for the offerwall flutter sdk:
final config = PerkoxConfig(
publisherId: 'YOUR_PUBLISHER_ID',
appId: 'YOUR_APP_ID',
userId: 'user_12345',
// Currency configuration
currencyName: 'Gems',
currencyIconUrl: 'https://cdn.yourapp.com/gem-icon.png',
exchangeRate: 1.0, // 1 point = 1 Gem
// Design customization
themeColor: Color(0xFF6C5CE7),
darkMode: PerkoxDarkMode.auto, // auto, light, or dark
// Targeting
ageRating: PerkoxAgeRating.teen, // everyone, teen, or mature
countryFilter: ['US', 'CA', 'GB'],
excludeOfferCategories: ['gambling', 'dating'],
// Placement
placement: 'earn_tab',
// Test mode
testMode: false,
);
Currency Configuration
Set currencyName and exchangeRate to match your app’s virtual economy. The SDK automatically converts offer payouts to your currency and displays them in terms the user understands. An offer paying $0.50 with an exchange rate of 100 coins per dollar shows as “Earn 50 Gems” — not “$0.50” — creating a seamless experience.
Design Customization
The themeColor property accepts a standard Flutter Color object, so you can use the same color constants you use elsewhere in your app. darkMode follows the system setting by default but can be forced to light or dark. The offerwall widget automatically adapts to the platform’s native UI conventions (Material on Android, Cupertino-style on iOS) while respecting your theme overrides.
Targeting
Control which offers are shown to your users:
- Age rating — filter offers by content maturity
- Country filter — restrict offers to specific regions
- Category exclusion — block offer categories that don’t fit your audience
- Placement — tag where the offerwall was shown for analytics segmentation
Track how different placements and configurations perform using the Perkox analytics dashboard. For guidance on which metrics to monitor, see our SDK Security Best Practices and analytics guides.
7. Testing & Debugging
Enabling Test Mode
Test mode displays sandbox offers that convert instantly, letting you verify the full reward pipeline — stream events, postback delivery, and UI updates — without spending real ad budget:
final config = PerkoxConfig(
publisherId: 'YOUR_PUBLISHER_ID',
appId: 'YOUR_APP_ID',
testMode: true, // Enable sandbox
);
await Perkox.initialize(config);
Completing a test offer triggers the same stream events and postback requests as real offers. Set testMode: false before shipping to production.
Debug Logging
Enable verbose logging to trace SDK behavior during development:
Perkox.setLogLevel(PerkoxLogLevel.verbose);
This outputs detailed logs including initialization steps, platform-channel communication, network requests, offer fetching, and reward event processing. Filter your console output with “Perkox” to isolate SDK logs.
Testing on Multiple Platforms
One of Flutter’s strengths is the ability to test on multiple platforms from a single codebase. When testing the Perkox SDK, keep these platform differences in mind:
| Feature | Android | iOS |
|---|---|---|
| Advertising ID | Google Advertising ID (with permission) | IDFA (with ATT permission) |
| Offer availability | Full catalog (real device) | Full catalog (real device) |
| Simulator/Emulator | Limited (test offers) | Limited (test offers) |
| ATT prompt | N/A | Required for IDFA |
Always run final acceptance tests on physical devices for both platforms before releasing to the app stores.
Testing Postbacks
Use the postback tester in the Publisher Dashboard (App Settings → Postback → Test Postback) to send a sample postback to your server and inspect the response. This is the fastest way to validate your endpoint before going live.
8. Troubleshooting Common Issues
Issue: SDK fails to initialize
Symptom: Perkox.initialize() throws PerkoxException with code INIT_001.
Cause: Incorrect publisher ID or app ID, no network connection, or platform-specific dependency not installed.
Fix: Verify credentials in the publisher dashboard. Run flutter clean && flutter pub get and rebuild. For iOS, run cd ios && pod install. Check verbose logs for the specific error.
Issue: Offerwall opens but shows no offers
Symptom: Offerwall loads but displays “No offers available”.
Cause: Targeting filters too restrictive, advertising ID unavailable, or no offers in the user’s region.
Fix: Loosen countryFilter and excludeOfferCategories. Ensure ACCESS_ADVERTISING_ID permission is granted on Android and ATT is authorized on iOS. Test with a device set to a high-offer region like the US or UK.
Issue: Reward stream not emitting events
Symptom: User completes an offer but the reward stream doesn’t emit a Reward event.
Cause: The stream subscription was cancelled or not set up before the offerwall was shown, or the offer has a pending conversion period.
Fix: Subscribe to rewardController.rewardStream before calling showOfferwall(). Ensure the subscription is active (not cancelled). Some offers take hours to convert — check the dashboard transaction log for the offer’s status.
Issue: iOS build fails after adding the SDK
Symptom: pod install fails or Xcode build errors mention missing Perkox modules.
Cause: Podfile deployment target is below 13.0, or CocoaPods cache is stale.
Fix: Set platform :ios, '13.0' in the Podfile. Run pod install --repo-update. If issues persist, delete ios/Podfile.lock and ios/Pods/ then run pod install fresh.
Issue: Android build fails with minSdkVersion error
Symptom: Gradle error: “uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library”.
Cause: Your app’s minSdkVersion is below 21, the minimum required by the Perkox SDK.
Fix: Update minSdkVersion to 21 in android/app/build.gradle. If you need to support lower API levels, contact Perkox support — but note that API 21+ covers over 98% of active Android devices.
Issue: Postback not received by server
Symptom: Backend never receives the postback HTTP request.
Cause: Postback URL is incorrect, server is unreachable, or firewall blocks Perkox IP ranges.
Fix: Verify the URL in the dashboard. Ensure your server accepts public requests and returns HTTP 200. Perkox retries failed postbacks up to 5 times with exponential backoff. Check the dashboard postback log for delivery status.
9. FAQ
Q: Is Perkox the only platform with a native Flutter offerwall SDK?
Yes. Perkox is the first and only offerwall platform to ship a native Flutter SDK written in Dart, with platform-channel bridges to the native Android (Kotlin) and iOS (Swift) SDKs. Competitor platforms require manual method-channel implementations or third-party wrappers, which are fragile, untyped, and break on SDK updates. If you’re building a Flutter app, Perkox is the clear choice for offerwall monetization.
Q: What Flutter and Dart versions are required for the Perkox SDK?
The Perkox Flutter SDK requires Flutter 3.16.0 or later and Dart 3.2.0 or later. It supports Android (minSdk 21) and iOS (min deployment target 13.0). The SDK uses Dart 3 null-safe typed streams for reward handling, so you get full compile-time type safety for reward events.
Q: Do I need to configure both Android and iOS native projects when using the Flutter SDK?
Yes. While the SDK handles reward logic and offerwall presentation entirely in Dart, you must add required permissions to AndroidManifest.xml (INTERNET, ACCESS_ADVERTISING_ID) and Info.plist (NSUserTrackingUsageDescription, SKAdNetworkItems). The SDK automates native SDK installation via Gradle and CocoaPods dependencies, so no manual native code changes are needed beyond the permission entries.
Q: How does stream-based reward handling work in the Flutter SDK?
The SDK exposes a PerkoxRewardController that emits Reward objects via a Dart Stream. You subscribe to the stream using listen() and receive events whenever a user completes an offer. The stream is broadcast-style, allowing multiple listeners. This integrates naturally with Flutter’s reactive state management solutions like Riverpod, Bloc, and Provider. For production, always verify rewards via server-side postbacks in addition to stream events.
Q: Can I use the Perkox Flutter SDK in a Flutter web project?
The Flutter SDK currently supports Android and iOS only. For web-based offerwall integration, use the Perkox JavaScript SDK directly in your web app. Web support for the Flutter SDK is on the roadmap but not yet available. If web support is critical for your project, contact Perkox support for the latest timeline.
10. Get Started
Ready to monetize your Flutter app?
Create a free Perkox publisher account and start earning within minutes.
Related Articles
- What Is an Offerwall? A Developer’s Guide
- The Android Offerwall SDK: Complete Guide
- The iOS Offerwall SDK: Complete Guide
- The Complete Offerwall Postback Guide
- Offerwall SDK Security Best Practices
