返回博客主页
← 所有文章

将 Flutter 与 NativeScript 结合使用

2023 年 5 月 10 日 — 作者:技术指导委员会 (TSC)

Flutter 为 NativeScript 提供了另一个用于创意视图开发的选项。让我们看看如何使用 @nativescript-community/ble 来丰富 Flutter 的蓝牙功能。

👉 演示仓库

这篇文章的灵感来自 Sovik Biswas 的探索 这里

创建一个与 NativeScript 一起使用的 Flutter 模块

先决条件

无论您是否有现有的 Flutter 应用程序,或者只是想在整个 NativeScript 应用程序中混合使用不同的 Flutter 视图,我们都可以将它们用作 Flutter 模块。

1. 将 Flutter 添加到 NativeScript 应用程序

您可以在任何现有的 NativeScript 应用程序中使用 Flutter,或者使用 ns create 创建一个新的应用程序。

然后,我们可以在项目目录的根目录下创建一个 Flutter 模块

flutter create --template module flutter_views

注意:您可以从这个 flutter_views 文件夹中运行 flutter run --debugflutter build ios,就像任何普通的 Flutter 项目一样进行开发。

Flutter 文档这里了解更多信息。

2. 配置您的 Dart 代码以具有命名入口点

命名入口点允许我们通过将入口点与视图 ID 匹配,在我们的 NativeScript 应用程序中使用不同的 Flutter 视图,例如:<Flutter id="myFlutterView" />

  • main.dart
@pragma('vm:entry-point')
void myFlutterView() => runApp(const MyFlutterView());

3. 配置平台以供使用

为了简洁起见,我们将在本文中演示 iOS,但是您可以看到包含 Android 的 完整示例库

App_Resources/iOS/Podfile 应该包含以下内容,以引用我们的 Flutter 模块。

platform :ios, '14.0'

flutter_application_path = '../../flutter_views'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
install_all_flutter_pods(flutter_application_path)

post_install do |installer|
    flutter_post_install(installer) if defined?(flutter_post_install)
end

将 Flutter 调试权限添加到 App_Resources/iOS/Info.plist

<key>NSLocalNetworkUsageDescription</key>
<string>Allow Flutter tools to debug your views.</string>
<key>NSBonjourServices</key>
<array>
  <string>_dartobservatory._tcp</string>
</array>

4. 安装 @nativescript/flutter

npm install @nativescript/flutter

5. 在需要的地方使用 Flutter

确保在引导您的应用程序之前初始化 Flutter 引擎,通常是在 app.tsmain.ts

import { init } from '@nativescript/flutter';
init();

// bootstrap app...

在任何地方使用 Flutter

<Page xmlns="http://schemas.nativescript.org/tns.xsd"
  xmlns:ui="@nativescript/flutter">
    <ui:Flutter id="myFlutterView"></ui:Flutter>
</Page>

当使用 flavors 时,您只需为您的标记注册元素以供使用

import { Flutter } from '@nativescript/flutter'

// Angular
import { registerElement } from '@nativescript/angular'
registerElement('Flutter', () => Flutter)

// Solid
import { registerElement } from 'dominative';
registerElement('flutter', Flutter);

// Svelte
import { registerNativeViewElement } from 'svelte-native/dom'
registerNativeViewElement('flutter', () => Flutter);

// React
import { registerElement } from 'react-nativescript';
registerElement('flutter', () => Flutter);

// Vue
import Vue from 'nativescript-vue'
Vue.registerElement('Flutter', () => Flutter)

准备 Flutter 以进行双向 NativeScript 通信

我们将使用 平台通道 来设置 Flutter 和 NativeScript 之间的双向通信。

在我们的 Dart 代码中,我们只需要一个 nativescript 通道来处理我们可能想要的 任何 平台行为。对于 json 负载消息,我们将使用 BasicMessageChannelStringCodec。我们也可以在我们的状态构造函数中设置消息处理程序。

  • main.dart
import 'package:flutter/services.dart';
import 'dart:convert';

class _MyPageState extends State<MyPage> {
  static const platform = BasicMessageChannel('nativescript', StringCodec());

  _MyPageState() {
    // Receive platform messages from NativeScript
    platform.setMessageHandler((String? message) async {
      Map<String, dynamic> platformMessage = jsonDecode(message!);
      switch (platformMessage['type']) {
        case 'salutation':
          // use any data from the platform
          String hello = platformMessage['data'];
          print(hello);
          break;
      }
      return 'success';
    });
  }
}
  • ns-view.html
<Flutter id="myFlutterView" loaded="loadedFlutter"></Flutter>
  • ns-view.ts
let flutter: Flutter;

export function loadedFlutter(args) {
  flutter = args.object;

  // Send Flutter messages from NativeScript
  flutter.sendMessage('salutation', 'hello');
}

通过 NativeScript 启用蓝牙

npm install @nativescript-community/ble

将蓝牙权限添加到 App_Resources/iOS/Info.plist

<key>UIRequiredDeviceCapabilities</key>
<array>
  <string>bluetooth-le</string>
  <string>location-services</string>
</array>
<key>UIBackgroundModes</key>
<array>
  <string>bluetooth-central</string>
  <string>bluetooth-peripheral</string>
  <string>location</string>
</array>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Use Bluetooth to connect to your devices</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Use Bluetooth to connect to your devices</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Use location with Bluetooth devices</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Use location with Bluetooth devices</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Use location with Bluetooth devices</string>

设置蓝牙插件以开始/停止扫描,同时将发现的设备发送到 Flutter。

import { Utils } from "@nativescript/core";
import { Bluetooth, Peripheral } from "@nativescript-community/ble";
import { Flutter, FlutterChannelType } from "@nativescript/flutter";

let flutter: Flutter;
// Flutter can call NativeScript through these channel types
const channel: FlutterChannelType = {
  startScanning: _startScanning,
  stopScanning: _stopScanning,
};

const bluetooth: Bluetooth = new Bluetooth();
const discoveredPeripherals: Array<{ name: string; address: string }> = [];
// reduce extraneous bluetooth scan results when emitting to Flutter
const throttleScanResults = Utils.throttle(_throttleScanResults, 600);

function _throttleScanResults() {
    flutter.sendMessage('scanResults', discoveredPeripherals);
}

function _startScanning() {
  bluetooth.on(Bluetooth.device_discovered_event, result => {
    const peripheral = <Peripheral>result.data;
    if (peripheral?.name) {
      if (!discoveredPeripherals.find((p) => p.address === peripheral.UUID)) {
        discoveredPeripherals.push({
          name: peripheral.name.trim(),
          address: peripheral.UUID?.trim(),
        });
      }
      throttleScanResults();
    }
  });
  bluetooth.startScanning({});
}

function _stopScanning() {
    bluetooth.stopScanning();
    bluetooth.off(Bluetooth.device_discovered_event);
}

我们可以将 channel 绑定到我们的 Flutter 组件。这在 NativeScript 和 Flutter 之间创建了一个消息类型的契约以进行通信。

<Flutter id="myFlutterView" channel="{{channel}}" />

从 NativeScript 在 Flutter 中接收蓝牙数据

在我们的 main.dart 文件中,我们可以设置接收数据的消息类型契约。

_MyPageState() {
  platform.setMessageHandler((String? message) async {
    Map<String, dynamic> platformMessage = jsonDecode(message!);
    switch (platformMessage['type']) {
      case 'scanResults':
        // prepare results for presentation in a ListView
        List<BluetoothDevice> devices = platformMessage['data']
            .map<BluetoothDevice>((i) => BluetoothDevice.fromJson(i))
            .toList();
        setState(() {
          _devicesList = devices;
        });
        break;
    }
    return 'success';
  });
}

// Bindable methods for Flutter to communicate to NativeScript
void stopScanning() {
  Map<String, dynamic> message = {'type': 'stopScanning'};
  platform.send(jsonEncode(message));
}

void startScanning() {
  // List<BluetoothDevice> devices = [];
  Map<String, dynamic> message = {'type': 'startScanning'};
  platform.send(jsonEncode(message));
}

我们现在可以设置我们的 Widget 来绑定到我们的数据。而不是显示整个 Flutter Widget 树,您可以看到 完整的示例这里

进一步探索物联网和 Arduino

这篇文章的灵感很大程度上来自 Sovik Biswas 的出色探索 这里,您可以在其中挑战自己 与 Arduino 设备对话,以获得更有趣的功能。

干杯 🍻

就像 Open Native 为框架和平台开发者跨生态系统结合各自优势创造了可能性一样,Flutter 开发者也可以利用 NativeScript 插件来丰富项目可能性。

与同行合作是富有成效和有益的;无论他们的背景或技能如何。

向一个充满欢快合作的友好科技社区致敬 🌸