Flutter
Install and wire the Rovenue SDK in a Flutter app.
Install the package
Add the app-facing package to your pubspec.yaml:
dependencies:
rovenue_flutter: ^0.16.0Then fetch it:
flutter pub getrovenue_flutter is a federated plugin — rovenue_flutter_ios and rovenue_flutter_android are pulled in automatically as platform implementations and wrap the same Swift and Kotlin SDKs (and the shared Rust core, librovenue) that back the native iOS and Android SDKs. Android needs no further setup.
iOS: the CocoaPods dependency isn't published yet. rovenue_flutter_ios's podspec depends on the Rovenue pod (packages/sdk-swift), which has not been published to CocoaPods Trunk — its podspec still carries a placeholder release-archive checksum. Until that pod ships, flutter pub get alone will not resolve on iOS for an app outside this monorepo. In the meantime, override the dependency in your ios/Podfile to point at a local checkout of sdk-swift:
target 'Runner' do
use_frameworks!
pod 'Rovenue', :path => '/path/to/sdk-swift'
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
endRemove the override once Rovenue is published to Trunk. rovenue_flutter_ios also raises the deployment target to iOS 16.0 to match the Rovenue pod's own floor.
Configure the SDK
Call Rovenue.instance.configure once, before any other SDK method — the earliest point in main() is the right place:
import 'package:flutter/material.dart';
import 'package:rovenue_flutter/rovenue_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Rovenue.instance.configure(
apiKey: 'rov_pub_YOUR_API_KEY',
);
runApp(const MyApp());
}Self-hosting? baseUrl is optional and defaults to the hosted endpoint https://api.rovenue.io. Override it to point at your own deployment, e.g. Rovenue.instance.configure(apiKey: 'rov_pub_YOUR_API_KEY', baseUrl: 'https://api.acme.com'). The host must use https:// — plain http:// is accepted only for localhost during local development.
Types on this page
Future<void> configure({
required String apiKey, // public SDK key — prefix rov_pub_
String? baseUrl, // optional — defaults to https://api.rovenue.io
RovenueLogLevel logLevel = RovenueLogLevel.warn,
String? appVersion, // override auto-detected version
String? environment,
});| Field | Type | Description |
|---|---|---|
apiKey | String | Public SDK key from your Rovenue dashboard. Prefix rov_pub_. |
baseUrl | String? | Optional. Defaults to https://api.rovenue.io. Override only when self-hosting (https://; http:// allowed for localhost only). |
logLevel | RovenueLogLevel | Verbosity of SDK logs (off/error/warn/info/debug/trace). Defaults to warn. |
appVersion | String? | Overrides the version auto-read from the app bundle. |
environment | String? | Optional environment label forwarded with requests. |
Wire foreground signals
The SDK uses setForeground to manage its internal polling cadence. Wire it to WidgetsBindingObserver:
import 'package:flutter/material.dart';
import 'package:rovenue_flutter/rovenue_flutter.dart';
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
Rovenue.instance.setForeground(true);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
Rovenue.instance.setForeground(state == AppLifecycleState.resumed);
}
@override
Widget build(BuildContext context) => const MaterialApp(home: RootScreen());
}Rovenue.instance.shutdown() stops all background work (polling, in-flight refreshes). Reserve it for full app teardown — do not call it on every background transition. setForeground(false) already pauses polling when the app backgrounds.
Identifying users
Anonymous subscribers get a permanent id automatically. Call identify once your own auth flow completes, to link that install to your backend user:
await Rovenue.instance.identify('user_123');
// When your user signs out:
await Rovenue.instance.logOut();Unlike the iOS and Android examples, packages/sdk-flutter/example does
not call identify() — it demonstrates the anonymous-subscriber path only.
The method exists and works identically on Flutter; add this step if your
app has its own auth. See Identifying Users
for the full identity model.
Entitlements
final pro = await Rovenue.instance.entitlement('pro');
if (pro?.active ?? false) {
// unlock the feature
}
final all = await Rovenue.instance.entitlementsAll();// Entitlement
class Entitlement {
final String id;
final bool active;
final String? expiresAt; // ISO 8601 — absent for non-expiring grants
final String? productId;
}Listen for server-pushed updates with Rovenue.instance.changes and re-read on any event:
final sub = Rovenue.instance.changes.listen((event) {
// event.kind: entitlementsChanged | identityChanged |
// virtualCurrenciesChanged | remoteConfigChanged
Rovenue.instance.entitlementsAll().then((updated) {
// update your local state with `updated`
});
});
// Cancel on teardown:
await sub.cancel();entitlementsAll() is a local cache read — never call refreshEntitlements()
from inside this listener. refreshEntitlements() hits the network and, on
success, re-emits entitlementsChanged itself, which would re-invoke this
same listener forever.
See Entitlements and Reacting to Changes for the full guides.
Offerings & purchases
Use getOfferings() to fetch products configured in your Rovenue dashboard, then purchase(product) to drive the full purchase flow. The SDK triggers the native StoreKit 2 / Play Billing sheet, validates the receipt server-side, and resolves with updated entitlements and virtual-currency balances. On Android the SDK acknowledges or consumes the Play purchase on the device after the server verified it, and purchase resolves even when Play cannot take that acknowledgement yet — see Who acknowledges a Play purchase.
import 'package:rovenue_flutter/rovenue_flutter.dart';
final offerings = await Rovenue.instance.getOfferings();
final products = <StoreProduct>[
for (final offering in offerings.offerings)
for (final package in offering.packages) package.product,
];
if (products.isNotEmpty) {
try {
final result = await Rovenue.instance.purchase(products.first);
// result.entitlements and result.virtualCurrencies are already up to date
} on RovenueException catch (e) {
if (e.kind == RovenueErrorKind.purchaseCanceled) {
// User dismissed the store sheet — no action needed
} else {
rethrow;
}
}
}To re-validate all prior transactions (e.g. after a reinstall):
await Rovenue.instance.restorePurchases();purchase also accepts an optional Android SubscriptionOption for base-plan/offer selection: Rovenue.instance.purchase(product, option: option).
See Processing Purchases for the full API reference including all error kinds and the StoreProduct shape.
Funnels
Placements serve native funnels, drawn by RovenueFunnelView (see
Placements). There is no
Dart getFunnel — the platform side resolves the placement itself:
import 'package:rovenue_flutter/rovenue_flutter.dart';
final onboarding = RovenueFunnelView(placementIdentifier: 'onboarding_end');Purchase attribution is automatic: the funnel's presentedContext is
attached to the next purchase() for you.
iOS: declare the languages your app supports
On iOS the funnel draws in the device's language only if your app declares
that language: iOS hands an app Locale.current from its declared
localizations, so an app that declares only English reaches the SDK as
en_XX (en_TR on a Turkish device) and shows the funnel's English copy.
Add CFBundleLocalizations (for example en, tr) to
ios/Runner/Info.plist, or add the localizations to the Runner project in
Xcode. Android needs nothing.
Funnel attribution
Resolve a funnel token from a deep link, an explicit install claim, or — on iOS — the clipboard:
import 'package:rovenue_flutter/rovenue_flutter.dart';
const incomingUrl = '<the URL that opened the app>';
// From a deep link / Universal Link handler:
final claim = await Rovenue.instance.claimFromUrl(incomingUrl);
// Or resolve one explicitly:
final installClaim = await Rovenue.instance.claimInstall();
// iOS only — always resolves null on Android (see the callout below):
final pastedClaim = await Rovenue.instance.claimFromClipboard();
await Rovenue.instance.claimViaEmail('user@example.com');
if (claim != null) {
// claim.subscriberId, claim.funnelAnswersJson (JSON-encoded string)
}Claims resolved asynchronously on the native side (a deferred deep link or a clipboard token picked up after configure) arrive on the funnelClaims stream, which replays the most recent claim to a new listener:
final sub = Rovenue.instance.funnelClaims.listen((claim) {
// handle claim.subscriberId / claim.funnelAnswersJson
});claimFromClipboard is an iOS-only path. On Android it always resolves null without reading the clipboard — Android's deferred-attribution source is the Play Install Referrer, which claimInstall reads for you. The method exists on both platforms so shared code needs no branching, but only claimInstall can recover a deferred Android claim.
Flutter's funnel claim is single-shot: claimFromUrl/claimInstall/claimFromClipboard each make one resolution attempt and return (or throw). The React Native SDK's resolveFunnelClaim additionally chains a retry/fallback sequence across multiple claim sources — that chain has not been ported to Flutter. Callers that need the same fallback behavior should compose it themselves from the primitives above.
Error handling
Every public method throws RovenueException — never a raw PlatformException:
import 'package:rovenue_flutter/rovenue_flutter.dart';
try {
await Rovenue.instance.identify('user_123');
} on RovenueException catch (e) {
switch (e.kind) {
case RovenueErrorKind.networkUnavailable:
case RovenueErrorKind.timeout:
// e.retryable is true for these — safe to retry
break;
case RovenueErrorKind.invalidApiKey:
case RovenueErrorKind.forbidden:
// configuration problem — do not retry
break;
default:
print('Rovenue error: ${e.kind.name} — ${e.detail}');
}
}class RovenueException implements Exception {
final RovenueErrorKind kind;
final String detail; // always non-null
final String? serverCode; // present for API-originated errors
final int? httpStatus; // present for API-originated errors
final bool retryable;
}| Field | Type | Description |
|---|---|---|
kind | RovenueErrorKind | Normalized error category, shared across every Rovenue SDK façade. |
detail | String | Human-readable message. Falls back to the kind's name when the native side sent none. |
serverCode | String? | Server-issued error code, when the error came from an API response. |
httpStatus | int? | HTTP status code, when the error came from an API response. |
retryable | bool | Whether the operation is safe to retry. |
Feature parity with React Native
Flutter tracks the React Native SDK as its parity bar. As of this release:
| Area | Status |
|---|---|
| Configure, identity, entitlements, virtual currencies | Full parity |
| Offerings, purchase, restore, subscription options | Full parity |
| Remote config, experiments | Full parity |
setFallbackPlacements | Full parity |
Funnel token extraction (extractFunnelToken), claimFromUrl/claimInstall/claimViaEmail/claimFromClipboard | Full parity |
resolveFunnelClaim retry/fallback chain | Not ported — see Funnel attribution above |
Funnels
The widget hosts the native renderer as a platform view — there is no Dart
getFunnel (packages/sdk-flutter/rovenue_flutter/lib/src/funnel_view.dart:10-14) — full path: Show a funnel.
RovenueFunnelView buildOnboardingFunnel() {
return const RovenueFunnelView(placementIdentifier: 'onboarding');
}What's next
- Identifying Users — link your backend user to the Rovenue subscriber.
- Entitlements — read and gate on active subscriptions.
- Virtual Currencies — read virtual-currency balances and grant/spend via server-side APIs.
- Processing Purchases — full purchase API and error handling.
- Placements — native funnels and how placements resolve them.
- App Lifecycle — foreground/background wiring in depth.
- Reference: Methods — full method reference.
- Reference: Types — all SDK types in one place.
- SDK API Reference — the generated dartdoc reference for every public Dart type and method, at
/api/sdk-flutter/.