Skip to content

Repository files navigation

Ripstop

Ripstop Flutter SDK

CI license: MIT

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.

Installation

Not yet on pub.dev. Until then, depend on the repository directly:

dependencies:
  ripstop:
    git: https://github.com/ripstop-dev/ripstop-flutter.git

or on a local checkout:

dependencies:
  ripstop:
    path: ../ripstop-flutter

Quick start

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()));
}

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.

Or drive it yourself

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.

Remote config

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;

What it does when things break

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.

Everything else

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 app

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.

Conformance

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

License

MIT

About

Ripstop SDK for Flutter. Required updates, update nudges, maintenance mode and remote config.

Topics

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages