In Flutter, how do PlatformException and MissingPluginException from MethodChannel.invokeMethod differ, and what usually causes each one?
answer
- error reply vs empty reply
- code, message, details, stacktrace
- notImplemented sends null
- new plugin needs a full rebuild
- OptionalMethodChannel returns null
basics
~10 sPlatformException means a host handler ran and replied with an error (code, message, details). MissingPluginException means the reply was empty: no handler on that channel name, or the handler answered notImplemented.
solid answer
~40 s`PlatformException` is the Dart face of a host **error envelope**: the handler called `result.error(code, message, details)`, or on Android threw an exception the channel caught and turned into code `'error'`. Its fields are `code`, `message`, `details` and `stacktrace`, so callers switch on `code`. `MissingPluginException` is thrown when the reply is **empty**: nothing is registered under that exact channel name in this engine, the name is misspelt, the handler called `notImplemented()`, or a plugin added to `pubspec.yaml` was picked up by hot reload or hot restart instead of a full rebuild, so its native half is not compiled in. `OptionalMethodChannel` returns `null` instead of throwing in that case. Handle `PlatformException` as a runtime failure; treat `MissingPluginException` almost always as a wiring bug.
go deeper
Recall that PlatformException carries a code and message from host code, while MissingPluginException means nothing answered the call.
Explain the empty reply versus error envelope distinction, the notImplemented path, and why adding a plugin needs a full rebuild rather than hot restart.
Show how you diagnose a MissingPluginException that appears only in a secondary engine, a background isolate or tests, and how you design error codes a caller can act on.
Decide how a team documents and versions channel error codes so Dart callers can branch on them reliably across app and plugin releases.
## Two different replies When a `MethodChannel` call comes back, the framework looks at the raw reply bytes before decoding anything: - **An empty (null) reply** means nobody answered. `MethodChannel.invokeMethod` then throws `MissingPluginException('No implementation found for method <m> on channel <name>')`. - **A non-empty reply** is decoded as an **envelope**. A success envelope completes the `Future` with the value; an **error envelope** makes the codec throw `PlatformException`. So the two exceptions answer different questions: *did a handler exist?* versus *did the handler succeed?* | | `PlatformException` | `MissingPluginException` | |---|---|---| | Reply on the wire | error envelope | empty reply | | Fields | `code` (required), `message`, `details`, `stacktrace` | `message` only | | Means | handler ran and reported failure | no handler answered | | Typical fix | handle by `code`, show a fallback | fix registration, name or build | ## What produces a PlatformException 1. The host handler calls `result.error("NO_SENSOR", "Step counter unavailable", null)` (Android) or answers with a `FlutterError` (iOS). 2. On Android, the handler throws a `RuntimeException`; the channel catches it, logs it, and replies with an error envelope whose code is `"error"` and whose message is the exception's message. 3. `EventChannel` streams deliver host-side `error(...)` events as `PlatformException` **error events** on the stream rather than as thrown exceptions. `details` can carry any codec-supported value, which is the place to put structured data such as a retry hint. ## What produces a MissingPluginException - **Name mismatch.** `'com.example.app/power'` on one side, `'com.example.app/Power'` on the other. - **Handler never registered in this engine.** Registration happens when the host code runs; if it lives in an activity that never executed, or the call runs in a second `FlutterEngine` that never registered plugins, nothing answers. - **`notImplemented()` / `FlutterMethodNotImplemented`.** The handler exists but did not recognise the method name; this also sends an empty reply. - **A plugin added without rebuilding.** Hot reload and hot restart replace Dart code only. A newly added plugin's Kotlin or Swift code is not in the running binary until you stop and run the app again. - **A platform the plugin does not support**, such as a desktop or web target with no implementation registered. - **A test without a mock.** In widget tests there is no host at all, so every call returns empty unless a mock handler is installed. A Dart handler set with `setMethodCallHandler` can produce the same outcome in the other direction: if it throws `MissingPluginException`, the framework sends the empty reply the host interprets as not implemented. ## Handling them in code ```dart Future<int?> todaySteps(MethodChannel ch) async { try { return await ch.invokeMethod<int>('todaySteps'); } on PlatformException catch (e) { if (e.code == 'NO_SENSOR') return null; // expected on some devices rethrow; } on MissingPluginException { // Wiring bug or unsupported platform: log it loudly. rethrow; } } ``` - Switch on **`code`**, not on `message`; messages are for humans and can change. - Catching `MissingPluginException` silently hides real bugs. The deliberate way to make a call optional is **`OptionalMethodChannel`**, whose `invokeMethod` returns `null` when no handler answers. - Neither exception covers a handler that never replies at all; that `Future` simply stays pending, so add a timeout only where it is genuinely acceptable.
- You added a plugin, hot restarted, and now see MissingPluginException. Why, and what fixes it?Hot reload and hot restart only swap Dart code in the running app; the plugin's native half and its generated registration are compiled into the host binary. Stop the app and run it again (a full rebuild) so the Kotlin or Swift code is built in and registered with the engine.
- When is OptionalMethodChannel the right tool?When a call is genuinely optional on some platforms or hosts, for example a hint the Android side supports but iOS does not implement. `OptionalMethodChannel.invokeMethod` returns `null` on an empty reply instead of throwing, which documents the intent better than catching `MissingPluginException` everywhere.
- What arrives in Dart when an Android handler throws an IllegalStateException?The Android `MethodChannel` catches the `RuntimeException`, logs it and replies with an error envelope whose code is `"error"`, whose message is the exception's message and which carries the stack trace. Dart receives a `PlatformException` with `code == 'error'`, not a crash of the app.
saying these in an interview costs you the question
- Says MissingPluginException means the host code threw an error
- Believes hot reload picks up a newly added plugin's native code
- Switches on PlatformException.message instead of code
- Catches MissingPluginException everywhere to make calls optional
- Thinks an uncaught host exception on Android always crashes the app