Remote config for shipped apps. Update walls and maintenance mode built in, signed, verified on device.
Six months after you ship, a backend change breaks every install older than 3.3.0. You open the panel, set the oldest version you still support, and within seconds those installs show a wall telling them to update. That moment is the product.
Not yet on pub.dev. Until then, depend on the repository directly:
dependencies:
ripstop:
git: https://github.com/ripstop-dev/ripstop-flutter.gitor on a local checkout:
dependencies:
ripstop:
path: ../ripstop-flutterimport '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()));
}That's the whole integration. RipstopShell shows your app until a decision says
otherwise, then renders the right wall.
The version comes from the bundle, so there is no literal to forget to bump.
Pass appVersion: when you need to override it (a staged rollout pretending to
be an older build, or a test) and yours wins. If the platform will not say what
it is running, the SDK has no opinion and your app runs.
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 RsMaintenance(:final message, :final endsAt):
// You are down on purpose.
case RsNone():
// Carry on.
}The switch is exhaustive: if the protocol ever grows a decision, your app stops compiling instead of silently showing nobody anything.
Values ride in the same signed payload as the rules, so reading one costs no extra request:
final enabled = ripstop.values['checkout_enabled'] as bool? ?? true;This is the part worth reading, because a config service that can lock users out of their own app is worse than no config service.
| Situation | What happens |
|---|---|
| No network | The last signed payload drives the decision |
| No network, no cache | RsNone, so your app runs unrestricted |
| Server returns 500 | Cache, then RsNone |
| Signature doesn't verify | Treated as a failure. A forged payload can never wall your app |
| Cache file edited on device | Re-verified on read, so it grants nothing |
The SDK fails open, always. Every payload is Ed25519-signed and verified on device against a pinned key, so a compromised CDN cannot show your users anything, because it does not have the signing key.
minFetchInterval |
How long a payload is fresh enough to skip the network. Default 6 hours |
timeout |
Fetch budget. Default 5 seconds, then cache |
storage |
Swap SharedPreferencesStorage for your own, or InMemoryStorage |
locale |
Which language the wall copy resolves to; falls back to en per key |
signingKeys |
Override the pinned keys, for self-hosted deployments |
onOpenUrl |
How to open the store. Wire url_launcher here; the SDK doesn't depend on it |
example/ is a full runnable app that boots against a signed local
stub, so you can flip through every wall without an account. It is the same app
we use for store-quality screenshots.
All five Ripstop SDKs run the same 62 golden vectors from vectors.json:
version ordering, evaluation order, snooze accounting, the fail-open state
machine. flutter test runs them here. If this package and the TypeScript
reference implementation ever disagree about a single comparison, CI goes red.
Full docs: ripstop.dev/docs/flutter
