| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | import 'package:flutter/foundation.dart'; |
| 6 | |
| 7 | import 'platform_channel.dart'; |
| 8 | |
| 9 | export 'dart:typed_data' show ByteData; |
| 10 | |
| 11 | /// A message encoding/decoding mechanism. |
| 12 | /// |
| 13 | /// Both operations throw an exception, if conversion fails. Such situations |
| 14 | /// should be treated as programming errors. |
| 15 | /// |
| 16 | /// See also: |
| 17 | /// |
| 18 | /// * [BasicMessageChannel], which use [MessageCodec]s for communication |
| 19 | /// between Flutter and platform plugins. |
| 20 | abstract class MessageCodec<T> { |
| 21 | /// Encodes the specified [message] in binary. |
| 22 | /// |
| 23 | /// Returns null if the message is null. |
| 24 | ByteData? encodeMessage(T message); |
| 25 | |
| 26 | /// Decodes the specified [message] from binary. |
| 27 | /// |
| 28 | /// Returns null if the message is null. |
| 29 | T? decodeMessage(ByteData? message); |
| 30 | } |
| 31 | |
| 32 | /// A command object representing the invocation of a named method. |
| 33 | @pragma('vm:keep-name' ) |
| 34 | @immutable |
| 35 | class MethodCall { |
| 36 | /// Creates a [MethodCall] representing the invocation of [method] with the |
| 37 | /// specified [arguments]. |
| 38 | const MethodCall(this.method, [this.arguments]); |
| 39 | |
| 40 | /// The name of the method to be called. |
| 41 | final String method; |
| 42 | |
| 43 | /// The arguments for the method. |
| 44 | /// |
| 45 | /// Must be a valid value for the [MethodCodec] used. |
| 46 | /// |
| 47 | /// This property is `dynamic`, which means type-checking is skipped when accessing |
| 48 | /// this property. To minimize the risk of type errors at runtime, the value should |
| 49 | /// be cast to `Object?` when accessed. |
| 50 | final dynamic arguments; |
| 51 | |
| 52 | @override |
| 53 | String toString() => ' ${objectRuntimeType(this, 'MethodCall' )}( $method, $arguments)' ; |
| 54 | } |
| 55 | |
| 56 | /// A codec for method calls and enveloped results. |
| 57 | /// |
| 58 | /// All operations throw an exception, if conversion fails. |
| 59 | /// |
| 60 | /// See also: |
| 61 | /// |
| 62 | /// * [MethodChannel], which use [MethodCodec]s for communication |
| 63 | /// between Flutter and platform plugins. |
| 64 | /// * [EventChannel], which use [MethodCodec]s for communication |
| 65 | /// between Flutter and platform plugins. |
| 66 | abstract class MethodCodec { |
| 67 | /// Encodes the specified [methodCall] into binary. |
| 68 | ByteData encodeMethodCall(MethodCall methodCall); |
| 69 | |
| 70 | /// Decodes the specified [methodCall] from binary. |
| 71 | MethodCall decodeMethodCall(ByteData? methodCall); |
| 72 | |
| 73 | /// Decodes the specified result [envelope] from binary. |
| 74 | /// |
| 75 | /// Throws [PlatformException], if [envelope] represents an error, otherwise |
| 76 | /// returns the enveloped result. |
| 77 | /// |
| 78 | /// The type returned from [decodeEnvelope] is `dynamic` (not `Object?`), |
| 79 | /// which means *no type checking is performed on its return value*. It is |
| 80 | /// strongly recommended that the return value be immediately cast to a known |
| 81 | /// type to prevent runtime errors due to typos that the type checker could |
| 82 | /// otherwise catch. |
| 83 | dynamic decodeEnvelope(ByteData envelope); |
| 84 | |
| 85 | /// Encodes a successful [result] into a binary envelope. |
| 86 | ByteData encodeSuccessEnvelope(Object? result); |
| 87 | |
| 88 | /// Encodes an error result into a binary envelope. |
| 89 | /// |
| 90 | /// The specified error [code], human-readable error [message] and error |
| 91 | /// [details] correspond to the fields of [PlatformException]. |
| 92 | ByteData encodeErrorEnvelope({required String code, String? message, Object? details}); |
| 93 | } |
| 94 | |
| 95 | /// Thrown to indicate that a platform interaction failed in the platform |
| 96 | /// plugin. |
| 97 | /// |
| 98 | /// See also: |
| 99 | /// |
| 100 | /// * [MethodCodec], which throws a [PlatformException], if a received result |
| 101 | /// envelope represents an error. |
| 102 | /// * [MethodChannel.invokeMethod], which completes the returned future |
| 103 | /// with a [PlatformException], if invoking the platform plugin method |
| 104 | /// results in an error envelope. |
| 105 | /// * [EventChannel.receiveBroadcastStream], which emits |
| 106 | /// [PlatformException]s as error events, whenever an event received from the |
| 107 | /// platform plugin is wrapped in an error envelope. |
| 108 | class PlatformException implements Exception { |
| 109 | /// Creates a [PlatformException] with the specified error [code] and optional |
| 110 | /// [message], and with the optional error [details] which must be a valid |
| 111 | /// value for the [MethodCodec] involved in the interaction. |
| 112 | PlatformException({required this.code, this.message, this.details, this.stacktrace}); |
| 113 | |
| 114 | /// An error code. |
| 115 | final String code; |
| 116 | |
| 117 | /// A human-readable error message, possibly null. |
| 118 | final String? message; |
| 119 | |
| 120 | /// Error details, possibly null. |
| 121 | /// |
| 122 | /// This property is `dynamic`, which means type-checking is skipped when accessing |
| 123 | /// this property. To minimize the risk of type errors at runtime, the value should |
| 124 | /// be cast to `Object?` when accessed. |
| 125 | final dynamic details; |
| 126 | |
| 127 | /// Native stacktrace for the error, possibly null. |
| 128 | /// |
| 129 | /// This contains the native platform stack trace, not the Dart stack trace. |
| 130 | /// |
| 131 | /// The stack trace for Dart exceptions can be obtained using try-catch blocks, for example: |
| 132 | /// |
| 133 | /// ```dart |
| 134 | /// try { |
| 135 | /// // ... |
| 136 | /// } catch (e, stacktrace) { |
| 137 | /// print(stacktrace); |
| 138 | /// } |
| 139 | /// ``` |
| 140 | /// |
| 141 | /// On Android this field is populated when a `RuntimeException` or a subclass of it is thrown in the method call handler, |
| 142 | /// as shown in the following example: |
| 143 | /// |
| 144 | /// ```kotlin |
| 145 | /// import androidx.annotation.NonNull |
| 146 | /// import io.flutter.embedding.android.FlutterActivity |
| 147 | /// import io.flutter.embedding.engine.FlutterEngine |
| 148 | /// import io.flutter.plugin.common.MethodChannel |
| 149 | /// |
| 150 | /// class MainActivity: FlutterActivity() { |
| 151 | /// private val CHANNEL = "channel_name" |
| 152 | /// |
| 153 | /// override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { |
| 154 | /// super.configureFlutterEngine(flutterEngine) |
| 155 | /// MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { |
| 156 | /// call, result -> throw RuntimeException("Oh no") |
| 157 | /// } |
| 158 | /// } |
| 159 | /// } |
| 160 | /// ``` |
| 161 | /// |
| 162 | /// It is also populated on Android if the method channel result is not serializable. |
| 163 | /// If the result is not serializable, an exception gets thrown during the serialization process. |
| 164 | /// This can be seen in the following example: |
| 165 | /// |
| 166 | /// ```kotlin |
| 167 | /// import androidx.annotation.NonNull |
| 168 | /// import io.flutter.embedding.android.FlutterActivity |
| 169 | /// import io.flutter.embedding.engine.FlutterEngine |
| 170 | /// import io.flutter.plugin.common.MethodChannel |
| 171 | /// |
| 172 | /// class MainActivity: FlutterActivity() { |
| 173 | /// private val CHANNEL = "channel_name" |
| 174 | /// |
| 175 | /// override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { |
| 176 | /// super.configureFlutterEngine(flutterEngine) |
| 177 | /// MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { |
| 178 | /// call, result -> result.success(Object()) |
| 179 | /// } |
| 180 | /// } |
| 181 | /// } |
| 182 | /// ``` |
| 183 | /// |
| 184 | /// In the cases described above, the content of [stacktrace] will be the unprocessed output of calling `toString()` on the exception. |
| 185 | /// |
| 186 | /// MacOS, iOS, Linux and Windows don't support querying the native stacktrace. |
| 187 | /// |
| 188 | /// On custom Flutter embedders this value will be null on platforms that don't support querying the call stack. |
| 189 | final String? stacktrace; |
| 190 | |
| 191 | @override |
| 192 | String toString() => 'PlatformException( $code, $message, $details, $stacktrace)' ; |
| 193 | } |
| 194 | |
| 195 | /// Thrown to indicate that a platform interaction failed to find a handling |
| 196 | /// plugin. |
| 197 | /// |
| 198 | /// See also: |
| 199 | /// |
| 200 | /// * [MethodChannel.invokeMethod], which completes the returned future |
| 201 | /// with a [MissingPluginException], if no plugin handler for the method call |
| 202 | /// was found. |
| 203 | /// * [OptionalMethodChannel.invokeMethod], which completes the returned future |
| 204 | /// with null, if no plugin handler for the method call was found. |
| 205 | class MissingPluginException implements Exception { |
| 206 | /// Creates a [MissingPluginException] with an optional human-readable |
| 207 | /// error message. |
| 208 | MissingPluginException([this.message]); |
| 209 | |
| 210 | /// A human-readable error message, possibly null. |
| 211 | final String? message; |
| 212 | |
| 213 | @override |
| 214 | String toString() => 'MissingPluginException( $message)' ; |
| 215 | } |
| 216 | |