---
title: "How devices get patches"
description: "When a device checks for, downloads and runs a patch, and what the shorebird_code_push package returns and throws when your code checks."
---

You published a patch and want to know when a device will run it, or you’re writing code that checks for patches yourself. This page covers what the Shorebird updater in your app does on a device. If a patch isn’t arriving, read the device’s logs first: see [Read device logs](/code-push/device-logs/).

## From publish to running

1. The app’s process starts (a cold start). It runs the patch already installed on the device, or the release’s own code if there isn’t one.
2. Once the app is running, the updater checks Shorebird for the newest patch on the `stable` track for this release, on a background thread, so launch isn’t delayed.
3. If there’s a patch the device doesn’t have, the updater downloads it, checks it, and installs it on disk.
4. The next time the process starts, the app runs the new patch.

So a device runs a new patch on the launch after the one that downloaded it. A patch is never swapped into an app that is already running.

## When the automatic check runs

The updater checks once each time the app’s process starts. It doesn’t check again while the process is alive, and it doesn’t check when the app comes back from the background. Mobile operating systems end background apps often, so most users start a fresh process regularly. An app whose process stays alive, for example because of a foreground service, checks only when that process restarts; see the foreground service case in [Patch not showing up](/code-push/troubleshooting/#patch-not-showing-up).

The automatic check always asks for the `stable` track, except in a build installed with `shorebird preview --track`, which checks the previewed track. To use another track, see [Tracks](/code-push/tracks/#subscribing-a-device-to-a-track).

The check can also tell the device that a patch was rolled back. The device removes that patch, and the next launch runs the previous patch or the release. See [Roll back a patch](/code-push/rollback/#what-happens-when-a-patch-is-rolled-back).

To check at other times, such as when the app resumes or when a push notification arrives, call `package:shorebird_code_push` from your code, as described [below](#check-for-patches-from-your-code).

## Turning off the automatic check

With [`auto_update: false`](/code-push/shorebird-yaml/#auto_update) in `shorebird.yaml`, the app doesn’t check on launch. The device log shows:

```
Shorebird auto_update disabled, not checking for updates.
```

The app still runs the patch already installed on the device. It doesn’t download new patches or learn about rollbacks until your code calls `checkForUpdate()` or `update()`. Read the caution in [Update strategies](/code-push/update-strategies/#manual) before you turn it off.

## Check for patches from your code

Add [`package:shorebird_code_push`](https://pub.dev/packages/shorebird_code_push) to your app with `flutter pub add shorebird_code_push`. This example checks the `stable` track once the first frame is on its way, and downloads a new patch if there is one:

```
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:shorebird_code_push/shorebird_code_push.dart';

final _updater = ShorebirdUpdater();

void main() {
  runApp(const MyApp());
  // Don't await this. It makes network requests, and awaiting it before
  // runApp would hold the app on its splash screen until they finish.
  unawaited(_checkForPatch());
}

Future<void> _checkForPatch() async {
  final status = await _updater.checkForUpdate();
  if (status != UpdateStatus.outdated) return;
  try {
    await _updater.update();
    // The patch runs the next time the app starts.
  } on UpdateException catch (error) {
    debugPrint('Shorebird update failed: ${error.message} (${error.reason})');
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: Scaffold(body: Center(child: Text('Hi'))));
  }
}
```

To check a track other than `stable`, pass the same `track` to both calls, for example `checkForUpdate(track: UpdateTrack.beta)` and `update(track: UpdateTrack.beta)`. See [Tracks](/code-push/tracks/#subscribing-a-device-to-a-track). To run a downloaded patch without waiting for the next launch, see [Restarting after an update](/code-push/update-strategies/#restarting-after-an-update).

The rest of this section describes `shorebird_code_push` 2.0.6 and newer.

### What each call returns

| Call                      | Returns                                                                                                                                                                                                                                   |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `isAvailable`             | `false` when the build doesn’t contain the updater: a debug or profile build, `flutter run`, a build made with `flutter build` rather than `shorebird release` or `shorebird preview`, or the web. Every other call then returns at once. |
| `checkForUpdate({track})` | An `UpdateStatus` (below). A check that fails, for example because the device is offline, doesn’t throw: it returns as if there were no new patch.                                                                                        |
| `update({track})`         | Completes normally when it installs a patch, when there’s no newer patch, and when another update is already running. Throws `UpdateException` when the update fails (below).                                                             |
| `readCurrentPatch()`      | The patch this launch is running, or `null` for the release’s own code. If the updater can’t read its state, it logs the error and returns `null`.                                                                                        |
| `readNextPatch()`         | The patch the next launch will run, or `null` for the release’s own code. It’s the same as `readCurrentPatch()` until a new patch downloads or the running patch is rolled back.                                                          |

`update()` completing normally doesn’t mean a patch was installed. When the automatic check is already downloading a patch, `update()` returns at once and the download carries on. To find out whether a patch is waiting for the next launch, compare `readNextPatch()` with `readCurrentPatch()`.

### Update statuses

| `UpdateStatus`    | Meaning                                                                                                                                                                                                                    |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `upToDate`        | There’s no newer patch on the track for this device to download, and nothing is waiting for a restart.                                                                                                                     |
| `outdated`        | The track has a patch this device hasn’t downloaded. Call `update()` to download it.                                                                                                                                       |
| `restartRequired` | The patch the next launch will run differs from the one running now: a new patch has downloaded, or the running patch was rolled back. Rollbacks report `restartRequired` on releases built with Flutter 3.41.9 and newer. |
| `unavailable`     | The build doesn’t contain the updater (see `isAvailable`).                                                                                                                                                                 |

A patch this device has marked bad (see [Failed and rolled-back patches](#failed-and-rolled-back-patches)) isn’t reported as `outdated`.

### When `update()` throws

`update()` throws an `UpdateException`. Its `message` says what went wrong, and its `reason` is one of:

* `installFailed`: the newest patch on the track is one this device has marked bad, so the device won’t install it. The message is `Update available but previously failed to install. Not installing.`
* `unknown`: any other failure, such as a patch check or download that couldn’t reach Shorebird, a refused patch check (for example `Request failed with status: 403` when your organization [reached its patch install limit](/code-push/troubleshooting/#your-organization-reached-its-patch-install-limit)), or a downloaded patch that failed its checks. The device tries again on the next check, except for a patch it has marked bad.

`UpdateFailureReason` also has `noUpdate` and `downloadFailed`, but 2.0.6 and newer don’t throw them.

Older versions

* `shorebird_code_push` before 2.0.6 throws `UpdateException` with `noUpdate` when there’s no new patch, and throws when another update is already running.
* On releases built with Flutter before 3.41.7, `update()` throws `unknown` with the message `Update already in progress` when it overlaps the automatic check, whatever the package version.

## Failed and rolled-back patches

* **The download fails**, for example because the device went offline: nothing changes on the device, and it tries again on the next check.
* **The patch downloads but fails its checks**, for example because the release’s build on the device isn’t the one Shorebird has (see [Patches can’t install because the app isn’t the build Shorebird released](/code-push/troubleshooting/#patches-cant-install-because-the-app-isnt-the-build-shorebird-released)). On releases built with Flutter 3.41.9 and newer, the device marks the patch bad and never tries it again. On older Flutter versions, it downloads the patch again on every check.
* **The patch fails to load**, including when the app’s process ends while the engine is still loading it: the device marks the patch bad and goes back to the previous patch or the release. Which Flutter versions do this, and what the app runs on that launch, is in [Patch integrity and automatic rollback](/code-push/rollback/#patch-integrity-and-automatic-rollback).
* **You roll the patch back**: devices remove it on their next check and run the previous patch, or the release, from the launch after that. See [Roll back a patch](/code-push/rollback/).

A patch marked bad is bad only on that device, and only that patch number. Fix the problem and publish a new patch; devices install it as usual. Roll the bad patch back so devices that haven’t tried it yet don’t download it.
