# dancing_with_flutter **Repository Path**: maginawin/dancing_with_flutter ## Basic Information - **Project Name**: dancing_with_flutter - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-12-31 - **Last Updated**: 2025-01-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Dancing With Flutter 在已有的 iOS 或 Android 项目中,新增 Flutter 页面的示例。 以 NFC 通讯为例,iOS 与 Android 引用同样的 Flutter 代码,实现相同的功能。 ## Set up iOS project 参考链接:https://docs.flutter.dev/add-to-app/ios/project-setup 本示例 iOS 最小版本为 15.0 ``` cd dancing_with_flutter flutter create --template module my_flutter cd my_flutter flutter pub get ``` 现在有三种方法添加 Flutter 到 iOS App 中: * Use CocoaPods * Use frameworks * Use frameworks and Cocoapods > 示例中使用 Use Cocoapods,更灵活 ### Use CocoaPods **Cocoapods 版本至少 1.16.0** > 校验 > pod --version > 1.16.2 示例结构: * /path/to/MyApp * my_flutter * .ios/ * Flutter/ * podhelper.rb * MyApp/ * Podfile ``` cd ios_demo/demo pod init // Update your Podfile config file. ``` 当 build 时,Xcode 会将 Dart 代码、每个 Flutter 插件和 Flutter 引擎打包到 *.xcframework bundles 中。 然后 CocoaPods 的 podhelper.rb 脚本将这个 *.xcframework bundles 嵌入到项目中。 * Flutter.xcframework contains the Flutter engine. * App.xcframework contains the compiled Dart code for this project. * .xcframework contains one Flutter plugin. 要将 Flutter engine、Dart code, and Flutter plugins 嵌入到 iOS App 中,请完成以下过程。 ``` flutter pub get cd /path/to/MyApp/MyApp pod install // open MyApp.xcworkspace, and select Product > Build ``` 如果在 pod install 时出现问题,按以下思路解决问题: * 检查 pod --version 是否为 1.16.0+ * 检查 echo $PATH 是否正常,是否有重复 * 检查 which ruby 是否显示 /opt/homebrew/opt/ruby/bin/ruby * 检查 ruby --version 是否 3.4.0+ 若否: * 升级 brew 然后安装 ruby * 更新 ~/.zshrc 文件解决 $PATH 重复和没有使用 /opt/homebrew ruby 的问题 * 完全删除所有 cocoapods 重新用 gem install cocoapods 安装 * 校验 pod 与 ruby 如果在 Xcode 中运行时报错: ``` [ +22 ms] Flutter failed to write to a file at "/Users/maginawin/Library/Developer/Xcode/DerivedData/demo-aklbjsyedqnqvjgpnmunibmytajt/Build/Products/Debug-iphoneos/.last_build_id". Sandbox: rsync.samba(21125) deny(1) file-write-create /Users/maginawin/Library/Developer/Xcode/DerivedData/demo-aklbjsyedqnqvjgpnmunibmytajt/Build/Products/Debug-iphoneos/Flutter.framework ``` 试试:**Set "No" for the "Use Scripting sandbox" option on Build Settings of target.** ## Add a single Flutter screen 从现有的 iOS App 启动 Flutter 界面,需要启动 FlutterEngine 和一个 FlutterViewController。 FlutterEngine 充当 Dart VM 和 Flutter 运行时的主机,FlutterViewController 附加到 FlutterEngine 以将输入事件传递给 Flutter 并显示 FlutterEngine 渲染的帧。 Tips: * 在 Info.plist 中 Add Row: Bonjour services `_dartVmService._tcp` 和 Privacy - Local Network Usage Description * 其实应该添加 Info-Debug.plist 和 Info-Release.plist 但是不添加应该也可以;添加的流程[参考官网](https://docs.flutter.dev/add-to-app/ios/project-setup) * 为 App 预热一个长寿命的 FlutterEngine * Flutter 和 Dart state 将比一个 FlutterViewController 存活更久 * 显示 UI 之前,App 与插件可以与 Flutter 和 Dart 逻辑进行交互 ### UIKit Swift 在 AppDelegate 中创建一个 FlutterEngine,它作为 Property 公开。 关键代码: ``` import Flutter import FlutterPluginRegistrant lazy var flutterEngine = FlutterEngine(name: "my flutter engine") flutterEngine.run() GeneratedPluginRegistrant.register(with: self.flutterEngine) setupMethodChannel() return super.application(application, didFinishLaunchingWithOptions: launchOptions) // Receive message from flutter privatae func setupMethodChannel() { let messenger = flutterEngine.binaryMessenger let channel = FlutterMethodChannel(name: "com.sunricher.dancing_with_flutter/channel", binaryMessenger: messenger) channel.setMethodCallHandler{ call, result in switch call.method { case "sendDataToNative": if let args = call.arguments as? [String: Any] { let message = args["message"] as? String ?? "none" NSLog("Received from flutter: \(message)", "") result("iOS response sendDataToNative: \(message)") } else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "Invalid args", details: nil)) } default: result(FlutterMethodNotImplemented) } } } ``` 在 ViewController 中跳转到 FlutterViewController 并发送数据给 Flutter。 关键代码: ``` import Flutter private var flutterViewController: FlutterViewController! private var methodChannel: FlutterMethodChannel! let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil) methodChannel = FlutterMethodChannel(name: "", binaryMessenger: flutterViewController.binaryMessenger) @objc func showFlutter() { present(flutterViewController, animated: true) } @objc func sendDataToFlutter() { let data = [ "name": "WWD", "age": 18, ] as [String: Any] methodChannel.invokeMethod("sendDataToFlutter", arguments: data) { result in if let error = result as? FlutterError { NSLog("sendDataToFlutter result error: \(error.message ?? "unknown error")", "") } else if let response = result as? String { NSLog("sendDataToFlutter response from flutter: \(response)", "") } } } ``` ### Flutter Dart 在 main.dart 中增加与 iOS 通讯的主要代码: ``` import 'package:flutter/services.dart'; class _MyHomePageState extends State { static const platform = MethodChannel('com.sunricher.dancing_with_flutter/channel'); String content = 'No message yet'; @override void initState() { super.initState(); _setupMethodChannel(); } Future _setupMethodChannel() async { platform.setMethodCallHandler((call) async { switch (call.method) { case 'sendDataToFlutter': final Map argsRaw = call.arguments; final Map args = Map.from(argsRaw); setState(() { content = 'Flutter received from iOS: ${args["name"], ${args["age"}'; }) return 'Flutter received data $args'; default: throw PlatformException(code: 'NOT_IMPLEMENTED', message: 'Flutter: method ${call.method} not implemented.'); } }) } Future sendDataToNative() async { try { final result = await platform.invokeMethod('sendDataToNative', {'message': 'hello i am from flutter'}); setState(() { content = result; }) } catch (e) { setState(() { content = 'Failed to send to iOS: $e'; }) } } ... } ``` ## Set up Android project 在 MyApp/app/build.gradle.kts 中添加 ndk 过滤 Flutter 支持的平台。 ```groovy android { //... defaultConfig { ndk { // Filter for architectures supported by Flutter abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86_64") } } } ``` ### Add to an Android App * 选择使用 AAR (Android Archive)的方式,Module source code 尚未跑通。 * Gradle 选择 Groovy 的方式 ``` cd some/path/my_flutter flutter build aar ``` 输出结果: ```text maginawin@Watermelon my_flutter % flutter build aar Running Gradle task 'assembleAarDebug'... 158.4s ✓ Built build/host/outputs/repo Running Gradle task 'assembleAarProfile'... 33.1s ✓ Built build/host/outputs/repo Running Gradle task 'assembleAarRelease'... 30.5s ✓ Built build/host/outputs/repo Consuming the Module 1. Open /app/build.gradle 2. Ensure you have the repositories configured, otherwise add them: String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com" repositories { maven { url '/Users/maginawin/Developer/FlutterProjects/dancing_with_flutter/my_flutter/build/host/outputs/repo' } maven { url "$storageUrl/download.flutter.io" } } 3. Make the host app depend on the Flutter module: dependencies { debugImplementation 'com.example.my_flutter:flutter_debug:1.0' profileImplementation 'com.example.my_flutter:flutter_profile:1.0' releaseImplementation 'com.example.my_flutter:flutter_release:1.0' } 4. Add the `profile` build type: android { buildTypes { profile { initWith debug } } } To learn more, visit https://flutter.dev/to/integrate-android-archive ``` **注意** 上面步骤中让在 /app/build.gradle 中增加 maven 配置,若 Android 项目的依赖存在 settings.gradle 中,则需要按以下步骤增加 maven 配置,其他配置不变。 * storageUrl 一定要放在 pluginManagement 下面; * 在 dependencyResolutionManagement.repositories 中增加 maven { ... } 等,需要在 google() 和 mavenCentral() 之前; * gradle build Android 项目。 ### Show flutter page 参考地址:[https://docs.flutter.cn/add-to-app/android/add-flutter-screen](https://docs.flutter.cn/add-to-app/android/add-flutter-screen) 在 AndroidManifest.xml 中添加 FlutterActivity ```xml ``` > android:theme 可以替换为 AndroidManifest.xml 中一样的主题。 加载 FlutterActivity ``` import io.flutter.embedding.android.FlutterActivity; myButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startActivity( FlutterActivity.createDefaultIntent(currentActivity) ); } }); // 打开自定义路由的页面 myButton.addOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startActivity( FlutterActivity .withNewEngine() .initialRoute("/my_route") .build(currentActivity) ); } }); ``` 使用缓存的 FlutterEngine,提升打开 FlutterActivity 的速度。 创建 MyApplication 并在 AndroidManifest.xml 在 中增加 android:name=".myApplication" ``` public class MyApplication extends Application { public FlutterEngine flutterEngine; @Override public void onCreate() { super.onCreate(); // Instantiate a FlutterEngine. flutterEngine = new FlutterEngine(this); // Start executing Dart code to pre-warm the FlutterEngine. flutterEngine.getDartExecutor().executeDartEntrypoint( DartEntrypoint.createDefault() ); // Cache the FlutterEngine to be used by FlutterActivity. FlutterEngineCache .getInstance() .put("my_engine_id", flutterEngine); } } ``` 此后使用 CachedEngine 打开 FlutterActivity ``` myButton.addOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startActivity( FlutterActivity .withCachedEngine("my_engine_id") .build(currentActivity) ); } }); ``` FlutterEngine 配置初始路由。 ``` public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // Instantiate a FlutterEngine. flutterEngine = new FlutterEngine(this); // Configure an initial route. flutterEngine.getNavigationChannel().setInitialRoute("your/route/here"); // Start executing Dart code to pre-warm the FlutterEngine. flutterEngine.getDartExecutor().executeDartEntrypoint( DartEntrypoint.createDefault() ); // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment. FlutterEngineCache .getInstance() .put("my_engine_id", flutterEngine); } } ``` > 配置透明的 FlutterActivity 等[参考此处](https://docs.flutter.cn/add-to-app/android/add-flutter-screen#add-a-translucent-flutter-screen)。