Files
shorebird-updater/shorebird_code_push
Eric Seidel 34509fca3c refactor: split C API into Dart and engine surfaces (#350)
The C surface in `library/src/c_api` was a single bucket of `pub extern "C"`
functions covering both consumers — `package:shorebird_code_push` (via
ffigen) and Shorebird's Flutter engine fork (via direct C++ link). That
made it hard to reason about which symbols are stable ABI versus
internal, and ffigen was generating bindings for engine-only symbols
that no Dart code calls.

Split into two self-contained submodules and two cbindgen-generated
headers:

- `c_api::dart` → `include/updater_dart.h` (stable ABI; ffigen entry
  point). Defines `UpdateResult`, the `SHOREBIRD_*` status constants, and
  the five Dart-stable functions: `shorebird_current_boot_patch_number`,
  `shorebird_next_boot_patch_number`,
  `shorebird_check_for_downloadable_update`,
  `shorebird_update_with_result`, `shorebird_free_update_result`.
- `c_api::engine` → `include/updater_engine.h` (no stability guarantee).
  Defines `AppParameters`, `FileCallbacks`, and the engine-only functions:
  `shorebird_init`, `shorebird_should_auto_update`,
  `shorebird_validate_next_boot_patch`, `shorebird_next_boot_patch_path`,
  `shorebird_free_string`, `shorebird_start_update_thread`, and the
  `shorebird_report_launch_*` trio.

Each bucket file is self-contained: cbindgen scans only the file
(`with_src` in build.rs) and emits the items it defines plus the C
types they reference. There are no exclude/include lists in the
cbindgen configs — adding a function to one bucket automatically lands
it in the right header, and items in the other bucket cannot leak.

`mod.rs` shrinks to a thin layer of private helpers shared by both
buckets (`to_rust`, `allocate_c_string`, `free_c_string`, `log_on_error`)
plus the test module.

`include/updater.h` is removed; consumers include the specific header
for their use case. The Flutter engine's
`shell/common/shorebird/updater.cc` will be updated in a follow-up
engine-repo PR to include `updater_engine.h` directly.

Also drops two retired Dart-side symbols:

- `shorebird_update` (replaced by `shorebird_update_with_result` in the
  Dart 2.0 rewrite, Nov 2024).
- `shorebird_check_for_update` (replaced by
  `shorebird_check_for_downloadable_update` in the same rewrite).

The shorebird_code_push package's `_legacyFallback` was the only path
that still called `shorebird_update`. The package's `flutter: >=3.24.5`
constraint guarantees the engine has `shorebird_update_with_result`, so
the fallback was unreachable in practice. Removing it lets us drop the
ABI symbol.

Bumps shorebird_code_push to 2.0.7. Bindings regenerated via ffigen now
contain only the five Dart-stable symbols.

Follow-up engine PR will: include `updater_engine.h` instead of the
removed `updater.h`; clean up `android_exports.lst` (drop the ghost
`shorebird_active_path` and `shorebird_active_patch_number` exports,
drop `shorebird_check_for_update`).
2026-05-04 15:56:09 -07:00
..
2023-06-20 19:55:08 -04:00

Shorebird logo

Code Push

Discord

ci codecov License: MIT

Instantly push updates to your Flutter app without lengthy app store review cycles.

WebsiteDocsXYouTube

This Dart package communicates with the Shorebird Code Push Updater to:

  • Get the currently installed patch version
  • Check whether a new patch is available
  • Download new patches

Demo

Explore this interactive demo to learn more

Getting Started

If your Flutter app does not already use Shorebird, follow our Getting Started Guide to add code push to your app.

Installation

flutter pub add shorebird_code_push

Usage

Shorebird automatically checks for and downloads updates in the background. Most apps do not need this package. This package is for apps that want additional control, such as displaying update status to the user or prompting before downloading.

Important: checkForUpdate() and update() make network calls that may be slow. Avoid gating app startup on the result (e.g. awaiting in initState), as the app may appear stuck on the splash screen. Use .then() instead.

import 'package:shorebird_code_push/shorebird_code_push.dart';

void main() => runApp(const MyApp());

// [Other code here]

class _MyHomePageState extends State<MyHomePage> {
  final updater = ShorebirdUpdater();
  Patch? _currentPatch;
  bool _updateAvailable = false;

  @override
  void initState() {
    super.initState();

    // Read the current patch number (null if no patch is installed).
    updater.readCurrentPatch().then((patch) {
      setState(() => _currentPatch = patch);
    });

    // Check if an update is available to show in the UI.
    updater.checkForUpdate().then((status) {
      setState(() => _updateAvailable = status == UpdateStatus.outdated);
    });
  }

  // [Other code here]
}

See the example for a complete working app.

Tracks

Shorebird supports publishing patches to different tracks, which can be used to target different segments of your user base. See the percentage based rollout guide for implementation details.

You must first publish a patch to a specific track (patches are published to the stable track by default). To publish a patch to a different track, update your patch command to use the --track argument:

shorebird patch android --track beta

(We're just using Android for this example. Tracks are supported on all platforms).

To check for updates on a given track, pass an UpdateTrack to checkForUpdate (and update if you use it):

updater.checkForUpdate(track: UpdateTrack.beta);

You can also use custom track names. When creating a patch, specify a track name like this:

shorebird patch android --track my-custom-track

And:

updater.checkForUpdate(track: UpdateTrack('my-custom-track'));

Note: Updating to a specific track does not uninstall patches from other tracks. See #3484 for details.

Join us on Discord!

We have an active Discord server where you can ask questions and get help.

Contributing

See CONTRIBUTING.md.