Flutter 三端分離模式開發(fā)(先基于原有iOS項目開發(fā),更新中···)

2020年之前有些教程創(chuàng)建方式已經(jīng)不適用,要么缺文件、要么原生項目運行報錯,折騰幾天半個月都不行,還是官方文檔教程比較靠譜。

目前原生App和Flutter混合開發(fā)有兩種模式:

  • 統(tǒng)一管理模式:將原生工程作為Flutter工程的子工程,由Flutter進行統(tǒng)一管理。
  • 三端分離模式:將Flutter工程作為原生工程的子模塊,維持原有的原生工程管理方式不變。

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

前提要擁有CocoaPods和Flutter環(huán)境配置
Flutter官方文檔

1.1、創(chuàng)建項目文件夾

新建總項目文件夾,存放三端項目文件如:carry_sniper

1.2、創(chuàng)建Flutter模塊

在總項目文件夾內(nèi),創(chuàng)建Flutter模塊工程如:flutter_module

cd xxx/carry_sniper 你的文件夾路徑
flutter create --template module flutter_module

執(zhí)行結(jié)果:

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、創(chuàng)建iOS項目工程

在總項目文件夾,使用Xcode創(chuàng)建iOS項目工程如:CarrySniperiOS

1.4、關(guān)聯(lián)操作
1.4.1為iOS工程添加CocoaPods依賴,生成Proflie文件

終端指令執(zhí)行:

cd xxx/carry_sniper/CarrySniperiOS 你的iOS工程項目路徑
pod init
1.4.2打開Proflie文件添加、修改內(nèi)容

主要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)

配置結(jié)果如下:

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執(zhí)行指令,完成Flutter模塊的添加和CocoaPods依賴
pod install

執(zhí)行結(jié)果:
要看到Installing Flutter相關(guān)依賴的安裝,否則運行報錯
關(guān)閉當(dāng)前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、完成基礎(chǔ)配置

可以直接打開項目運行,沒問題就說明配置成功。部分目錄如下:

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項目調(diào)用Flutter

根據(jù)官方Flutter文檔,進行簡單的原生app調(diào)用Flutter,列舉2種方法,這里是Objective-C代碼,文檔有Swift代碼。更高級的調(diào)用方法可以繼續(xù)看文檔。

2.1方式一:使用FlutterViewController

直接在ViewController.m文件編寫代碼,運行項目即可:

#import "ViewController.h"
#import <Flutter/Flutter.h>

@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創(chuàng)建一個實體FlutterEngine

AppDelegate.h文件內(nèi)容:

#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>

@interface AppDelegate : FlutterAppDelegate

@property (nonatomic,strong) FlutterEngine *flutterEngine;

@end

AppDelegate.m文件內(nèi)容:

#import "AppDelegate.h"
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>

@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調(diào)用Flutter頁面和傳參

ViewController.m文件內(nèi)容:

#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項目跟路徑執(zhí)行指令:

flutter attach

如果有多個設(shè)備(或者輸入下標選擇相應(yīng)設(shè)備):

flutter attach -d xxxxx設(shè)備id

如果有多個包名:

那么需要統(tǒng)一Android和iOS的包名后再試一遍

如果出現(xiàn)Waiting for a connection from Flutter on iPhone ...一直等待,

說明原生App沒有啟動,控制臺連接不到設(shè)備應(yīng)用運行。
需要手動到手機/模擬器點擊運行App即可(只需運行Android Studio,不需要打開Xcode軟件,Xcode會在Flutter下默默運行。當(dāng)然不打開Xcode,就看不到控制臺打印輸出,但影響不大)。

執(zhí)行結(jié)果:

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項目后續(xù)可能遇到的問題

  • 如果發(fā)現(xiàn)運行App進度和Flutter開發(fā)進度不一致,需要去Xcode運行原生項目App。需要保證打包的代碼是最新的,否則安裝的App再次啟動后,永遠是之前的版本,沒有包含最新Flutter部分的代碼。
個人理解:已安裝的App,我們使用飯flutter attach,熱更新和熱重載只保證運行時代碼是最新的,運行結(jié)束之后就恢復(fù)原生App安裝時的模樣,并沒有把最新的Flutter代碼打包到安裝包里面。
  • Showing Recent Messages Undefined symbol: protocol conformance descriptor fo xxx 等幾十上百個報錯
莫名其妙的出現(xiàn),之前運行還好好的,可能Flutter添加了某些package,在原生項目就突然出問題了。
可能解決方法:
嘗試一:升級CocoaPods;
嘗試二:更新依賴庫 pod install --verbose --no-repo-update
嘗試三:為原生項目添加swift橋接文件,任意直接New一個.swift文件,Xcode 提示 Create Bridging Header ,選擇創(chuàng)建即可。記得.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對應(yīng)sdk路徑;
如果不知道路徑可以直接復(fù)制flutter項目的.ios的Generated.xcconfig里面的FLUTTER_ROOT內(nèi)容,會自動幫導(dǎo)入到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指令執(zhí)行時會被重置,影響開發(fā)和調(diào)試。當(dāng)然原生項目的info.plist文件是不影響的,只是不想開發(fā)期間直接用原生項目,效率不一樣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容