Flutter 三端分离模式开发(先基于原有iOS项目开发,更新中···)

2020年之前有些教程创建方式已经不适用,要么缺文件、要么原生项目运行报错,折腾几天半个月都不行,还是官方文档教程比较靠谱。

目前原生App和Flutter混合开发有两种模式:

  • 统一管理模式:将原生工程作为Flutter工程的子工程,由Flutter进行统一管理。
  • 三端分离模式:将Flutter工程作为原生工程的子模块,维持原有的原生工程管理方式不变。

一、基于原有iOS项目集成Flutter框架

前提要拥有CocoaPods和Flutter环境配置
Flutter官方文档

1.1、创建项目文件夹

新建总项目文件夹,存放三端项目文件如:carry_sniper

1.2、创建Flutter模块

在总项目文件夹内,创建Flutter模块工程如:flutter_module

cd xxx/carry_sniper 你的文件夹路径
flutter create --template module flutter_module

执行结果:

Creating project flutter_module...
  flutter_module/test/widget_test.dart (created)
  flutter_module/flutter_module.iml (created)
  flutter_module/.gitignore (created)
  flutter_module/.metadata (created)
  flutter_module/pubspec.yaml (created)
  flutter_module/README.md (created)
  flutter_module/lib/main.dart (created)
  flutter_module/flutter_module_android.iml (created)
  flutter_module/.idea/libraries/Dart_SDK.xml (created)
  flutter_module/.idea/modules.xml (created)
  flutter_module/.idea/workspace.xml (created)
Running "flutter pub get" in flutter_module...                      0.8s
Wrote 11 files.

All done!
Your module code is in flutter_module/lib/main.dart.
1.3、创建iOS项目工程

在总项目文件夹,使用Xcode创建iOS项目工程如:CarrySniperiOS

1.4、关联操作
1.4.1为iOS工程添加CocoaPods依赖,生成Proflie文件

终端指令执行:

cd xxx/carry_sniper/CarrySniperiOS 你的iOS工程项目路径
pod init
1.4.2打开Proflie文件添加、修改内容

主要2段3行代码,注意代码存放位置,和flutter_module名称一致

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

配置结果如下:

platform :ios, '11.0'
# 1、Flutter模块加入
flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'CarrySniperiOS' do
  use_frameworks!
  # 2、安装嵌入Flutter模块
  install_all_flutter_pods(flutter_application_path)

  # Pods for CarrySniperiOS

end
1.4.3执行指令,完成Flutter模块的添加和CocoaPods依赖
pod install

执行结果:
要看到Installing Flutter相关依赖的安装,否则运行报错
关闭当前Xcode项目,从此使用CarrySniperiOS.xcworkspac运行工程

/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/universal-darwin19/rbconfig.rb:229: warning: Insecure world writable dir /Users/Macbook/Documents/FlutterSDK/flutter/bin in PATH, mode 040777
Analyzing dependencies
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Installing flutter_module (0.0.1)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `CarrySniperiOS.xcworkspace` for this project from now on.
Pod installation complete! There are 3 dependencies from the Podfile and 3 total pods installed.
1.5、完成基础配置

可以直接打开项目运行,没问题就说明配置成功。部分目录如下:

carry_sniper/
├── CarrySniperiOS/
│   ├── CarrySniperiOS/
│   ├── Pods/
│   ├── Podfile
│   ├── Podfile.lock
│   ├── CarrySniperiOS.xcodeproj
│   └── CarrySniperiOS.xcworkspace
├── flutter_module/
│   ├── .android/
│   ├── .ios/
│   │    ├── Runner.xcworkspace
│   │    └── Flutter/podhelper.rb
│   ├── lib/
│       └── main.dart
│   ├── flutter_module_android.iml
│   ├── flutter_module.iml
│   ├── pubspec.lock
│   ├── test/
│   └── pubspec.yaml

二、原生iOS项目调用Flutter

根据官方Flutter文档,进行简单的原生app调用Flutter,列举2种方法,这里是Objective-C代码,文档有Swift代码。更高级的调用方法可以继续看文档。

2.1方式一:使用FlutterViewController

直接在ViewController.m文件编写代码,运行项目即可:

#import "ViewController.h"
#import 

@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self action:@selector(showFlutter)
     forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
    button.backgroundColor = UIColor.blueColor;
    button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
    [self.view addSubview:button];
}

- (void)showFlutter {
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
    [self presentViewController:flutterViewController animated:YES completion:nil];
}

@end
2.2、方式二:使用FlutterEngine
2.1.1、依赖FlutterAppDelegate创建一个实体FlutterEngine

AppDelegate.h文件内容:

#import 
#import 

@interface AppDelegate : FlutterAppDelegate

@property (nonatomic,strong) FlutterEngine *flutterEngine;

@end

AppDelegate.m文件内容:

#import "AppDelegate.h"
#import 

@interface AppDelegate ()
@end

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
    // Runs the default Dart entrypoint with a default Flutter route.
    [self.flutterEngine run];
    // Used to connect plugins (only if you have plugins with iOS platform code).
    [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
2.2.2、使用FlutterEngine调用Flutter页面和传参

ViewController.m文件内容:

#import "ViewController.h"
#import "AppDelegate.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    // Make a button to call the showFlutter function when pressed.
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self action:@selector(showFlutter)
     forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
    button.backgroundColor = UIColor.blueColor;
    button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
    [self.view addSubview:button];
}

- (void)showFlutter {
    FlutterEngine *flutterEngine =
    ((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
    FlutterViewController *flutterViewController =
    [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self presentViewController:flutterViewController animated:YES completion:nil];
}

@end
2.2.3、运行Xcode中的CarrySniperiOS项目

三、原生iOS项目也能使用热更新、热重载

官方文档:
要先安装homebrew,运行Xcode项目,保证原生App有安装到手机/模拟器上,然后在Android Studio的Flutter项目跟路径执行指令:

flutter attach

如果有多个设备(或者输入下标选择相应设备):

flutter attach -d xxxxx设备id

如果有多个包名:

那么需要统一Android和iOS的包名后再试一遍

如果出现Waiting for a connection from Flutter on iPhone ...一直等待,

说明原生App没有启动,控制台连接不到设备应用运行。
需要手动到手机/模拟器点击运行App即可(只需运行Android Studio,不需要打开Xcode软件,Xcode会在Flutter下默默运行。当然不打开Xcode,就看不到控制台打印输出,但影响不大)。

执行结果:

Waiting for iPhone 12 Pro to report its views...                     7ms
Syncing files to device iPhone 12 Pro...                                
 8,140ms (!)                                       

Flutter run key commands.
r Hot reload. 
R Hot restart.
h Repeat this help message.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
An Observatory debugger and profiler on iPhone 12 Pro is available at: http://127.0.0.1:51810/ucfjPzF2_sY=/

四、原生iOS项目后续可能遇到的问题

  • 如果发现运行App进度和Flutter开发进度不一致,需要去Xcode运行原生项目App。需要保证打包的代码是最新的,否则安装的App再次启动后,永远是之前的版本,没有包含最新Flutter部分的代码。
个人理解:已安装的App,我们使用饭flutter attach,热更新和热重载只保证运行时代码是最新的,运行结束之后就恢复原生App安装时的模样,并没有把最新的Flutter代码打包到安装包里面。
  • Showing Recent Messages Undefined symbol: protocol conformance descriptor fo xxx 等几十上百个报错
莫名其妙的出现,之前运行还好好的,可能Flutter添加了某些package,在原生项目就突然出问题了。
可能解决方法:
尝试一:升级CocoaPods;
尝试二:更新依赖库 pod install --verbose --no-repo-update
尝试三:为原生项目添加swift桥接文件,任意直接New一个.swift文件,Xcode 提示 Create Bridging Header ,选择创建即可。记得.swift文件保留不删除。
  • Command PhaseScriptExecution failed with a nonzero exit code
  • /packages/flutter_tools/bin/xcode_backend.sh: No such file or directory
先检查Flutter SDK里面存不存在xcode_backend.sh文件,不存在就去找一个或者重新下载sdk;
存在的话,可能就是Xcode项目配置可能缺少FLUTTER_ROOT,Target -> Build Setting -> User-Defined 添加FLUTTER_ROOT对应sdk路径;
如果不知道路径可以直接复制flutter项目的.ios的Generated.xcconfig里面的FLUTTER_ROOT内容,会自动帮导入到User-Defined。
  • Support for empty structs is deprecated and will be removed in the next stable version of Dart. Use Opaque instead.
遇到SDK版本更新,API过期更替。不影响使用,需要等待第三方插件更新支持最新包。可以用'flutter downgrade'指令降级SDK。

五、一些想法

三端分离,flutter里面iOS的info.plist文件很容易在安卓合并代码或pub get指令执行时会被重置,影响开发和调试。当然原生项目的info.plist文件是不影响的,只是不想开发期间直接用原生项目,效率不一样。

你可能感兴趣的:(Flutter 三端分离模式开发(先基于原有iOS项目开发,更新中···))