---
title: "Percentage-Based Rollouts"
description: "Gradually roll out patches to your users"
---

This guide describes how you can implement a percentage-based patch rollout system using predefined tracks.

The sample code for this guide can by found at <https://github.com/shorebirdtech/samples/tree/main/progressive_rollout_demo>

This percentage-based rollout system has three parts:

1. Using Shorebird’s “tracks” feature to publish patches to different sets of users.
2. Using a cloud key-value (KV) store to manage rollout percentages for each release version.
3. Randomly assigning every device a “group number” between 1 and 100. If a device’s group number is less than or equal to the current rollout percentage for the current app version, that user will get the partially rolled-out (“beta”) patch. Otherwise, the user will receive the stable patch.

## Add Shorebird to your app

[Section titled “Add Shorebird to your app”](#add-shorebird-to-your-app)

If you haven’t already, run Shorebird init in your Flutter project to add Shorebird to your app.

# Add the shorebird\_code\_push package

[Section titled “Add the shorebird\_code\_push package”](#add-the-shorebird_code_push-package)

This feature also requires v2.0.0 of [`package:shorebird_code_push`](https://pub.dev/packages/shorebird_code_push). You can add this to your project using `flutter pub add shorebird_code_push` or by adding it to your `pubspec.yaml` manually.

You will need to update your `shorebird.yaml` to tell Shorebird you want to manage your own updates:

```
app_id: your-app-id-here

# Add this line. Setting auto_update to false tells Shorebird not to check
# for stable track updates on app launch

auto_update: false
```

## Add logic to bucket your users

[Section titled “Add logic to bucket your users”](#add-logic-to-bucket-your-users)

Do this by assigning each user a number between 1 and 100 and saving that value to a local cache using [`package:shared_preferences`](https://pub.dev/packages/shared_preferences):

```
Future<int> getGroupNumber() async {
  final prefs = await SharedPreferences.getInstance();
  final cachedGroupNumber = prefs.getInt(groupKey);
  if (cachedGroupNumber != null) return cachedGroupNumber;
  final groupNumber = Random().nextInt(100) + 1;
  await prefs.setInt(groupKey, groupNumber);
  return groupNumber;
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final groupNumber = await getGroupNumber();
  runApp(MyApp(groupNumber: groupNumber));
}
```

## Add logic to determine rollout percentage

[Section titled “Add logic to determine rollout percentage”](#add-logic-to-determine-rollout-percentage)

This guide uses Firebase’s Cloud Firestore to create a simple key-value pairing of release versions to rollout percentages, although any method of retrieving a rollout percentage from the cloud will work.

```
Future<int?> _fetchRolloutPercentage() async {
  final collection = await FirebaseFirestore.instance
    .collection(firestoreCollectionName)
    .get();
  if (collection.docs.isEmpty) return null;
  final releaseVersion = await _releaseVersion();
  return collection.docs.first.data()[releaseVersion] as int?;
}
```

## Use these values to choose a track

[Section titled “Use these values to choose a track”](#use-these-values-to-choose-a-track)

Now, tie this all together by using the group number and the rollout percentage to decide whether a user should get patches in the beta track or in the stable track.

```
Future<UpdateTrack> _updateTrack() async {
  final rolloutPercentage = await _fetchRolloutPercentage();

  // If no rollout percentage is set for this version, default to stable.
  if (rolloutPercentage == null) return UpdateTrack.stable;

  // If the user's group number is less than or equal to the rollout
  // percentage, they are on the beta track. For example:
  // - if the rollout percentage is 25%, users with group numbers 1-25
  //   are on the beta track, and the other 75% are on the stable track.
  // - if the rollout percentage is 100%, all users are on the beta track.
  return widget.groupNumber <= rolloutPercentage
      ? UpdateTrack.beta
      : UpdateTrack.stable;
}
```

## Seeing it in action

[Section titled “Seeing it in action”](#seeing-it-in-action)

Start by creating a release for Android:

```
shorebird release android
```

This will create a release build of your app (an aab file) that is patchable with Shorebird. You will distribute this build to your users the same way you distribute your apps today.

Now, launch the example using `shorebird preview`, which downloads the release and runs it on a local device:

![Release build showing group number 76 with a red background](/_astro/rollout_group_76_red.CEn2Dvj__2rhAUh.webp)

This screenshot shows that the device is in group 76. This means that it will request patches on the stable track until the rollout percentage is >= 76, at which point it will start requesting patches in the beta track.

Because a patch hasn’t been created on the beta track or a rollout percentage set yet, there isn’t much to see yet.

Change the background color from `Colors.deepPurple` to `Colors.red` and create a patch on the beta track using the following command:

```
shorebird patch android --track=beta
```

And update the rollout percentage in Firebase:

![Cloud Firestore showing 1.0.0+1 at 50% rollout](/_astro/rollout_firestore_50pct.DhGwdE4R_Z1xUC8M.webp)

Press the update button, and the Shorebird preview output shows the following (formatted for readability):

```
11-13 15:54:06.188 1854 1943 I flutter : updater::network:
  [shorebird] Sending patch check request: PatchCheckRequest {
    app_id: "fdd48b3f-0b05-41b0-ac22-464600f739ee",
    channel: "stable",
    release_version: "1.0.0+1",
    platform: "android",
    arch: "aarch64"
  }
```

Now, change the rollout percentage in Firebase to 76%:

![Cloud Firestore showing 1.0.0+1 at 76% rollout](/_astro/rollout_firestore_76pct.BoyYmvq9_ZLxUCf.webp)

Press the update button again, and you’ll now see:

```
11-13 16:41:42.525 7036 7102 I flutter : updater::network:
  [shorebird] Sending patch check request: PatchCheckRequest {
    app_id: "fdd48b3f-0b05-41b0-ac22-464600f739ee",
    channel: "beta",
    release_version: "1.0.0+1",
    platform: "android",
    arch: "aarch64"
  }

11-13 16:41:42.666 7036 7102 I flutter : updater::updater:
  [shorebird] Patch check response: PatchCheckResponse {
    patch_available: true,
    patch: Some(Patch {
      number: 1,
      hash: "6baa53e40fe1ef0d1230c0ab04ef9dacc7fb5d19368278f2cb2b09a333fdd0c2",
      download_url: "https://cdn-dev.shorebird.cloud/api/v1/patches/fdd48b3f-0b05-41b0-ac22-464600f739ee/android/aarch64/3098/dlc.vmcode",
      hash_signature: None
    }),
    rolled_back_patch_numbers: Some([])
  }

11-13 16:41:43.279 7036 7102 I flutter : updater::updater:
  [shorebird] Patch 1 successfully downloaded. It will be launched when the app next restarts.
```

Note that the channel field in the patch check request has changed from stable to beta, and that the patch has been downloaded.

![Patched build showing group number 76 with a purple background](/_astro/rollout_group_76_purple.D6jedEVz_Z2vSeHL.webp)
