美文网首页flutter
Flutter 三端分离模式开发(先基于原有iOS项目开发,更新

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

作者: CarrySniper | 来源:发表于2020-12-03 17:30 被阅读0次

    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 <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创建一个实体FlutterEngine

    AppDelegate.h文件内容:

    #import <UIKit/UIKit.h>
    #import <Flutter/Flutter.h>
    
    @interface AppDelegate : FlutterAppDelegate
    
    @property (nonatomic,strong) FlutterEngine *flutterEngine;
    
    @end
    

    AppDelegate.m文件内容:

    #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调用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项目开发,更新

        本文链接:https://www.haomeiwen.com/subject/yoapvktx.html