canopy 0.1.0 copy "canopy: ^0.1.0" to clipboard
canopy: ^0.1.0 copied to clipboard

PlatformAndroid

Open-source code push for Flutter. Push Dart code updates to your apps instantly — without app store reviews. Self-hosted, no engine fork.

Canopy #

Open-source code push for Flutter. Push Dart code and asset updates to your apps instantly — without app store reviews.

No engine fork required. Works with the standard Flutter engine.

Features #

  • OTA Updates — Push code changes to your Flutter apps in seconds
  • Android Support — Patches applied via snapshot replacement, no engine fork needed
  • Crash Protection — Automatic rollback if a patch causes boot failures
  • Ed25519 Signing — Verify patch integrity with cryptographic signatures
  • Staged Rollouts — Roll out patches to a percentage of users
  • Self-Hosted — Run the Canopy server on your own infrastructure

Getting Started #

1. Install the SDK #

dependencies:
  canopy: ^0.1.0

2. Android Setup #

a) Create a custom Application class in android/app/src/main/kotlin/.../CanopyApplication.kt:

package com.example.yourapp

import android.app.Application
import android.content.Context
import android.util.Log
import dev.canopy.sdk.CanopyFlutterLoader
import dev.canopy.sdk.SnapshotManager
import io.flutter.FlutterInjector

class CanopyApplication : Application() {
    override fun attachBaseContext(base: Context) {
        super.attachBaseContext(base)
        try {
            val snapshotManager = SnapshotManager.getInstance(base)
            val snapshotPath = snapshotManager.getActiveSnapshotPath()
            FlutterInjector.setInstance(
                FlutterInjector.Builder()
                    .setFlutterLoader(CanopyFlutterLoader(snapshotPath))
                    .build()
            )
        } catch (e: Exception) {
            Log.e("CanopyApp", "Init failed: ${e.message}")
        }
    }
}

b) Register it in android/app/src/main/AndroidManifest.xml:

<application
    android:name=".CanopyApplication"
    android:usesCleartextTraffic="true"
    ...>

c) Update MainActivity.kt:

import dev.canopy.sdk.CanopyFlutterActivity

class MainActivity : CanopyFlutterActivity()

3. Initialize Canopy #

Add canopy.yaml to your Flutter assets in pubspec.yaml:

flutter:
  assets:
    - canopy.yaml

Then initialize in main.dart — all config is read from canopy.yaml automatically:

import 'package:canopy/canopy.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Reads app_id, server_url, public_key from canopy.yaml
  await CanopyUpdater.instance.initFromAsset();

  // Check and apply updates
  final status = await CanopyUpdater.instance.checkForUpdate();
  if (status == UpdateStatus.updateAvailable) {
    await CanopyUpdater.instance.update();
    // Patch will be applied on next cold restart
  }

  // Report successful boot (enables crash protection)
  CanopyUpdater.instance.reportBootSuccess();

  runApp(const MyApp());
}

You can override the server URL for local development:

await CanopyUpdater.instance.initFromAsset(
  serverUrl: 'http://192.168.1.4:8080',
);

4. Push Updates #

# Create your first release
canopy release --platform android

# Make code changes, then push a patch
canopy patch --platform android --release 1.0.0

5. Listen to Events (optional) #

CanopyUpdater.instance.events.listen((event) {
  switch (event) {
    case UpdateAvailableEvent(:final patchNumber, :final sizeBytes):
      print('Patch #$patchNumber available ($sizeBytes bytes)');
    case UpdateAppliedEvent(:final patchNumber):
      print('Patch #$patchNumber staged — restart to activate');
    case UpdateFailedEvent(:final error):
      print('Update failed: $error');
    default:
      break;
  }
});

How It Works #

  1. You build and upload a release with the Canopy CLI (canopy release)
  2. You make code changes and push a patch (canopy patch)
  3. The SDK checks for updates on app launch
  4. If a patch is available, it downloads and verifies it (SHA-256 + Ed25519)
  5. On next cold restart, the patched libapp.so is loaded instead of the bundled one
  6. If the patch causes crashes, automatic rollback kicks in after 2 failed boots

Crash Protection #

Canopy tracks boot success. If the app crashes twice in a row after applying a patch, the SDK automatically rolls back to the original bundled version. Call reportBootSuccess() after your app initializes successfully to reset the crash counter.

API Reference #

CanopyUpdater #

Method Description
initFromAsset({serverUrl?, channel?}) Initialize from bundled canopy.yaml
init(serverUrl, appId, ...) Initialize with explicit parameters
checkForUpdate({channel?}) Check if a patch is available
update({channel?, onProgress?}) Download and stage a patch
reportBootSuccess() Mark current boot as successful
currentPatch Current patch number (null = original)
currentChannel Current update channel
events Stream of update events
dispose() Clean up resources

UpdateStatus #

Value Description
upToDate No update available
updateAvailable A new patch is ready to download
error Failed to check for updates

Requirements #

  • Flutter >= 3.19.0
  • Dart >= 3.0.0
  • Android: minSdk 24 (Android 7.0+)

License #

Apache License 2.0

0
likes
130
points
29
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Open-source code push for Flutter. Push Dart code updates to your apps instantly — without app store reviews. Self-hosted, no engine fork.

Repository (GitLab)
View/report issues

Topics

#code-push #ota-update #hot-update #flutter-plugin

License

unknown (license)

Dependencies

crypto, ffi, flutter, http, package_info_plus, path_provider, shared_preferences, web, yaml

More

Packages that depend on canopy

Packages that implement canopy