SDK
Flutter
Dart 3.3+, Flutter 3.19+. Three dependencies, prebuilt walls you can theme, and the same golden vectors every other Ripstop SDK runs.
Install
dependencies:
ripstop: ^0.1.0Quickstart
Hand the gate to RipstopShell and it renders the right wall at the right time. This is the whole integration.
import 'package:flutter/material.dart';
import 'package:ripstop/ripstop.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final ripstop = await Ripstop.init(apiKey: 'rs_pub_your_key');
runApp(RipstopShell(gate: ripstop, child: const MyApp()));
}Headless
If you would rather draw your own screens, take the decision and switch on it. The switch is exhaustive: when the protocol grows a decision, your app stops compiling instead of silently showing nobody anything.
switch (await ripstop.check()) {
case RsForceUpdate(:final title, :final body, :final storeUrl):
// Blocked. Not dismissible.
case RsSoftUpdate(:final title, :final canSnooze):
// A nudge. ripstop.snooze() records it and re-evaluates.
case RsKilled(:final message):
// This build has been withdrawn.
case RsMaintenance(:final message, :final endsAt, :final showEta):
// You are down on purpose. endsAt is display-only.
case RsNone():
// Carry on.
}Remote config
Values ride in the same signed payload as the rules, so reading one costs no extra request and cannot be out of step with them.
final checkout = ripstop.values['checkout_enabled'] as bool? ?? true;
final limit = (ripstop.values['upload_limit'] as num?)?.toInt() ?? 10;Always pass a fallback. On a first launch with no network there is no payload yet, and the map is empty. That is the fail-open path working as intended.
Theming the walls
RipstopShell(
gate: ripstop,
theme: RsTheme.dark().copyWith(
accent: const Color(0xFF2F6BFF),
onAccent: Colors.white,
logo: Image.asset('assets/logo.png', height: 28),
),
onOpenUrl: (url) => launchUrlString(url), // url_launcher
child: const MyApp(),
)RsTheme.of(context) is the default and follows your app’s ColorScheme. The SDK does not depend on url_launcher; supply onOpenUrlso apps that never show a store link don’t carry it.
Snooze
A soft prompt is a favour you are asking, so it can be dismissed within limits you set in the panel. The SDK keeps the ledger per target version.
max_snoozes: how many times it may be dismissed. After that the prompt keeps returning but loses its dismiss button.cooldown_hours: how long a dismissal buys, measured against the device’s own snooze timestamp, never a server clock.- A new target version resets the allowance. A fresh release is a fresh ask.
Options
| Option | Default | What it does |
|---|---|---|
apiKey | no default | Your app’s public SDK key. Safe in a binary |
appVersion | no default | The running build; rules are evaluated against it |
platform | detected | Override for tests or unusual targets |
locale | en | Which wall copy to resolve; falls back to en per key |
minFetchInterval | 6 hours | How long a payload is fresh enough to skip the network |
timeout | 5 seconds | Fetch budget. After that, cache |
storage | SharedPreferences | Swap for your own, or InMemoryStorage |
signingKeys | pinned | Override for self-hosted deployments and tests |
Testing your integration
Inject a stub client and your own key pair, and you can assert every wall without a network or an account. This is exactly how the SDK’s own tests work.
final gate = await Ripstop.init(
apiKey: 'rs_pub_test',
appVersion: '3.0.0',
signingKeys: {'k1': myTestPublicKeyB64},
storage: InMemoryStorage(),
httpClient: myStubClient,
);
expect(await gate.check(), isA<RsForceUpdate>());Example app
The repository contains an example/ app that signs its own payloads locally, so you can see all four walls without an account. Change one constant and hot-restart.
Source
github.com/ripstop-dev/ripstop-flutter (MIT). CI runs format, analyze and the golden vectors on every push.