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.

example/lib/main.dart

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

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

  // All config is read from canopy.yaml (app_id, server_url, public_key)
  await CanopyUpdater.instance.initFromAsset();

  // Check and apply updates automatically
  final status = await CanopyUpdater.instance.checkForUpdate();
  if (status == UpdateStatus.updateAvailable) {
    await CanopyUpdater.instance.update();
  }

  CanopyUpdater.instance.reportBootSuccess();

  runApp(const CanopyExampleApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Canopy Example',
      theme: ThemeData(
        colorSchemeSeed: Colors.green,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  UpdateStatus? _status;
  double _progress = 0;
  String? _error;
  bool _checking = false;
  bool _updating = false;

  @override
  void initState() {
    super.initState();
    CanopyUpdater.instance.events.listen((event) {
      if (!mounted) return;
      setState(() {
        switch (event) {
          case UpdateCheckingEvent():
            _checking = true;
            _error = null;
          case UpdateAvailableEvent(:final patchNumber, :final sizeBytes):
            _checking = false;
            _status = UpdateStatus.updateAvailable;
            _showUpdateDialog(patchNumber, sizeBytes);
          case UpdateDownloadingEvent(:final progress):
            _progress = progress;
          case UpdateAppliedEvent(:final patchNumber):
            _updating = false;
            _showSnackBar('Patch #$patchNumber staged. Restart to apply.');
          case UpdateFailedEvent(:final error):
            _checking = false;
            _updating = false;
            _error = error;
          case UpdateUpToDateEvent():
            _checking = false;
            _status = UpdateStatus.upToDate;
        }
      });
    });
  }

  Future<void> _checkForUpdate() async {
    final status = await CanopyUpdater.instance.checkForUpdate();
    if (status == UpdateStatus.upToDate && mounted) {
      _showSnackBar('App is up to date!');
    }
  }

  Future<void> _applyUpdate() async {
    setState(() => _updating = true);
    try {
      await CanopyUpdater.instance.update(
        onProgress: (p) => setState(() => _progress = p),
      );
    } catch (e) {
      if (mounted) _showSnackBar('Update failed: $e');
    }
  }

  void _showUpdateDialog(int patchNumber, int sizeBytes) {
    final sizeMb = (sizeBytes / (1024 * 1024)).toStringAsFixed(1);
    showDialog<void>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Update Available'),
        content: Text('Patch #$patchNumber ($sizeMb MB)\nDownload and apply?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(ctx),
            child: const Text('Later'),
          ),
          FilledButton(
            onPressed: () {
              Navigator.pop(ctx);
              _applyUpdate();
            },
            child: const Text('Update'),
          ),
        ],
      ),
    );
  }

  void _showSnackBar(String message) {
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  @override
  Widget build(BuildContext context) {
    final currentPatch = CanopyUpdater.instance.currentPatch;

    return Scaffold(
      appBar: AppBar(title: const Text('Canopy Example')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Current patch: ${currentPatch ?? "original release"}',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            Text(
              'Channel: ${CanopyUpdater.instance.currentChannel}',
              style: Theme.of(context).textTheme.bodyMedium,
            ),
            if (_status != null)
              Text(
                'Status: ${_status!.name}',
                style: Theme.of(context).textTheme.bodySmall,
              ),
            const SizedBox(height: 24),
            if (_checking)
              const Row(
                children: [
                  SizedBox(
                    width: 16, height: 16,
                    child: CircularProgressIndicator(strokeWidth: 2),
                  ),
                  SizedBox(width: 12),
                  Text('Checking for updates...'),
                ],
              ),
            if (_updating) ...[
              Text('Downloading... ${(_progress * 100).toStringAsFixed(0)}%'),
              const SizedBox(height: 8),
              LinearProgressIndicator(value: _progress),
            ],
            if (_error != null)
              Text(
                'Error: $_error',
                style: TextStyle(color: Theme.of(context).colorScheme.error),
              ),
            const Spacer(),
            SizedBox(
              width: double.infinity,
              child: FilledButton.icon(
                onPressed: _checking || _updating ? null : _checkForUpdate,
                icon: const Icon(Icons.refresh),
                label: const Text('Check for updates'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
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