---
title: "Plugins and Native Code"
description: "How Flutter plugins, platform channels, Pigeon and FFI connect Dart to Kotlin, Swift and C code, what MissingPluginException means, and which side of a plugin a Code Push patch can update"
---

Most Flutter apps call native code somewhere: the camera, payments, push notifications, secure storage. That code comes from plugins or from your own `android/` and `ios/` folders, and it is built differently from your Dart code. This page explains how the two sides connect, and why the line between them is also the line between what a Shorebird patch can and can’t change.

## Packages, plugins and FFI packages

Flutter’s docs describe three [kinds of package](https://docs.flutter.dev/packages-and-plugins/developing-packages#types):

* **Dart packages** contain only Dart code, such as `path`, `http` or `flutter_bloc`. Everything in them is compiled into your app’s Dart code.
* **Plugin packages** combine a Dart API with platform-specific implementations: Kotlin or Java for Android, Swift or Objective-C for iOS and macOS, C++ for Windows and Linux. `url_launcher` and `camera` are plugins.
* **FFI packages** call C-compatible native code directly through `dart:ffi`. A build hook (`hook/build.dart`) compiles the native code and bundles it with your app.

A plugin’s `pubspec.yaml` declares the platforms it implements, and that is the quickest way to tell whether a package ships native code:

```
flutter:
  plugin:
    platforms:
      android:
        package: com.example.hello
        pluginClass: HelloPlugin
      ios:
        pluginClass: HelloPlugin
```

An Android, iOS, macOS, Windows or Linux entry with a `pluginClass`, or with `ffiPlugin: true`, ships native code for that platform (web entries are Dart). An FFI package has no `platforms:` entry; its `hook/build.dart` file is the sign that it builds native code. Large plugins are often [federated](https://docs.flutter.dev/packages-and-plugins/developing-packages#federated-plugins): the package you depend on (`url_launcher`) pulls in one package per platform (`url_launcher_android`, `url_launcher_ios`), so check those too.

## How Dart calls native code

### Platform channels

A [platform channel](https://docs.flutter.dev/platform-integration/platform-channels) sends messages between your Dart code and the host app. The Dart side calls a method by name, and a handler registered in Kotlin or Swift answers:

```
const channel = MethodChannel('com.example.app/battery');

Future<int> batteryLevel() async {
  final level = await channel.invokeMethod<int>('getBatteryLevel');
  return level ?? -1;
}
```

Messages are serialized with `StandardMessageCodec`, which carries JSON-like values: `null`, booleans, numbers, strings, typed byte lists, lists and maps. Calls are asynchronous, and the native handler runs on the platform’s main thread unless it uses a task queue. Each plugin registers its handlers when the engine starts, through the generated plugin registrant.

Channels aren’t type safe: both sides have to agree on method names and argument types. Flutter recommends [Pigeon](https://pub.dev/packages/pigeon), which generates matching Dart and native code from one Dart definition, for new code.

When the Dart side calls a method that has no handler on the native side, or the handler answers “not implemented”, `invokeMethod` throws a [`MissingPluginException`](https://api.flutter.dev/flutter/services/MissingPluginException-class.html):

```
MissingPluginException(No implementation found for method getBatteryLevel on channel com.example.app/battery)
```

In development, that usually means the app needs a full restart after adding a plugin, because hot reload and hot restart don’t rebuild native code. In a release, it means the Dart code expects native code that isn’t in the installed app, which is exactly the situation a patch can create (see below).

### FFI

[`dart:ffi`](https://dart.dev/interop/c-interop) calls C functions directly, without serializing messages, and synchronously. That makes it the faster option for heavy work such as image processing, cryptography or a database engine. The C code is compiled for each target platform and shipped inside the app as a native library. Generators such as [ffigen](https://pub.dev/packages/ffigen) and [jnigen](https://pub.dev/packages/jnigen) write the Dart bindings for C, Objective-C, Swift, Java and Kotlin APIs.

## What a Code Push patch can change

A Shorebird patch replaces your app’s compiled Dart code and nothing else (see [How Flutter builds your app](/flutter-concepts/how-flutter-builds-your-app/)). For a plugin, that splits along the channel:

| Change                                                    | Patch? | Why                                             |
| --------------------------------------------------------- | :----: | ----------------------------------------------- |
| Your Dart code that calls a plugin                        |   Yes  | It’s Dart code                                  |
| A Dart-only package added, removed or upgraded            |   Yes  | It compiles into your Dart code                 |
| The Dart half of a plugin, with its native half unchanged |   Yes  | Only the Dart code changed                      |
| A plugin with native code added to the app                |   No   | Its native code isn’t in the installed app      |
| A plugin upgraded in a way that changes its native code   |   No   | The installed app still has the old native code |
| Your own Kotlin, Java, Swift or Objective-C code          |   No   | Native code ships only with a store release     |
| C code built for FFI                                      |   No   | It’s a native library inside the installed app  |

If a patch needs native code the installed app doesn’t have, the patch still installs, but the calls fail on users’ devices: a platform channel call throws `MissingPluginException`, FFI code can’t find its native library, and a plugin whose Dart and native halves no longer match can crash the app. Ship native changes in a new release with `shorebird release`, then patch on top of it.

`shorebird patch` helps you catch these changes. It compares the new build with the release and warns about native changes it can detect. On Android it compares the app’s `.dex` files (Java and Kotlin code), so a change to a C or C++ library alone doesn’t produce a warning. On iOS and macOS it compares `Podfile.lock` once the app’s executable differs, so an app whose plugins all come through Swift Package Manager, with no `Podfile.lock`, gets no native-changes warning. The details, and what to do about a warning you didn’t expect, are in [`Your app contains native changes` warning when creating a patch](/code-push/troubleshooting/#your-app-contains-native-changes-warning-when-creating-a-patch-even-though-you-havent-changed-swiftobjective-ckotlinjava-code).

### Keeping Dart and native code compatible

Every patch you ship runs against the native code of the release it patches. Some habits make that easy:

* **Commit `pubspec.lock`**, so a plugin upgrade, including a transitive one, shows up in review rather than in a native-changes warning.
* **Add a plugin in a release**, even if you only start using it later. The Dart code that calls it can then arrive in a patch.
* **Treat your own channels as an API.** When a patch’s Dart code calls a method that only newer native code implements, catch `MissingPluginException` and fall back, so the same Dart change can also ship as a patch to older releases that don’t have the method yet.

The FAQ’s [Can new packages or plugins be added in a patch?](/code-push/faq/#can-new-packages-or-plugins-be-added-in-a-patch) has more on checking a package for native code.
