Flutter SDK
Preview · 0.1.0-beta.4 · Flutter 3.22+ · Android API 23+ / iOS 15+
Download the Flutter plug-in, unzip it into your app (for example packages/pushhub_flutter), and add it as a path dependency until pub.dev promotion:
dependencies:
pushhub_flutter:
path: packages/pushhub_flutter
A Git dependency on sdk/flutter in https://github.com/kemegames-studio/pushhub.git, pinned to the release commit, works too.
await PushHubFlutter.configure(appId: appId, publishableKey: 'ph_pub_YOUR_KEY');
final installationId = await PushHubFlutter.start();
await PushHubFlutter.identify('player-1842', attributes: {'tier': 'gold'});
await PushHubFlutter.track('level_completed', properties: {'level': '20'});
The MethodChannel delegates to Kotlin and Swift cores. The package validates client-safe credentials before crossing the channel.
Android. Nothing else to install. The plug-in depends on com.kemegames.pushhub:pushhub-android:0.1.0-beta.7 and adds PushHub's Maven repository (https://docs.pushhub.kemegames.com/maven, limited to the com.kemegames.pushhub group) to your Gradle projects, so the Android core and its dependencies resolve on a clean machine. Earlier plug-in versions expected the core in Maven local; that step is no longer needed. If your settings.gradle sets RepositoriesMode.FAIL_ON_PROJECT_REPOS (Flutter's template doesn't), add that repository in settings.gradle instead. Add your Firebase configuration (google-services.json) as for any Firebase app.
Status. A new flutter create app with the plug-in from its zip builds with flutter build apk --debug (scripts/sdk-consumer-validation/flutter.mjs). It hasn't been run on a device or emulator, so the plug-in stays Preview. Instant refresh and signed config come from the Android core, but they haven't been shown through the Flutter API, so this guide doesn't claim them.
Android: notification permission and errors
Version requirements: this applies from plug-in 0.1.0-beta.4. In plug-in 0.1.0-beta.3 and earlier, requestPermission() always throws a PlatformException on Android, and a failed configure() throws with the code invalid_configuration.
On Android:
requestPermission()shows the system notification prompt on Android 13 and later, if the user hasn't answered it yet. It doesn't wait for the answer: it returns the current permission state straight away, so it returnsfalsewhile the prompt is still open. On Android 12 and earlier there's no prompt, and it returns whether notifications are enabled for the app.requestPermission()needs the app's Activity. Without one, for example from a background isolate, it throws aPlatformExceptionwith the codeactivity_unavailable.- Other failures throw a
PlatformExceptionwith the codepushhub_error, as on iOS. This includes a failedconfigure(), which threwinvalid_configurationin plug-in 0.1.0-beta.3 and earlier, so if you check for that code, check forpushhub_errorinstead.
Notification permission only decides whether notifications are shown. It isn't data-processing consent (setConsent), and remote config works without it: see SDK support levels.
iOS setup
Version requirements: these steps apply to plug-in 0.1.0-beta.2 and later. Its iOS code needs the next iOS SDK release, which is pending a macOS build, and doesn't build against iOS SDK 0.1.0-beta.1. Plug-in 0.1.0-beta.1 doesn't declare the iOS SDK pod, and on iOS it implements only configure, identify, unlinkUser, setConsent, track and reset.
The iOS SDK isn't on the CocoaPods trunk, so add it to ios/Podfile inside the Runner target, then run pod install (or flutter run). The pods need iOS 15 or later, so also set the Podfile's platform:
platform :ios, '15.0'
target 'Runner' do
use_frameworks!
pod 'PushHub', :git => 'https://github.com/kemegames-studio/pushhub.git', :commit => 'COMMIT_SHA'
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
# ...
end
Pin a commit that contains PushHub.podspec, or the iOS SDK release tag once it's published (:tag => '...'). Without this line, pod install fails with Unable to find a specification for 'PushHub'.
Enable the Push Notifications capability in Xcode. The plug-in receives the APNs token through FlutterAppDelegate, so no AppDelegate code is needed. If your AppDelegate overrides application(_:didRegisterForRemoteNotificationsWithDeviceToken:), call super from it.
On iOS:
start()registers with APNs without showing a prompt, waits up to 60 seconds for the token, then returns the installation ID. It throws aPlatformExceptionifconfigure()hasn't completed, if APNs registration fails, or if no token arrives in time.requestPermission()shows the iOS notification prompt the first time and returns whether the user allowed notifications. Unlike on Android, it waits for the user's answer.reportOpened(payload, actionId: ...)takes the notification'suserInfo, as your notification plug-in delivers it.- Failures throw a
PlatformExceptionwith the codepushhub_error, anddiagnostics()returns the same keys as on Android.
Remote config
Remote config lets you change values in a shipped app, such as starter coins, a maintenance flag or a store layout, from the PushHub dashboard. You reach it through PushHub.config (PushHub is an alias of PushHubFlutter).
Version requirements: remote config needs plug-in 0.1.0-beta.2 or later; 0.1.0-beta.1 doesn't have it. On Android, 0.1.0-beta.2 uses Android SDK 0.1.0-beta.6. On iOS it needs the next iOS SDK release, which is pending a macOS build: the plug-in's iOS code calls the iOS SDK's PushHub.config API, so it doesn't build against an earlier iOS SDK.
Setup and defaults
Set in-app defaults before you read any value. The getters use them before the first fetch and for any key the server leaves out.
PushHub.config.setDefaults({
'starter_coins': 100,
'maintenance_mode': false,
'welcome_text': 'Welcome!',
'store_layout': {'tabs': ['offers', 'coins']}, // a Map/List, or a JSON string
});
setDefaults replaces the previous defaults, and a null value is ignored.
Fetch on load
await PushHub.configure(appId: appId, publishableKey: 'ph_pub_YOUR_KEY');
await PushHub.start();
final changed = await PushHub.config.fetchAndActivate(); // true when the active values changed
Fetching needs a registered installation, so call it after start(). To apply config only at a safe moment, such as a loading screen, fetch and activate separately:
await PushHub.config.fetch(); // downloads, but the getters don't change yet
// ...later, on the loading screen
if (await PushHub.config.activate()) applyConfig();
Read values
final coins = PushHub.config.getInt('starter_coins');
final maintenance = PushHub.config.getBool('maintenance_mode');
final multiplier = PushHub.config.getDouble('price_multiplier');
final text = PushHub.config.getString('welcome_text');
final layoutJson = PushHub.config.getJson('store_layout'); // raw JSON text, or null
final layout = layoutJson == null ? null : jsonDecode(layoutJson);
final source = PushHub.config.getValue('starter_coins').source; // remote, defaultValue or staticValue
final all = PushHub.config.getAll(); // Map<String, PushHubRemoteConfigValue>
Each getter looks for the active remote value first, then your in-app default, and otherwise returns the type's zero value ('', false, 0, or null for JSON). The getters are synchronous and never throw. PushHubRemoteConfigSource uses defaultValue and staticValue because default is a reserved word in Dart.
Listen for updates
final subscription = PushHub.config.onUpdated.listen((keys) {
if (keys.contains('maintenance_mode')) refreshMaintenanceBanner();
});
// later: subscription.cancel();
onUpdated is a broadcast Stream<Set<String>>. It emits after an activation that changed values, with only the keys that were added, removed or changed.
Offline behaviour
The native SDK stores the active values, ETag, version and last fetch time on the device. When configure() completes, the last activated config is loaded, so it is available offline and on the next cold start before any network call.
A failed fetch never throws: fetchAndActivate() and fetch() complete with false, print a debug message, and keep the active values. Each fetch sends the stored ETag, so an unchanged config costs a 304 Not Modified.
Fetch interval and timeout
A fetch reaches the network at most once per interval. The interval is the larger of the server's minimumFetchIntervalSeconds (900 seconds by default) and PushHub.config.minimumFetchInterval. Inside the interval, a fetch succeeds and uses the stored config. Pass force: true to skip the interval in development builds. Each fetch times out after fetchTimeout: 10 seconds by default, and it can be set from 1 to 300 seconds.
PushHub.config.minimumFetchInterval = const Duration(hours: 1);
await PushHub.config.fetchAndActivate(force: kDebugMode);
Instant refresh
When you publish, roll back or schedule a publish with Notify devices set to Test devices only or All players, PushHub sends the environment's devices a silent message. The native SDK fetches and activates the newer config, with no Dart code:
- Android: from plug-in 0.1.0-beta.2, which uses Android SDK 0.1.0-beta.6, refreshes are applied while the app is running. Only that case is supported: the Android core 0.1.0-beta.7 in plug-ins 0.1.0-beta.3 and 0.1.0-beta.4 can configure itself when Android starts the app's process for a refresh, but that hasn't been validated with Flutter, so don't rely on it. See SDK support levels.
- iOS: from plug-in 0.1.0-beta.2 with the next iOS SDK release (pending a macOS build). The plug-in takes PushHub's background notifications through
FlutterAppDelegate, so noAppDelegatecode is needed. In Xcode, add Background Modes to theRunnertarget and turn on Remote notifications.
Signed config is not available in this SDK. It has no API to set trusted signing keys, so it accepts unsigned config even when Config signing is turned on for the environment. Native verification exists in the Android core only when your own Android code sets the keys. See SDK support levels.
FlutterAppDelegate offers each background notification to one plug-in after another and stops at the first that claims it. If another plug-in, such as firebase_messaging, claims every notification, forward to PushHub yourself in ios/Runner/AppDelegate.swift:
import PushHub
override func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if PushHub.handleBackgroundNotification(userInfo, completionHandler: completionHandler) { return }
super.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler)
}
The native SDK activates the new values right away. PushHub.config shows them, and onUpdated emits, the next time Dart calls fetchAndActivate() or activate() (a call inside the minimum interval makes no request but still picks them up), or at the next launch. Instant refresh is best effort, so keep fetching on load.
Platforms
- Android: uses the Android SDK's
PushHub.config(0.1.0-beta.7 in plug-ins 0.1.0-beta.3 and 0.1.0-beta.4). - iOS: uses the iOS SDK's
PushHub.config, available in the next iOS SDK release. - Other platforms and widget tests without a mocked channel: no network calls. The getters return your in-app defaults, the fetch methods complete with
false, and the plug-in prints this once.