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

pubspec.yaml
dependencies:
ripstop: ^0.1.0

Quickstart

Hand the gate to RipstopShell and it renders the right wall at the right time. This is the whole integration.

main.dart
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.

Dart
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.

Dart
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

Dart
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

OptionDefaultWhat it does
apiKeyno defaultYour app’s public SDK key. Safe in a binary
appVersionno defaultThe running build; rules are evaluated against it
platformdetectedOverride for tests or unusual targets
localeenWhich wall copy to resolve; falls back to en per key
minFetchInterval6 hoursHow long a payload is fresh enough to skip the network
timeout5 secondsFetch budget. After that, cache
storageSharedPreferencesSwap for your own, or InMemoryStorage
signingKeyspinnedOverride 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.

Dart
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.

⌘K