dbrij_ship 0.1.1
dbrij_ship: ^0.1.1 copied to clipboard
Dbrij Ship for Flutter and Dart: feature flags, remote config, a kill switch and store version policy, resolved per device on one check in.
example/dbrij_ship_example.dart
// A complete, runnable use of Dbrij Ship in plain Dart:
//
// dart run example/dbrij_ship_example.dart shp_your_app_key
//
// The same code works unchanged in a Flutter app. There, swap FileShipStorage for an
// adapter over shared_preferences (the README has the six lines) and read the
// version from package_info_plus instead of typing it.
import 'dart:convert';
import 'dart:io';
import 'package:dbrij_ship/dbrij_ship.dart';
/// Keeps the device id and the last answer in one small file.
///
/// Storage is not optional in a shipped app. Without it the device id is made again on
/// every launch, which turns one phone into thirty billable devices a month and
/// rerolls it in and out of every staged rollout.
class FileShipStorage implements ShipStorage {
FileShipStorage(this.file);
final File file;
Future<Map<String, dynamic>> _read() async {
if (!await file.exists()) return <String, dynamic>{};
try {
return jsonDecode(await file.readAsString()) as Map<String, dynamic>;
} on FormatException {
return <String, dynamic>{};
}
}
@override
Future<String?> getItem(String key) async => (await _read())[key] as String?;
@override
Future<void> setItem(String key, String value) async {
final all = await _read();
all[key] = value;
await file.writeAsString(jsonEncode(all));
}
}
Future<void> main(List<String> args) async {
if (args.isEmpty) {
stderr.writeln(
'Pass your app key: dart run example/dbrij_ship_example.dart shp_...');
exitCode = 64;
return;
}
final ship = Ship(
appKey: args
.first, // from the Setup card on the Ship dashboard; public, not a secret
channel: 'production',
binaryVersion: '1.4.0',
platform: Platform.isIOS ? 'ios' : 'android',
storage: FileShipStorage(
File('${Directory.systemTemp.path}/dbrij_ship_example.json')),
);
try {
final answer = await ship.checkIn();
// The kill switch: everything remote is off and your fallbacks are in charge.
if (answer.killSwitch) {
stdout.writeln('Kill switch is on: running on built in defaults.');
}
// The store version policy: the half of "update the app" that needs a store review.
final binary = answer.binaryUpdate;
if (binary != null) {
stdout.writeln(binary.required
? 'This version is no longer supported. Update to ${binary.latestVersion}: ${binary.url ?? 'see the store'}'
: 'Version ${binary.latestVersion} is available. ${binary.message}');
}
} on ShipException catch (e) {
// Never let a release desk break the app it exists to protect: the reads below
// still answer, from the last successful check in or from your fallbacks.
stdout.writeln(
'Could not check in (${e.message}); using what we already have.');
}
// Both read the last answer, which is cached, so they work offline. The fallback
// is what shipped in your binary: it runs on first launch and under the kill switch.
final newCheckout = ship.getFlag('new_checkout');
final banner = ship.getConfig<String>('banner', '');
stdout.writeln('new_checkout: $newCheckout');
stdout.writeln('banner: ${banner.isEmpty ? '(none)' : banner}');
ship.close();
}