本文内容较长,涵盖:网络请求、序列化、平台通道介绍。
一、网络请求
1、发起网络请求
Flutter中的网络请求是一个常见的需求,它用于从服务器获取数据或向服务器发送数据。Flutter社区提供了多种网络请求库,其中比较流行的有dio和http。以下是关于Flutter网络请求的配置、使用、原理及功能代码的详细介绍。
1、配置
-
安装Flutter SDK
- Flutter是一个跨平台的移动开发框架,需要首先安装Flutter SDK。
-
创建Flutter项目
- 使用Flutter CLI工具创建一个新的Flutter项目。
-
配置Info.plist(iOS特有)
- 在iOS项目中,需要在Info.plist文件中配置网络请求的权限。例如,添加
NSAppTransportSecurity设置以允许任意网络请求(生产环境中建议使用HTTPS)。
- 在iOS项目中,需要在Info.plist文件中配置网络请求的权限。例如,添加
-
添加依赖
- 对于第三方网络请求库(如dio或http),需要在
pubspec.yaml文件中添加相应的依赖,并运行flutter pub get命令来安装依赖。
- 对于第三方网络请求库(如dio或http),需要在
-
证书配置
- Android
Android 应用程序必须在 Android manifest (
AndroidManifest.xml) 中设置<manifest xmlns:android...> ... <uses-permission android:name="android.permission.INTERNET" /> <application ... </manifest>- macOS
macOS 应用程序必须在相关
*.entitlements的文件中允许网络访问<key>com.apple.security.network.client</key> <true/>
2、使用
-
引入库
- 根据所使用的网络请求库,在Dart文件中引入相应的库。例如,使用dio时需要引入
package:dio/dio.dart。
- 根据所使用的网络请求库,在Dart文件中引入相应的库。例如,使用dio时需要引入
-
创建实例
- 创建网络请求库的实例。例如,对于dio,可以使用
var dio = Dio();来创建实例。
- 创建网络请求库的实例。例如,对于dio,可以使用
-
构建请求
- 构建请求的URL和参数。对于GET请求,通常将参数添加到URL的查询字符串中;对于POST请求,可以将参数作为请求体发送。
-
发送请求
- 使用实例的相应方法(如
get、post等)发送请求。这些方法是异步的,因此需要使用async和await关键字来处理。
- 使用实例的相应方法(如
-
处理响应
- 请求成功后,处理服务器返回的响应。通常需要将响应体从JSON格式解码为Dart对象。
-
错误处理
- 使用
try-catch语句来捕获和处理网络请求中可能发生的错误。
- 使用
3、原理
Flutter中的网络请求是通过Dart I/O完成的,并不会使用原生平台的网络请求封装。当Dart的HTTP请求到达Dart底层的Socket对象时,会根据运行平台(如Android、iOS或Web)选择具体的Socket实现方式。在Android和iOS上,网络请求是通过Dart VM发送的;在Web平台上,网络请求是通过浏览器发送的。
4、功能代码示例
以下是一个使用dio库进行GET请求的示例代码:
import 'package:dio/dio.dart';
Future<void> fetchData() async {
try {
// 创建dio实例
var dio = Dio();
// 发起GET请求
Response response = await dio.get('https://api.example.com/data');
// 打印响应数据
print(response.data);
} catch (error) {
// 处理错误
print(error);
}
}
对于POST请求,可以使用类似的代码,但需要将请求方法改为post,并设置请求体:
由于绝大多数公司都只用 Post 拉取、更新、删除 服务器数据,这里不介绍 put 、delete 等操作。
import 'package:dio/dio.dart';
Future<void> postData() async {
try {
var dio = Dio();
// 构造请求体
var formData = FormData.fromMap({
"key": "value"
});
// 发起POST请求
Response response = await dio.post('https://api.example.com/submit', data: formData);
// 打印响应数据
print(response.data);
} catch (error) {
// 处理错误
print(error);
}
}
Flutter中的网络请求配置相对简单,但需要确保项目已正确安装Flutter SDK,并根据需要配置Info.plist文件(iOS特有)。在使用过程中,可以选择使用Flutter社区提供的第三方网络请求库(如dio或http)来简化网络请求的操作。这些库提供了丰富的功能和简洁的API,使得开发者可以更容易地发送和接收数据。同时,需要注意网络请求的错误处理和响应数据的解码
2、需认证的网络请求
在Flutter中构建需要认证的网络请求时,你通常需要在HTTP请求中包含认证信息,如API密钥、用户名和密码、令牌(如JWT)等。这些信息可以通过HTTP头(headers)发送给服务器。
而最常见的方法或许就是使用 Authorization HTTP header 了
以下是一个使用http包在Flutter中发起需要认证的网络请求的示例。在这个例子中,我们假设服务器要求一个名为Authorization的HTTP头,其值为一个Bearer令牌。
首先,确保你的pubspec.yaml文件中包含了http包的依赖:
dependencies:
flutter:
sdk: flutter
http: ^x.y.z # 替换为最新版本号
然后,在你的Dart文件中,你可以这样编写代码:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Authenticated Network Request Demo'),
),
body: Center(
child: FutureBuilder<Map<String, dynamic>>(
future: fetchAuthenticatedData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
// 假设服务器返回的是JSON数据,我们将其解码为Map
Map<String, dynamic> data = snapshot.data ?? {};
return Text('Data: ${jsonEncode(data)}');
}
},
),
),
),
);
}
}
Future<Map<String, dynamic>> fetchAuthenticatedData() async {
String url = 'https://api.example.com/protected/data';
String token = 'your_bearer_token_here'; // 替换为你的实际令牌
// 创建HTTP头,包含认证信息
Map<String, String> headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json', // 根据需要设置Content-Type
};
// 发起GET请求
final response = await http.get(Uri.parse(url), headers: headers);
// 检查响应状态码
if (response.statusCode == 200) {
// 如果请求成功,解析响应体为JSON
return jsonDecode(response.body) as Map<String, dynamic>;
} else {
// 如果请求失败,抛出一个错误
throw Exception('Failed to fetch authenticated data: ${response.statusCode}');
}
}
在这个例子中,fetchAuthenticatedData函数是一个异步函数,它使用await来等待HTTP请求的完成。我们在请求中包含了Authorization头,其值为一个Bearer令牌。服务器将使用这个令牌来验证请求的合法性。
请注意,你需要将your_bearer_token_here替换为你的实际令牌。在实际应用中,你可能需要从某个安全的地方(如用户的密钥库、环境变量或加密存储)中获取这个令牌。
此外,如果服务器返回的是JSON数据,我们使用jsonDecode函数将其解码为Dart对象(在这个例子中是Map<String, dynamic>)。如果服务器返回的是其他格式的数据,你需要相应地调整解码逻辑
3、WebSockets 通信
除了普通的 HTTP 请求,你还可以通过 WebSockets 来连接服务器, WebSockets 可以以非轮询的方式与服务器进行双向通信。
Flutter 中的 WebSocket 通信是一种全双工的通信方式,允许客户端和服务器之间建立持久的连接,并通过这个连接双向传输数据。以下是关于 Flutter 中 WebSocket 通信的相关流程、功能代码和原理的概述。
WebSocket 通信流程
- 建立连接:客户端(Flutter 应用)通过 WebSocket 协议向服务器发送一个连接请求。服务器接受请求后,双方建立持久的连接。
- 数据传输:一旦连接建立,客户端和服务器可以通过这个连接双向传输数据。这意味着服务器可以主动向客户端发送数据,而客户端也可以随时向服务器发送数据。
- 处理数据:客户端和服务器都需要处理接收到的数据。在 Flutter 应用中,这通常涉及到解析数据并更新 UI。
- 关闭连接:当不再需要通信时,客户端或服务器可以主动关闭连接。这通常涉及到发送一个关闭帧,并等待对方确认。
功能代码
在 Flutter 中,你可以使用 dart:web_socket 库来处理 WebSocket 通信。以下是一个简单的示例,展示了如何在 Flutter 应用中建立 WebSocket 连接、发送和接收数据以及关闭连接。
import 'dart:web_socket' as WebSocket;
import 'dart:convert';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('WebSocket Demo'),
),
body: WebSocketDemo(),
),
);
}
}
class WebSocketDemo extends StatefulWidget {
@override
_WebSocketDemoState createState() => _WebSocketDemoState();
}
class _WebSocketDemoState extends State<WebSocketDemo> {
WebSocket.WebSocket? _socket;
TextEditingController _controller = TextEditingController();
String _response = '';
@override
void initState() {
super.initState();
connectToWebSocket();
}
void connectToWebSocket() {
String url = 'wss://your-websocket-server-url'; // 替换为你的 WebSocket 服务器 URL
_socket = WebSocket.WebSocket(url);
_socket!.onOpen = () {
print('WebSocket connection opened.');
// 连接打开后,你可以发送一个初始消息或执行其他操作
};
_socket!.onMessage = (WebSocket.Message message) {
setState(() {
_response = message.data;
});
print('Received message: ${message.data}');
};
_socket!.onClose = (int code, String reason) {
print('WebSocket connection closed with code $code and reason: $reason.');
// 连接关闭后,你可能需要清理资源或通知用户
};
_socket!.onError = (Object error) {
print('WebSocket error: $error');
// 处理错误,可能包括重试连接或通知用户
};
}
void sendMessage() {
if (_socket!.readyState == WebSocket.WebSocket.OPEN) {
String message = _controller.text;
_socket!.send(message);
print('Sent message: $message');
_controller.clear();
} else {
print('WebSocket is not open. Ready state: ${_socket!.readyState}');
}
}
@override
void dispose() {
_controller.dispose();
_socket?.close(1000, 'Client closing connection');
_socket = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextField(
controller: _controller,
decoration: InputDecoration(labelText: 'Send a message'),
onSubmitted: sendMessage,
),
SizedBox(height: 16),
Text('Response:', style: TextStyle(fontWeight: FontWeight.bold)),
Text(_response),
],
),
);
}
}
原理
WebSocket 通信基于 TCP 连接,但它在 TCP 连接之上添加了一个更高级的协议层,以支持全双工通信。这个协议层定义了如何打开和关闭连接、如何格式化消息以及如何处理错误。
- 握手:WebSocket 连接是通过 HTTP 握手建立的。客户端向服务器发送一个特殊的 HTTP 请求,请求头中包含
Upgrade: websocket和Connection: Upgrade字段,以及一个Sec-WebSocket-Key字段(用于安全性)。服务器如果同意升级连接,它会返回一个包含相同Sec-WebSocket-Key的响应,但经过一个特定的算法处理后(通常是 Base64 编码的 SHA-1 哈希),作为Sec-WebSocket-Accept头的值。 - 数据传输:一旦握手完成,客户端和服务器就可以开始通过 TCP 连接双向传输数据了。数据被封装成帧(frames),每个帧都有一个特定的操作码(如文本帧、二进制帧、关闭帧等)和可选的负载数据。
- 关闭连接:关闭 WebSocket 连接时,双方可以发送一个关闭帧。接收方在确认收到关闭帧后,可以关闭底层的 TCP 连接。关闭帧可以包含一个状态码和关闭原因(可选)。
在 Flutter 应用中,dart:web_socket 库提供了必要的 API 来处理 WebSocket 连接的建立、数据传输和关闭。你可以使用这些 API 来构建需要与服务器进行实时通信的 Flutter 应用。
二、序列化
在Flutter中,序列化是一个关键过程,它涉及将复杂数据结构(如对象、数组、字典等)转换为线性格式或字节流,以便于数据的存储、传输和共享。以下是关于Flutter序列化的相关流程、框架、功能代码和原理的详细解释。
1、序列化方案
1、序列化流程
- 定义数据结构:首先,你需要定义你的数据结构,这通常是通过创建Dart类来实现的。
- 选择序列化方法:Flutter支持多种序列化方法,包括手动实现序列化与反序列化方法、使用Dart内置的
dart:convert库进行JSON序列化和反序列化,以及使用第三方库如json_serializable来自动生成序列化代码。 - 实现序列化逻辑:根据选择的序列化方法,实现相应的序列化逻辑。例如,如果你选择使用
json_serializable库,你需要为类添加@JsonSerializable()注解,并生成相应的.g.dart文件。 - 使用序列化功能:在需要序列化或反序列化的地方,调用相应的方法。例如,将对象转换为JSON字符串时,使用
json.encode()方法;将JSON字符串转换为对象时,使用json.decode()方法或生成的fromJson方法。
2、支持运行时反射?
Flutter 不支持运行时反射:
- 性能优化:Flutter 依赖于 Dart VM 的 AOT(Ahead-Of-Time)编译,这意味着在编译时而不是运行时进行代码优化。摇树优化是其中的一种技术,它通过分析代码来移除那些未被使用的代码,从而减小应用的体积并提高性能。
- 代码安全性:运行时反射增加了代码的复杂性,可能导致难以追踪的错误和潜在的安全漏洞。
3、Flutter 中的替代方案
尽管 Flutter 不支持运行时反射,但它提供了其他方式来实现 JSON 的序列化和反序列化,主要是基于代码生成的技术。这种方法在编译时生成必要的代码,从而避免了运行时的性能开销和体积增加。
4、常用的 Dart/Flutter JSON 序列化库:
- dart:convert库:这是Dart内置的一个库,提供了基本的JSON序列化和反序列化功能。你可以使用
json.encode()方法将Dart对象转换为JSON字符串,使用json.decode()方法将JSON字符串转换为Dart对象。 - json_serializable:这是 Flutter 社区中最常用的 JSON 序列化库。它使用 Dart 的构建系统(dart-lang/build)来生成将 Dart 对象转换为 JSON 和从 JSON 转换回 Dart 对象的代码。这种方法既安全又高效,因为它在编译时完成所有工作。
- kt_dart:虽然不如 json_serializable 流行,但 kt_dart 是另一个基于代码生成的 JSON 序列化库。
5、使用 json_serializable 的基本步骤:
- 添加依赖:在你的
pubspec.yaml文件中添加json_annotation和build_runner的依赖。 - 创建数据模型:使用
@JsonSerializable()注解你的 Dart 类。 - 生成代码:运行
flutter pub run build_runner build命令来生成序列化代码。 - 使用生成的函数:生成的代码将包含
fromJson和toJson方法,用于将对象与 JSON 之间进行转换。
这种方法利用了 Dart 的编译时特性,既保持了 Flutter 应用的性能优化,又提供了与原生 Android/iOS 应用相似的 JSON 处理能力。
2、Json to JsonObject
在 Flutter 中基础的序列化 JSON 十分容易的。 Flutter 有一个内置的 dart:convert 的库,这个库包含了一个简单的 JSON 编码器和解码器。
下面的样例实现了一个简单用户模型。
{
"name": "John Smith",
"email": "john@example.com"
}
调用 jsonDecode() 方法来解码 json to jsonObject:
final user = jsonDecode(jsonString) as Map<String, dynamic>;
print('name: ${user['name']}, email:${user['email']}');
jsonDecode() 返回一个 Map<String, dynamic>,同原生字典,这意味着你在运行时以前都不知道值的类型。使用这个方法,你失去了大部分的静态类型语言特性:类型安全、自动补全以及最重要的编译时异常。
因此,字符串更容易出错,仅仅试用少量数据解析
3、Json to model (硬编码)
通过在模型类中实现 JSON 数据的序列化和反序列化,确保类型安全、自动完成和编译时异常检测。
实现步骤
-
定义模型类:
- 创建一个
User类,包含name和email字段。 - 提供两个构造函数:
- 一个默认构造函数
User(name, email)。 - 一个从 JSON 映射中构造实例的构造函数
User.fromJson(Map<String, dynamic> json)。
- 一个默认构造函数
- 实现一个
toJson()方法,将User实例转换为一个 JSON 映射。
- 创建一个
-
User 类实现:
class User { final String name; final String email; User(this.name, this.email); User.fromJson(Map<String, dynamic> json) : name = json['name'] as String, email = json['email'] as String; Map<String, dynamic> toJson() => { 'name': name, 'email': email, }; } -
反序列化 JSON 数据:
- 使用
jsonDecode将 JSON 字符串转换为Map<String, dynamic>。 - 调用
User.fromJson构造函数将映射转换为User实例。
final userMap = jsonDecode(jsonString) as Map<String, dynamic>; final user = User.fromJson(userMap); print('Howdy, ${user.name}!'); print('We sent the verification link to ${user.email}.'); - 使用
-
序列化 User 实例:
- 直接将
User对象传递给jsonEncode函数,不需要手动调用toJson()。
String json = jsonEncode(user); - 直接将
当属性多了手动添加会很烦,下面介绍 json_serializable 系列化公交,通过脚本生成文件,解放双手。
4、json_serializable 库
1、先看一下 简单model的系列化使用
以下是一个使用json_serializable库进行序列化的功能代码示例:
// 在pubspec.yaml文件中添加依赖
dependencies:
json_annotation: ^最新版本号
dev_dependencies:
build_runner: ^最新版本号
json_serializable: ^最新版本号
// 创建一个Dart类,并使用@JsonSerializable()注解
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
final int id;
final String name;
final bool isFollowed;
User({required this.id, required this.name, required this.isFollowed});
// 生成的fromJson方法
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
// 生成的toJson方法
Map<String, dynamic> toJson() => _$UserToJson(this);
}
// 在终端中运行flutter pub run build_runner build生成.g.dart文件
// 使用生成的代码处理JSON数据
void main() {
// 假设你从API或本地JSON文件获取了如下JSON数据
Map<String, dynamic> json = {"id": 1, "name": "John Doe", "is_followed": true};
// 从JSON创建User对象
User user = User.fromJson(json);
// 将User对象转换为JSON
Map<String, dynamic> jsonData = user.toJson();
print(jsonData); // 输出: {id: 1, name: John Doe, is_followed: true}
}
当你首次创建 json_serializable 类时,可能错误提示:
(图片来自flutter.cn)
要解决这个问题,你需要运行代码生成器来生成序列化数据模板。
有两种方式运行代码生成器。
一次性代码生成
通过在项目根目录运行命令 dart run build_runner build --delete-conflicting-outputs,你可以在任何需要的时候为你的模型生成 JSON 序列化数据代码。这会触发一次构建,遍历源文件,选择相关的文件,然后为它们生成必须的序列化数据代码。
虽然这样很方便,但是如果你不需要在每次修改了你的模型类后都要手动构建那将会很棒。
持续生成代码
监听器 让我们的源代码生成过程更加方便。它会监听我们项目中的文件变化,并且会在需要的时候自动构建必要的文件。你可以在项目根目录运行 dart run build_runner watch --delete-conflicting-outputs 启动监听。
启动监听并让它留在后台运行是安全的。
2、json_annotation 系列化流程和原理
在Flutter中,json_serializable包通过注解和代码生成工具(通常是build_runner)来自动生成JSON序列化和反序列化的代码。其核心代码逻辑可以分为几个步骤:
解析三步核心流程:
-
创建model文件
user_model.dart-> 2. 运行flutter pub run build_runner build命令 -> 3.生成解析文件user_model.g.dart -
添加依赖: 首先,你需要在你的
pubspec.yaml文件中添加json_annotation和build_runner依赖:dependencies: flutter: sdk: flutter json_annotation: ^4.5.0 # 确保版本是最新的 dev_dependencies: build_runner: ^2.1.7 # 确保版本是最新的 -
创建数据模型 文件 xx.dart: 然后,在你的Dart文件中定义一个数据模型,并使用
@JsonSerializable()注解。例如:import 'package:json_annotation/json_annotation.dart'; part 'user_model.g.dart'; // 这行告诉生成器生成代码的文件名 @JsonSerializable() class User { final String name; final int age; User({required this.name, required this.age}); // 生成的fromJson方法将在这里 factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); // 生成的toJson方法将在这里 Map<String, dynamic> toJson() => _$UserToJson(this); } -
运行代码生成器: 使用Flutter命令行工具运行
flutter pub run build_runner build命令。这将生成一个user_model.g.dart文件,其中包含了fromJson和toJson方法的实现。 -
生成的代码 对应代码文件 xx.g.dart:
json_serializable生成的代码大致如下(注意,这是示例代码,实际生成的代码可能会有所不同):part of 'user_model.dart'; User _$UserFromJson(Map<String, dynamic> json) { return User( name: json['name'] as String, age: json['age'] as int, ); } Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{ 'name': instance.name, 'age': instance.age, }; -
使用生成的代码: 现在,你可以在你的应用中使用这些方法来序列化和反序列化
User对象。例如:void main() { // 创建一个User对象 User user = User(name: 'Alice', age: 30); // 序列化User对象为JSON Map<String, dynamic> json = user.toJson(); print(json); // 输出: {name: Alice, age: 30} // 反序列化JSON为User对象 User newUser = User.fromJson(json); print(newUser.name); // 输出: Alice print(newUser.age); // 输出: 30 }
核心逻辑概述
- 注解处理
- 使用
@JsonSerializable()注解标记需要生成代码的类。 - 通过
part指令指定生成代码的文件。
- 使用
- 代码生成
build_runner工具扫描项目中所有的Dart文件,寻找带有@JsonSerializable()注解的类。- 根据类的字段和构造函数生成
fromJson和toJson方法。 - 生成的代码文件(如
user_model.g.dart)包含实际的序列化和反序列化逻辑。
- 使用生成的代码
- 开发者在代码中调用生成的
fromJson和toJson方法,实现JSON数据的序列化和反序列化。
- 开发者在代码中调用生成的
通过上述步骤,json_serializable包简化了JSON数据处理的复杂性,提高了代码的可读性和可维护性
3、嵌套model系列化
下面是 json_serializable example 示例代码 ,注释了代码的主要部分和它们的作用
example.dart 文件:
// 版权信息和许可证声明
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// 导入json_annotation包,用于JSON序列化和反序列化
import 'package:json_annotation/json_annotation.dart';
// 指定生成的代码文件(由json_serializable生成)
part 'example.g.dart';
// 使用@JsonSerializable()注解,指示这个类可以被自动转换为JSON或从JSON转换回来
@JsonSerializable()
class Person {
// 必填字段
final String firstName;
// 可选字段,如果为null则不包括在生成的JSON中
@JsonKey(includeIfNull: false)
final String? middleName;
final String lastName;
// 使用@JsonKey指定JSON字段名
@JsonKey(name: 'date-of-birth')
final DateTime dateOfBirth;
// 可选的DateTime字段,如果为null则不包括在生成的JSON中
@JsonKey(name: 'last-order')
final DateTime? lastOrder;
// 订单列表
List<Order> orders;
// 构造函数
Person(
this.firstName,
this.lastName,
this.dateOfBirth, {
this.middleName,
this.lastOrder,
List<Order>? orders,
}) : orders = orders ?? <Order>[]; // 如果orders为null,则默认为空列表
// 从JSON创建Person对象的工厂方法
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
// 将Person对象转换为JSON
Map<String, dynamic> toJson() => _$PersonToJson(this);
}
// Order类,同样使用@JsonSerializable(includeIfNull: false)来自动处理JSON转换
@JsonSerializable(includeIfNull: false)
class Order {
int? count;
int? itemNumber;
bool? isRushed;
Item? item;
// 使用@JsonKey自定义fromJson和toJson方法处理Duration的转换
@JsonKey(
name: 'prep-time',
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration? prepTime;
// 使用@JsonKey自定义fromJson和toJson方法处理DateTime的转换
@JsonKey(fromJson: _dateTimeFromEpochUs, toJson: _dateTimeToEpochUs)
final DateTime date;
// 构造函数
Order(this.date);
// 从JSON创建Order对象的工厂方法
factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
// 将Order对象转换为JSON
Map<String, dynamic> toJson() => _$OrderToJson(this);
// 静态方法,用于从毫秒数转换Duration
static Duration? _durationFromMilliseconds(int? milliseconds) =>
milliseconds == null ? null : Duration(milliseconds: milliseconds);
// 静态方法,用于将Duration转换为毫秒数
static int? _durationToMilliseconds(Duration? duration) =>
duration?.inMilliseconds;
// 静态方法,用于从微秒数转换DateTime
static DateTime _dateTimeFromEpochUs(int us) =>
DateTime.fromMicrosecondsSinceEpoch(us);
// 静态方法,用于将DateTime转换为微秒数
static int? _dateTimeToEpochUs(DateTime? dateTime) =>
dateTime?.microsecondsSinceEpoch;
}
// Item类,使用@JsonSerializable()注解
@JsonSerializable()
class Item {
int? count;
int? itemNumber;
bool? isRushed;
// 默认构造函数
Item();
// 从JSON创建Item对象的工厂方法
factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);
// 将Item对象转换为JSON
Map<String, dynamic> toJson() => _$ItemToJson(this);
}
// 使用@JsonLiteral注解从指定的JSON文件加载数据
@JsonLiteral('data.json')
Map get glossaryData => _$glossaryDataJsonLiteral;
这段代码主要展示了如何使用json_annotation包来自动处理Dart对象与JSON之间的转换,包括如何处理可选字段、自定义字段名、以及自定义类型转换
运行flutter pub run build_runner build命令 生成 example.g.dart 解析文件:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'example.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Person _$PersonFromJson(Map<String, dynamic> json) => Person(
json['firstName'] as String,
json['lastName'] as String,
DateTime.parse(json['date-of-birth'] as String),
middleName: json['middleName'] as String?,
lastOrder: json['last-order'] == null
? null
: DateTime.parse(json['last-order'] as String),
orders: (json['orders'] as List<dynamic>?)
?.map((e) => Order.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$PersonToJson(Person instance) => <String, dynamic>{
'firstName': instance.firstName,
if (instance.middleName case final value?) 'middleName': value,
'lastName': instance.lastName,
'date-of-birth': instance.dateOfBirth.toIso8601String(),
'last-order': instance.lastOrder?.toIso8601String(),
'orders': instance.orders,
};
Order _$OrderFromJson(Map<String, dynamic> json) => Order(
Order._dateTimeFromEpochUs((json['date'] as num).toInt()),
)
..count = (json['count'] as num?)?.toInt()
..itemNumber = (json['itemNumber'] as num?)?.toInt()
..isRushed = json['isRushed'] as bool?
..item = json['item'] == null
? null
: Item.fromJson(json['item'] as Map<String, dynamic>)
..prepTime =
Order._durationFromMilliseconds((json['prep-time'] as num?)?.toInt());
Map<String, dynamic> _$OrderToJson(Order instance) => <String, dynamic>{
if (instance.count case final value?) 'count': value,
if (instance.itemNumber case final value?) 'itemNumber': value,
if (instance.isRushed case final value?) 'isRushed': value,
if (instance.item case final value?) 'item': value,
if (Order._durationToMilliseconds(instance.prepTime) case final value?)
'prep-time': value,
if (Order._dateTimeToEpochUs(instance.date) case final value?)
'date': value,
};
Item _$ItemFromJson(Map<String, dynamic> json) => Item()
..count = (json['count'] as num?)?.toInt()
..itemNumber = (json['itemNumber'] as num?)?.toInt()
..isRushed = json['isRushed'] as bool?;
Map<String, dynamic> _$ItemToJson(Item instance) => <String, dynamic>{
'count': instance.count,
'itemNumber': instance.itemNumber,
'isRushed': instance.isRushed,
};
// **************************************************************************
// JsonLiteralGenerator
// **************************************************************************
final _$glossaryDataJsonLiteral = {
'glossary': {
'title': 'example glossary',
'GlossDiv': {
'title': 'S',
'GlossList': {
'GlossEntry': {
'ID': 'SGML',
'SortAs': 'SGML',
'GlossTerm': 'Standard Generalized Markup Language',
'Acronym': 'SGML',
'Abbrev': 'ISO 8879:1986',
'GlossDef': {
'para': 'A meta-markup language, used to create markup languages.',
'GlossSeeAlso': ['GML', 'XML']
},
'GlossSee': 'markup'
}
}
}
}
};
5、解析大型JSON数据
当你需要进行一个非常复杂的计算时,例如解析一个巨大的 JSON 文档。如果这项工作耗时超过了 16 毫秒,那么你的用户就会感受到掉帧,为了避免在UI线程(也称为主线程)上执行耗时操作,从而保持应用的流畅性。
这通常涉及将解析工作转移到后台线程或isolate中进行
注意:
Isolates 通过来回传递消息来交流。这些消息可以是任何值,它们可以是 null、num、bool、double 或者 String,或者一些简单的 List<Model> 都没问题。当你试图传递更复杂的对象时,你可能会遇到错误,例如在 isolates 之间的 Future 或者 http.Response。
因此,通常我们移到**后台线程****解析数据就足够了
1、后台线程解析数据
Future<List<MyData>> parseAndTransformData(String jsonString) async {
// 在这里解析JSON字符串为Dart类型(这一步通常很快,不会阻塞UI)
final parsedData = jsonDecode(jsonString) as List;
// 将Dart类型转换为自定义对象列表(这一步可能稍微耗时,但仍然在事件循环中异步执行)
return parsedData.map<MyData>((json) => MyData.fromJson(json)).toList();
}
然后,你可以结合fetchJsonData和parseAndTransformData来获取并解析数据:
Future<List<MyData>> fetchAndParseData() async {
final jsonString = await fetchJsonData();
return parseAndTransformData(jsonString);
}
在UI中显示数据
使用FutureBuilder或StatefulWidget的setState方法来在UI中显示解析后的数据。
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late Future<List<MyData>> futureData;
@override
void initState() {
super.initState();
futureData = fetchAndParseData();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<MyData>>(
future: futureData,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else if (snapshot.connectionState == ConnectionState.done) {
// 这里可以安全地使用snapshot.data,因为它已经包含了解析后的数据
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return Text('${snapshot.data![index].someProperty}');
},
);
} else {
return CircularProgressIndicator();
}
},
);
}
}
2、isolate中进行**解析数据
Flutter 提供的 compute() 方法将 不太复杂的对象 解析和转换的工作移交到一个后台 isolate 中。这个 compute() 函数可以在后台 isolate 中运行复杂的函数并返回结果。在这里,我们就需要将 parsePhotos() 方法放入后台。
Future<List<MyData>> fetchPhotos(http.Client client) async {
final response = await client
.get(Uri.parse('https://....'));
return compute(parsePhotos, response.body);
}
三、数据平台通道
消息使用平台通道在客户端(UI)和宿主(平台)之间传递,如下图所示:
Flutter与原生平台(如Android和iOS)之间的数据交互是通过平台通道(Platform Channels)实现的。
这种机制允许Flutter应用中调用原生代码,或者从原生代码中调用Flutter代码,从而利用原生平台提供的特性和API,同时保持大部分应用代码在Flutter中。
1、平台通道的类型
Flutter使用消息传递机制通过不同类型的平台通道进行跨平台交互,主要包括MethodChannel、EventChannel和BasicMessageChannel。
- MethodChannel:用于发送方法调用并接收返回值。它主要用于Flutter与原生代码之间的同步调用。例如,Flutter可以调用一个原生方法来获取电池电量,原生代码执行该操作并返回结果。
- EventChannel:用于发送从原生平台到Flutter的持续性数据流(如传感器数据、位置更新等)。它主要用于事件驱动的异步通信。
- BasicMessageChannel:支持双向通信,可以发送和接收任意类型的数据。它主要用于需要传递字符串或半结构化信息的场景。
2、MethodChannel
MethodChannel的工作原理:
- Flutter端:使用MethodChannel类创建一个通道实例,并通过invokeMethod方法调用原生平台的方法,或者使用setMethodCallHandler方法设置一个处理原生平台调用Flutter方法的回调。
- 原生平台端(Android和iOS):通过特定的代码来接收来自Flutter的方法调用,并可以通过通道向Flutter端返回结果。
展示Flutter与原生代码的交互过程:
1、Flutter端代码:
import 'package:flutter/services.dart';
final MethodChannel _channel = MethodChannel('com.example.channel');
Future<String> getPlatformVersion() async {
try {
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
} on PlatformException catch (e) {
return "Failed to get platform version: '${e.message}'.";
}
}
2、原生Android端代码:
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import android.os.Build;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.example.channel";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getPlatformVersion")) {
result.success(Build.VERSION.RELEASE);
} else {
result.notImplemented();
}
}
}
);
}
}
3、原生iOS端代码:
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "com.example.channel", binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if call.method == "getPlatformVersion" {
result(UIDevice.current.systemVersion)
} else {
result(FlutterMethodNotImplemented)
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
在上述示例中,Flutter端通过MethodChannel调用原生平台的方法(getPlatformVersion),原生平台接收到调用后执行相应的方法,并将结果返回给Flutter端。
3、EventChannel
Flutter的EventChannel是一种系统提供的用于从原生平台(iOS或Android)向Flutter发送事件流的机制:
- Flutter端:通过设置事件监听器来接收来自原生平台的事件数据流。
- 原生平台端:不断地发送事件到Flutter端。
1、EventChannel介绍
- 作用
- EventChannel允许原生平台主动向Flutter发送事件流,而不是像MethodChannel那样只能由Flutter发起单次方法调用。
- 通过EventChannel,Flutter可以监听原生平台发送的事件,并在Flutter应用中作出响应。
- 应用场景
- 传感器数据采集:如加速度计、陀螺仪、磁力计等的数据获取。
- 后台任务完成通知:当应用程序在后台运行时,执行长时间任务后的通知。
- 音频和视频流传输:在Flutter应用和原生平台之间传输音频和视频流。
- 实时数据更新:如网络状态、用户交互等实时数据的更新。
- 工作原理
- Flutter端创建一个EventChannel对象,并指定一个唯一的通道名称。
- Flutter端通过EventChannel的
receiveBroadcastStream方法监听原生平台发送的事件,该方法返回一个Stream对象。 - 原生端创建一个与Flutter端通道名称相匹配的EventChannel对象,并设置StreamHandler来处理事件发送。
- 当原生平台有事件需要发送给Flutter时,通过StreamHandler的
onListen方法发送事件数据。 - Flutter端通过监听的Stream对象接收事件数据,并执行相应的处理逻辑。
2、EventChannel使用方法
1. Flutter端代码
在Dart代码中,定义一个EventChannel,并指定一个唯一的通道名称。然后,通过receiveBroadcastStream方法监听来自原生平台的事件。
import 'package:flutter/services.dart';
class NativeEvent {
static const EventChannel _eventChannel = EventChannel('com.example.app/native_events');
void startListening() {
_eventChannel.receiveBroadcastStream().listen(
_onEvent,
onError: _onError,
);
}
void _onEvent(Object event) {
// 处理从原生平台发送过来的事件
print('Received event: $event');
}
void _onError(Object error) {
// 处理监听过程中的错误
print('Received error: $error');
}
}
在合适的位置调用startListening方法来开始监听事件。
2. 原生端代码(以Android为例)
在Android端,使用EventChannel在Java/Kotlin代码中发送事件到Flutter。
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.PluginRegistry;
public class NativeEventsPlugin implements EventChannel.StreamHandler {
private static final String CHANNEL = "com.example.app/native_events";
// 注册插件
public static void registerWith(PluginRegistry.Registrar registrar) {
new EventChannel(registrar.messenger(), CHANNEL).setStreamHandler(new NativeEventsPlugin());
}
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
// 当Flutter开始监听时开始发送事件
events.success("Event from native");
// 可以在这里设置更多逻辑来发送事件
}
@Override
public void onCancel(Object arguments) {
// 当Flutter取消监听时进行清理工作
}
}
在MainActivity中注册插件(如果需要在MainActivity中特别注册的话,通常插件注册会在插件的注册方法中自动完成)。
import android.os.Bundle;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 其他逻辑
// NativeEventsPlugin.registerWith(registrarFor(CHANNEL)); // 如果需要在这里注册,则取消注释并替换CHANNEL为实际通道名
}
}
3. 原生端代码(以iOS为例)
在iOS端,使用Swift或Objective-C代码实现EventChannel的事件发送。
import Flutter
class NativeEventsPlugin: NSObject, FlutterEventChannel.EventSink {
private let eventChannel: FlutterEventChannel
private var eventSink: FlutterEventChannel.EventSink?
init(withMessenger messenger: FlutterBinaryMessenger, channelName: String = "com.example.app/native_events") {
eventChannel = FlutterEventChannel(name: channelName, binaryMessenger: messenger)
super.init()
eventChannel.setStreamHandler({
(eventSink, error) -> FlutterError? in
self.eventSink = eventSink
return nil
})
}
func sendEvent(_ event: Any) {
eventSink?(event)
}
}
在需要发送事件的地方创建NativeEventsPlugin实例并调用sendEvent方法。
let nativeEventsPlugin = NativeEventsPlugin(withMessenger: self.binaryMessenger)
nativeEventsPlugin.sendEvent("Event from native")
4、注意事项
- 通道名称一致性:Flutter端和原生端的通道名称必须保持一致。
- 事件数据格式:Flutter端和原生端需要约定好事件数据的格式,以便能够正确地解析和处理。
- 异步处理:由于Stream对象是异步的,因此在监听来自原生平台的事件时需要使用异步编程的技术。
- 资源清理:当Flutter取消监听时,原生端应进行必要的资源清理工作。
4、BasicMessageChannel
Flutter的BasicMessageChannel是一种用于在Flutter和原生平台(Android和iOS)之间进行消息传递的通道。它允许Flutter和原生平台通过指定的编解码器对消息进行编码和解码,从而实现双向通信。以下是对Flutter BasicMessageChannel的详细介绍:
1、使用方法
1. 构造方法
在Flutter(Dart)端和原生端,都需要创建BasicMessageChannel对象,并指定通道名称和消息编解码器。通道名称在两端必须保持一致,以确保能够正确通信。消息编解码器用于对消息进行编码和解码,常用的编解码器包括BinaryCodec、StringCodec、JSONMessageCodec和StandardMessageCodec等。
2. 发送消息
在Flutter端,可以使用BasicMessageChannel对象的send方法向原生端发送消息。该方法接受一个消息参数,并返回一个Future对象,用于处理原生端的回复或错误。
在原生端,可以使用BasicMessageChannel对象的send方法向Flutter端发送消息。同样,该方法也接受一个消息参数,并可以选择性地提供一个回调函数来处理Flutter端的回复。
3. 接收消息
在Flutter端,需要为BasicMessageChannel对象设置一个消息处理器(MessageHandler),以便在接收到原生端发送的消息时进行处理。消息处理器是一个函数,它接受一个消息参数,并返回一个Future对象,该对象包含要发送给原生端的回复。
在原生端,可以使用setMessageHandler方法为BasicMessageChannel对象设置一个消息处理器。消息处理器是一个接口或函数,它接受一个消息参数和一个回复回调函数。在接收到Flutter端发送的消息时,消息处理器会调用回复回调函数来发送回复。
2、BasicMessageChannel
在Flutter应用中,BasicMessageChannel 用于在Flutter和原生平台(如iOS)之间进行双向的、持续的通信。以下是一个简单的iOS示例,展示了如何设置和使用BasicMessageChannel。
Flutter 端
首先,在Flutter端定义一个BasicMessageChannel,并指定消息编解码器。这里我们使用StandardMessageCodec,它可以处理大多数基本数据类型(如字符串、数字、列表、映射等)。
import 'package:flutter/services.dart';
// 定义通道名称和编解码器
const String _channelName = "com.example.my_app/basic_message";
const BasicMessageChannel<String, String> _channel =
BasicMessageChannel<String, String>(_channelName, StandardMessageCodec());
// 发送消息到原生平台
void sendMessageToNative(String message) {
_channel.send(message);
}
// 接收来自原生平台的消息
_channel.setMessageHandler((String message) {
// 处理接收到的消息
print("Received message from native: $message");
});
iOS 端 (Object-C)
在iOS端,你需要在AppDelegate或任何其他适当的位置设置并监听BasicMessageChannel。
-
导入必要的框架
#import <Flutter/Flutter.h> #import <Foundation/Foundation.h> -
定义通道名称和编解码器
static NSString *const ChannelName = @"com.example.my_app/basic_message"; -
在
AppDelegate中设置通道- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { FlutterViewController *controller = (FlutterViewController *)self.window.rootViewController; // 设置消息处理器 [FlutterBasicMessageChannel channelWithName:ChannelName binaryMessenger:controller codec:[FlutterStandardMessageCodec sharedInstance] ] setMessageHandler:^(id _Nullable message, FlutterReply _Nonnull callback) { // 处理来自Flutter的消息 NSString *messageString = (NSString *)message; NSLog(@"Received message from Flutter: %@", messageString); // 发送回复消息到Flutter NSString *reply = @"Hello from iOS!"; callback(@(reply)); }]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; }注意:在iOS中,
FlutterReply回调用于发送消息回Flutter。但是,对于BasicMessageChannel,这个回调实际上是不被直接使用的,因为BasicMessageChannel是双向的,并且原生平台可以通过相同的通道发送消息回Flutter,而不需要使用回调。这里的回调是MethodChannel特有的。对于BasicMessageChannel,你应该在需要的时候直接发送消息。 -
发送消息到Flutter
如果你需要从iOS发送消息到Flutter,你可以这样做:
FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel channelWithName:ChannelName binaryMessenger:[self flutterViewController] codec:[FlutterStandardMessageCodec sharedInstance]]; NSString *message = @"Hello from iOS!"; [channel sendMessage:message];在这个例子中,
[self flutterViewController]应该返回你的FlutterViewController实例。这通常是在你的AppDelegate中或者你的Flutter嵌入视图控制器中。
iOS 端 (Swift)
在iOS端,你需要在AppDelegate或任何其他适当的位置设置并监听BasicMessageChannel。
-
导入必要的框架
import UIKit import Flutter -
定义通道名称
let ChannelName = "com.example.my_app/swift_basic_message" -
在
AppDelegate中设置通道@UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) // 设置FlutterEngine(如果你使用的是FlutterEmbeddingV2) let flutterEngine = (window?.rootViewController as? FlutterViewController)?.engine // 设置消息处理器 let channel = FlutterBasicMessageChannel<String, String>( name: ChannelName, binaryMessenger: flutterEngine?.binaryMessenger, codec: FlutterStandardMessageCodec.sharedInstance() ) channel.setMessageHandler { (message, reply) in // 处理来自Flutter的消息 print("Received message from Flutter: \(message ?? "nil")") // 发送回复消息到Flutter(可选) reply("Hello from iOS (Swift)!") } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }注意:在Flutter 2.x及更高版本中,如果你使用的是
FlutterViewController或FlutterFragmentActivity来嵌入Flutter,则flutterEngine可能已经由这些组件管理。在这种情况下,你可能不需要在AppDelegate中显式创建FlutterEngine。 -
发送消息到Flutter
如果你需要从iOS发送消息到Flutter,你可以这样做:
// 假设你已经有了FlutterEngine的实例,这里用flutterEngine表示 let channel = FlutterBasicMessageChannel<String, String>( name: ChannelName, binaryMessenger: flutterEngine.binaryMessenger, codec: FlutterStandardMessageCodec.sharedInstance() ) let message = "Hello from iOS (Swift)!" channel.send(message)
Android 端 (Kotlin)
在Android端,你需要在MainActivity或任何其他适当的位置设置并监听BasicMessageChannel。
-
导入必要的包
import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.dart.DartExecutor import io.flutter.plugin.common.BasicMessageChannel import io.flutter.plugin.common.StandardMessageCodec import android.util.Log -
定义通道名称和编解码器
private const val CHANNEL_NAME = "com.example.my_app/kotlin_basic_message" -
在
MainActivity中设置通道class MainActivity: AppCompatActivity() { private lateinit var flutterEngine: FlutterEngine override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 初始化FlutterEngine(如果你使用的是FlutterEmbeddingV2) flutterEngine = FlutterEngine(this) flutterEngine.navigationChannel.setInitialRoute("/") flutterEngine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ) // 设置消息处理器 val channel = BasicMessageChannel( flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME, StandardMessageCodec.INSTANCE ) channel.setMessageHandler { message, reply -> // 处理来自Flutter的消息 val messageStr = message as String? ?: "null" Log.d("FlutterChannel", "Received message from Flutter: $messageStr") // 发送回复消息到Flutter(可选) val replyStr = "Hello from Android (Kotlin)!" reply.reply(replyStr) } // ... 其他初始化代码,如将FlutterEngine附加到FlutterActivity或Fragment } // 确保在适当的生命周期方法中销毁FlutterEngine(如果你自己管理它) override fun onDestroy() { super.onDestroy() flutterEngine.destroy() } } -
发送消息到Flutter
如果你需要从Android发送消息到Flutter,你可以这样做:
// 假设flutterEngine已经初始化 val channel = BasicMessageChannel( flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME, StandardMessageCodec.INSTANCE ) val message = "Hello from Android (Kotlin)!" channel.send(message)