问题
Flutter已经玩了些日子,但是老项目怎么集成Flutter呢?
集成了怎么热重载?
Flutter和原生安卓、iOS是怎么关联的呢?
Flutter和原生是怎么交互?
iOS老项目集成Flutter流程:
1、flutter环境安装 flutter中文网,特别详细,跟着来就行。
2、确保环境配置正确,全部都是小对号
终端执行
flutter doctor -v
如下是打印结果:
houjianan:FlutterMixed> flutter doctor -v
[✓] Flutter (Channel master, v0.10.2-pre.119, on Mac OS X 10.13.4 17E202, locale zh-Hans-CN)
• Flutter version 0.10.2-pre.119 at /Users/houjianan/flutter
• Framework revision 50098f149d (2 days ago), 2018-10-30 22:54:49 -0400
• Engine revision 3a67757300
• Dart version 2.1.0 (build 2.1.0-dev.8.0 bf26f760b1)
[✓] Android toolchain - develop for Android devices (Android SDK 28.0.3)
• Android SDK at /Users/houjianan/Library/Android/sdk
• Android NDK at /Users/houjianan/Library/Android/sdk/ndk-bundle
• Platform android-28, build-tools 28.0.3
• Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06)
• All Android licenses accepted.
[✓] iOS toolchain - develop for iOS devices (Xcode 9.4.1)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 9.4.1, Build version 9F2000
• ios-deploy 1.9.2
• CocoaPods version 1.5.3
[✓] Android Studio (version 3.2)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin version 29.1.1
• Dart plugin version 181.5656
• Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06)
[✓] IntelliJ IDEA Ultimate Edition (version 2018.1.1)
• IntelliJ at /Applications/IntelliJ IDEA.app
• Flutter plugin version 27.1.2
• Dart plugin version 181.4445.29
[✓] Connected device (6 available)
• “houjianan”的 iPhone • ffba6dc829885833aab8cf53b275216ee9949c05 • ios • iOS 12.0.1
• No issues found!
环境配置好了 就可以创建一个iOS项目或者在老项目上操作
3、iOS工程Enable Bitcode 需要关闭,因为Flutter混合开发不支持Bitcode
4、flutter module创建
a、切换分支flutter channel master
houjianan:Desktop> cd /Users/houjianan/flutter
houjianan:flutter> flutter channel master
╔════════════════════════════════════════════════════════════════════════════╗
║ A new version of Flutter is available! ║
║ ║
║ To update to the latest version, run "flutter upgrade". ║
╚════════════════════════════════════════════════════════════════════════════╝
Switching to flutter channel 'master'...
git: Already on 'master'
git: Your branch is behind 'origin/master' by 19 commits, and can be fast-forwarded.
git: (use "git pull" to update your local branch)
houjianan:flutter>
拉新代码
houjianan:flutter> git pull
b、创建flutter create -t module flutter_module
cd 进入的路径是混编项目的文件夹(里面报错iOS项目和马上要创建的flutter_module)
houjianan:fluttermixed> cd /Users/houjianan/Desktop/fluttermixed
houjianan:fluttermixed> ls
FlutterMixed
houjianan:fluttermixed> flutter create -t module flutter_module
Creating project flutter_module...
flutter_module/.gitignore (created)
flutter_module/.idea/libraries/Dart_SDK.xml (created)
flutter_module/.idea/libraries/Flutter_for_Android.xml (created)
flutter_module/.idea/modules.xml (created)
flutter_module/.idea/workspace.xml (created)
flutter_module/.metadata (created)
flutter_module/lib/main.dart (created)
flutter_module/flutter_module.iml (created)
flutter_module/flutter_module_android.iml (created)
flutter_module/pubspec.yaml (created)
flutter_module/README.md (created)
flutter_module/test/widget_test.dart (created)
Running "flutter packages get" in flutter_module... 21.9s
Wrote 12 files.
All done!
Your module code is in flutter_module/lib/main.dart.
houjianan:fluttermixed> ls
FlutterMixed flutter_module
houjianan:fluttermixed> cd flutter_module/
houjianan:flutter_module> ls -la
total 88
drwxr-xr-x 15 houjianan staff 510 11 2 10:38 .
drwxr-xr-x 5 houjianan staff 170 11 2 10:37 ..
drwxr-xr-x 12 houjianan staff 408 11 2 10:38 .android
-rw-r--r-- 1 houjianan staff 339 11 2 10:37 .gitignore
drwxr-xr-x 5 houjianan staff 170 11 2 10:37 .idea
drwxr-xr-x 7 houjianan staff 238 11 2 10:38 .ios
-rw-r--r-- 1 houjianan staff 308 11 2 10:37 .metadata
-rw-r--r-- 1 houjianan staff 4983 11 2 10:38 .packages
-rw-r--r-- 1 houjianan staff 162 11 2 10:37 [README.md](http://readme.md/)
-rw-r--r-- 1 houjianan staff 896 11 2 10:37 flutter_module.iml
-rw-r--r-- 1 houjianan staff 1465 11 2 10:37 flutter_module_android.iml
drwxr-xr-x 3 houjianan staff 102 11 2 10:37 lib
-rw-r--r-- 1 houjianan staff 8556 11 2 10:38 pubspec.lock
-rw-r--r-- 1 houjianan staff 370 11 2 10:37 pubspec.yaml
drwxr-xr-x 3 houjianan staff 102 11 2 10:37 test
houjianan:flutter_module>
创建完之后 如下图
注意:查看隐藏文件快捷键 shift+command+。
创建iOS项目的Config文件(管理Xcode工程的配置衔接文件) 里面包含分别创建 Flutter.xcconfig、Debug.xcconfig、Release.xcconfig 三个配置文件;其中Flutter.xcconfig 是指向外目录flutter module的Generated.xcconfig 文件路径引用文件,其他两个代表Xcode的环境配置文件。
使用了cocoapods 在Debug和Release里面添加
注意FlutterMixed 是自己iOS项目的名称
#include "Pods/Target Support Files/Pods-FlutterMixed/Pods-FlutterMixed.debug.xcconfig"
#include "Pods/Target Support Files/Pods-FlutterMixed/Pods-FlutterMixed.release.xcconfig"
Flutter.xcconfig
//
// Flutter.xcconfig
// FlutterMixed
//
// Created by 侯佳男 on 2018/11/2.
// Copyright © 2018年 侯佳男. All rights reserved.
//
#include "../../flutter_module/.ios/Flutter/Generated.xcconfig"
ENABLE_BITCODE=NO
Debug.xcconfig
//
// Debug.xcconfig
// FlutterMixed
//
// Created by 侯佳男 on 2018/11/2.
// Copyright © 2018年 侯佳男. All rights reserved.
//
#include "Flutter.xcconfig"
#include "Pods/Target Support Files/Pods-FlutterMixed/Pods-FlutterMixed.debug.xcconfig"
Release.xcconfig
//
// Release.xcconfig
// FlutterMixed
//
// Created by 侯佳男 on 2018/11/2.
// Copyright © 2018年 侯佳男. All rights reserved.
//
#include "Flutter.xcconfig"
#include "Pods/Target Support Files/Pods-FlutterMixed/Pods-FlutterMixed.debug.xcconfig"
FLUTTER_BUILD_MODE=release
Xcode project环境配置选择
添加脚本
注意:之前贴的脚本 格式不正确 如果出现以下错误 更改脚本文件格式即可
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
注意: Run Script 在Target Dependencies或者[CP]Check pods Manifest.lock后面
添加好了之后run下项目,就会执行脚本,iOS工程文件下会有一个Flutter文件夹(第一次搞是这么回事儿,有时候就是不会生成,具体为啥不清楚,有知道的请留言告诉我,感谢!)。
run之后如果没有Flutter这个文件夹,手动创建一个,然后把flutter_module->.ios->Fluter里面的App.framework、engine、flutter_assets添加进刚才手动创建的Flutter文件夹内
项目里面文件夹的颜色不同。
蓝色文件夹是选择了Create folder references
黄色文件夹是选择了Create groups
Xcode run或者build之后flutter_assets才会出现
flutter_assets一定要蓝色的 不然flutter界面啥都看不见。
用Android Studio运行项目
把main.dart文件里面的内容全部替换为如下代码
直接贴代码可能会出现空格丢失的情况
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or press Run > Flutter Hot Reload in a Flutter IDE). Notice that the
// counter didn't reset back to zero; the application is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// 创建一个给native的channel (类似iOS的通知)
static const methodChannel = const MethodChannel('com.pages.your/native_get');
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
print('flutter的log打印:现在输出count=$_counter');
// 当个数累积到3的时候给客户端发参数
if(_counter == 3) {
_toNativeSomethingAndGetInfo();
}
// 当个数累积到5的时候给客户端发参数
if(_counter == 1002) {
Map<String, String> map = { "title": "这是一条来自flutter的参数" };
methodChannel.invokeMethod('toNativePush',map);
}
// 当个数累积到8的时候给客户端发参数
if(_counter == 1005) {
Map<String, dynamic> map = { "content": "flutterPop回来","data":[1,2,3,4,5]};
methodChannel.invokeMethod('toNativePop',map);
}
});
}
// 给客户端发送一些东东 , 并且拿到一些东东
Future<Null> _toNativeSomethingAndGetInfo() async {
dynamic result;
try {
result = await methodChannel.invokeMethod('toNativeSomething','大佬你点击了$_counter下');
} on PlatformException {
result = 100000;
}
setState(() {
// 类型判断
if (result is int) {
_counter = result;
}
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
// appBar: new AppBar(
// // Here we take the value from the MyHomePage object that was created by
// // the App.build method, and use it to set our appbar title.
// title: new Text(widget.title),
// ),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'he button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
OC-ViewController.m
//
// ViewController.m
// FlutterMixed
//
// Created by 侯佳男 on 2018/11/2.
// Copyright © 2018年 侯佳男. All rights reserved.
//
#import "ViewController.h"
#import <Flutter/Flutter.h>
@interfaceViewController()
@end
@implementationViewController
- (void)viewDidLoad {
[superviewDidLoad];
}
- (void)didReceiveMemoryWarning {
[superdidReceiveMemoryWarning];
}
- (void)pushFlutterViewController {
FlutterViewController* flutterViewController = [[FlutterViewControlleralloc] initWithProject:nilnibName:nilbundle:nil];
flutterViewController.navigationItem.title= @"Flutter Demo";
__weak__typeof(self) weakSelf = self;
// 要与main.dart中一致
NSString*channelName = @"com.pages.your/native_get";
FlutterMethodChannel*messageChannel = [FlutterMethodChannelmethodChannelWithName:channelName binaryMessenger:flutterViewController];
[messageChannel setMethodCallHandler:^(FlutterMethodCall* _Nonnullcall, FlutterResult _Nonnullresult) {
// call.method 获取 flutter 给回到的方法名,要匹配到 channelName 对应的多个 发送方法名,一般需要判断区分
// call.arguments 获取到 flutter 给到的参数,(比如跳转到另一个页面所需要参数)
// result 是给flutter的回调, 该回调只能使用一次
NSLog(@"flutter 给到我:\nmethod=%@ \narguments = %@",call.method,call.arguments);
if([call.methodisEqualToString:@"toNativeSomething"]) {
UIAlertView*alertView = [[UIAlertViewalloc] initWithTitle:@"flutter回调"message:[NSStringstringWithFormat:@"%@",call.arguments] delegate:selfcancelButtonTitle:@"确定"otherButtonTitles:nil];
[alertView show];
// 回调给flutter
if(result) {
result(@1000);
}
} elseif([call.methodisEqualToString:@"toNativePush"]) {
// ThirdViewController *testVC = [[ThirdViewController alloc] init];
// testVC.parames = call.arguments;
// [weakSelf.navigationController pushViewController:testVC animated:YES];
} elseif([call.methodisEqualToString:@"toNativePop"]) {
[weakSelf.navigationControllerpopViewControllerAnimated:YES];
}
}];
// [self.navigationController pushViewController:flutterViewController animated:YES];
[selfpresentViewController:flutterViewController animated:YEScompletion:nil];
}
-(void)touchesBegan:(NSSet<UITouch*> *)touches withEvent:(UIEvent*)event{
[selfpushFlutterViewController];
}
@end
OC-AppDelegate.h
//
// AppDelegate.h
// FlutterMixed
//
// Created by 侯佳男 on 2018/11/2.
// Copyright © 2018年 侯佳男. All rights reserved.
//
#import <Flutter/Flutter.h>
@interfaceAppDelegate : FlutterAppDelegate<UIApplicationDelegate, FlutterAppLifeCycleProvider>
@end
OC-AppDelegate.m
//
// AppDelegate.h
// FlutterMixed
//
// Created by 侯佳男 on 2018/11/2.
// Copyright © 2018年 侯佳男. All rights reserved.
//
#import "AppDelegate.h"
@interfaceAppDelegate()
@end
@implementationAppDelegate
{
FlutterPluginAppLifeCycleDelegate*_lifeCycleDelegate;
}
- (instancetype)init {
if(self= [superinit]) {
_lifeCycleDelegate= [[FlutterPluginAppLifeCycleDelegatealloc] init];
}
returnself;
}
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
return[_lifeCycleDelegateapplication:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
[_lifeCycleDelegateapplicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication*)application {
[_lifeCycleDelegateapplicationWillEnterForeground:application];
}
- (void)applicationWillResignActive:(UIApplication*)application {
[_lifeCycleDelegateapplicationWillResignActive:application];
}
- (void)applicationDidBecomeActive:(UIApplication*)application {
[_lifeCycleDelegateapplicationDidBecomeActive:application];
}
- (void)applicationWillTerminate:(UIApplication*)application {
[_lifeCycleDelegateapplicationWillTerminate:application];
}
- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
[_lifeCycleDelegateapplication:application
didRegisterUserNotificationSettings:notificationSettings];
}
- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
[_lifeCycleDelegateapplication:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void(^)(UIBackgroundFetchResultresult))completionHandler {
[_lifeCycleDelegateapplication:application
didReceiveRemoteNotification:userInfo
fetchCompletionHandler:completionHandler];
}
- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
return[_lifeCycleDelegateapplication:application openURL:url options:options];
}
-(BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
return[_lifeCycleDelegateapplication:application handleOpenURL:url];
}
-(BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
sourceApplication:(NSString*)sourceApplication
annotation:(id)annotation {
return[_lifeCycleDelegateapplication:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation];
}
- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
completionHandler:(void(^)(BOOLsucceeded))completionHandler NS_AVAILABLE_IOS(9_0) {
[_lifeCycleDelegateapplication:application
performActionForShortcutItem:shortcutItem
completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnullNSString*)identifier
completionHandler:(nonnullvoid(^)(void))completionHandler {
[_lifeCycleDelegateapplication:application
handleEventsForBackgroundURLSession:identifier
completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void(^)(UIBackgroundFetchResultresult))completionHandler {
[_lifeCycleDelegateapplication:application performFetchWithCompletionHandler:completionHandler];
}
- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate {
[_lifeCycleDelegateaddDelegate:delegate];
}
#pragma mark - Flutter
// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {
UIViewController* viewController = [UIApplicationsharedApplication].keyWindow.rootViewController;
if([viewController isKindOfClass:[FlutterViewControllerclass]]) {
return(FlutterViewController*)viewController;
}
returnnil;
}
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
[supertouchesBegan:touches withEvent:event];
// Pass status bar taps to key window Flutter rootViewController.
if(self.rootFlutterViewController!= nil) {
[self.rootFlutterViewControllerhandleStatusBarTouches:event];
}
}
@end
如果没出现跳转或者返回 iOS项目是否给ViewController加NavigationController flutter界面是push出来的还是present出来的
贴完代码运行Xcode 点击屏幕 跳转flutter界面。
更改flutter界面代码 再运行Xcode 点击屏幕 跳转flutter界面,界面被更改。
热重载具体内容看闲鱼相关文章,多人写作开发看闲鱼相关文章,可以关注闲鱼公众号,他们定期更新超优质Flutter相关文章。s
中文文档
混编详细一篇
混编详细一篇
闲鱼一篇
官网
Flutter TIPS
Flutter和iOS交互
Adding a Flutter screen to an iOS app
下载新的项目demo
1、 Native跳转到不同的flutter界面
2、 Native传值给flutter
3、 Flutter传值给Native
项目地址
2020.1.20
使用pod接入flutter
cd 到你要混编的项目(YYFramework)同一个路径下 ,执行如下:
flutter create -t module flutter_yyframework
Podfile 文件
#注意路径和文件夹名字正确无误 最后有一个反斜杠
flutter_application_path = '/Users/houjianan/Documents/GitHub/iOS/flutter_yyframework/'
load File.join(flutter_application_path, 'YYFramework', 'Flutter', 'podhelper.rb')
target 'YYFramework' do
install_all_flutter_pods(flutter_application_path)
end
注:YYFramework 是iOS项目的文件名
添加好之后
pod install
注意,如下错误:[!] InvalidPodfilefile: No such file or directory @ rb_sysopen - ./flutter_yyframework/.ios/Flutter/podhelper.rb.
需要在flutter_yyframework文件夹下执行以下命令,把.ios和.android等flutter配置生成出来。(打开模拟器。链接真机都可以。)
open -a Simulator
flutter run
注意,如下错误是因为路径不对。
[!] Invalid `Podfile` file: cannot load such file -- path/to/flutter_yyframework/.ios/Flutter/podhelper.rb.
# from /Users/houjianan/Documents/GitHub/iOS/YYFramework/Podfile:7
# -------------------------------------------
# flutter_application_path = 'path/to/flutter_yyframework/'
> load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
#
# -------------------------------------------
houjianan:YYFramework> pod install
Analyzing dependencies
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Installing flutter_yyframework (0.0.1)
Generating Pods project
Integrating client project
Pod installation complete! There are 42 dependencies from the Podfile and 51 total pods installed.
houjianan:YYFramework>