flutter_boost学习3:iOSNative添加与flutter交互调用

上一篇:flutter_boost学习2:iOS集成flutter_boost

前言

在上一篇集成了flutter_boost后,还缺少flutter调用原生的方法,以及原生调用flutter的相互交互,这一块只要根据flutter提供的文档就能很好的接入使用

练习GitHub:HybridFlutter

一、原生设置相关内容可供flutter调用

1、iOS原生设置可供flutter调用
2、iOS原生调用flutter的channel
完整代码如下:

//
//  MyFlutterRouter.m
//  HybridiOS
//
//  Created by loneylyflow on 15/05/2019.
//  Copyright © 2019 Lonely traveller. All rights reserved.
//

#import "MyFlutterRouter.h"
#import "MyFlutterViewController.h"
#import <UIKit/UIKit.h>
#import "UINavigationController+FDFullscreenPopGesture.h"

@interface MyFlutterRouter() <FlutterStreamHandler>
@property(nonatomic, copy) FlutterEventSink nativeCallFlutterEventSink;
@property(nonatomic, strong) UINavigationController *navigationController;
@end

@implementation MyFlutterRouter
  
+ (instancetype)sharedRouter
{
    static MyFlutterRouter *_instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[MyFlutterRouter alloc] init];
        // 在这里初始化FlutterViewController
        // 初始化Router
        [FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:_instance onStart:^(FlutterViewController * fvc){
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.001 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                MyFlutterRouter.sharedRouter.fvc = fvc;
               
            });
        }];
    });
    return _instance;
}
- (void)setFvc:(FlutterViewController *)fvc
{
    _fvc = fvc;
    if(fvc != nil){
        [self setupFlutterCallNative];
        [self setupNativeCallFlutter];
    }
    
}
#pragma mark - FlutterCallNative
- (void)setupFlutterCallNative
{
    // 用于Flutter 调用 Native
    // 这个channelname必须与Native里接收的一致
    FlutterMethodChannel *flutterCallNativeChannel = [FlutterMethodChannel methodChannelWithName:@"samples.flutter.io/flutterCallNative" binaryMessenger:self.fvc];
    
    [flutterCallNativeChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) {
        NSLog(@"method: %@",call.method);
        NSLog(@"arguments: %@",call.arguments);
        if([@"getBatteryLevel" isEqualToString:call.method]){
//            result([FlutterError errorWithCode:@"UNAVAILABLE"
//                                       message:@"iOS ====== Battery info unavailable2222"
//                                       details:nil]);
            result(@"Battery info iOS 1234567");
        }else if([@"updateUserInfo" isEqualToString:call.method]){
            NSDictionary *params = call.arguments;
            NSInteger userId = [params[@"id"] integerValue];
            NSString *name = params[@"name"];
            result(@{@"id":@1000,
                     @"name":@"story",
                     @"fromId":@(userId),
                     @"fromName":name
                     });
        }else if([@"nativeCallFlutter" isEqualToString:call.method]){
            //batteryChannel cal
        }else if([@"isShowNav" isEqualToString:call.method]){
           result(@(! self.navigationController.childViewControllers.lastObject.fd_prefersNavigationBarHidden));
            
        }else if([@"hideNav" isEqualToString:call.method]){
            self.navigationController.childViewControllers.lastObject.fd_prefersNavigationBarHidden = YES;
            BOOL animited = [call.arguments boolValue];
            [self.navigationController setNavigationBarHidden:YES animated:animited];
            result(@(NO));
        }else if([@"showNav" isEqualToString:call.method]){
            self.navigationController.childViewControllers.lastObject.fd_prefersNavigationBarHidden = NO;
            BOOL animited = [call.arguments boolValue];
            [self.navigationController setNavigationBarHidden:NO animated:animited];
            result(@(YES));
        }
        else{
            result(FlutterMethodNotImplemented);
        }
        
    }];
}
#pragma mark - NativeCallFlutter
- (void)setupNativeCallFlutter
{
    // 用于Native调用Flutter
    FlutterEventChannel *nativeCallFlutterChannel = [FlutterEventChannel eventChannelWithName:@"samples.flutter.io/nativeCallFlutter" binaryMessenger:self.fvc];
    [nativeCallFlutterChannel setStreamHandler:self];
}
- (FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)events
{
    self.nativeCallFlutterEventSink = events;
//    static NSInteger mm = 0;
//    [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
//        if (!_eventSink) return;
//        NSString *txt = [NSString stringWithFormat:@"    iOS倒计时 %ld秒",++mm];
//        _eventSink(txt);
//    }];
    return nil;
}
- (FlutterError*)onCancelWithArguments:(id)arguments {
    //[[NSNotificationCenter defaultCenter] removeObserver:self];
    self.nativeCallFlutterEventSink = nil;
    return nil;
}
- (void)callFluterWithName:(NSString *)name params:(id)params
{
    if(self.nativeCallFlutterEventSink){
        self.nativeCallFlutterEventSink(@{@"name":name?:@"",@"params":params ?: [NSNull null]});
    }else{
        WS(weakSelf)
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [weakSelf callFluterWithName:name params:params];
        });
    }
}
#pragma mark - push/pop/close
- (void)openPage:(NSString *)name params:(NSDictionary *)params animated:(BOOL)animated completion:(void (^)(BOOL))completion
{
        if([params[@"present"] boolValue]){
            MyFlutterViewController *vc = MyFlutterViewController.new;
            [vc setName:name params:params];
            vc.hidesBottomBarWhenPushed = YES;
            [self.navigationController presentViewController:vc animated:animated completion:^{}];
        }else{
            MyFlutterViewController *vc = MyFlutterViewController.new;
            vc.hidesBottomBarWhenPushed = YES;
            [vc setName:name params:params];
            [self.navigationController pushViewController:vc animated:animated];
        }
    }
    
    
- (void)closePage:(NSString *)uid animated:(BOOL)animated params:(NSDictionary *)params completion:(void (^)(BOOL))completion
{
    FLBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController;
    if([vc isKindOfClass:FLBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: uid]){
        [vc dismissViewControllerAnimated:animated completion:^{}];
    }else{
        [self.navigationController popViewControllerAnimated:animated];
    }
}
- (UINavigationController *)navigationController
{
    UITabBarController *tabVC = (UITabBarController *)[UIApplication sharedApplication].delegate.window.rootViewController;
    if([tabVC isKindOfClass:[UITabBarController class]]){
        UINavigationController *nav = (UINavigationController *)tabVC.selectedViewController;
        if([nav isKindOfClass:[UINavigationController class]]){
            return nav;
        }else{
            return [[UINavigationController alloc] init];
        }
    }
    return [[UINavigationController alloc] init];
}
@end

二、flutter设置相关内容可调用Native及被Native调用

typedef void NativeCallBack(Object event);
Map<String,NativeCallBack> callbakcs = {};

 const EventChannel eventChannel = const EventChannel("samples.flutter.io/nativeCallFlutter");
 const MethodChannel platform = const MethodChannel("samples.flutter.io/flutterCallNative");


void main() {
  runApp(MyApp());
}


class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  void _onEvent(Object event)
  {
    if(event is Map){
      String name = event['name'];
      if(callbakcs[name] != null){
        callbakcs[name](event);
      }
    }

  }

  void _onError(Object error)
  {
    setState(() {
     // _changingStatus = "${error}";
    });
  }
  @override
  void initState() {
    // 接收Native调用
    eventChannel.receiveBroadcastStream().listen(_onEvent,onError:_onError);
    super.initState();

    FlutterBoost.singleton.registerPageBuilders({
      'first': (pageName, params, _) => FirstRouteWidget(),
      'second': (pageName, params, _) => SecondRouteWidget(),
      'tab': (pageName, params, _) => TabRouteWidget(),
      'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params),

      ///可以在native层通过 getContainerParams 来传递参数
      'flutterPage': (pageName, params, _) {
        print("flutterPage params:$params");

        return FlutterRouteWidget();
      },
    });
    FlutterBoost.handleOnStartPage();
  }
}

具体调用:

import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'main.dart';
import 'package:flutter/services.dart';

class FirstRouteWidget extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
    return new _FirstRouteWidgetState();
  }
}
class _FirstRouteWidgetState extends State<FirstRouteWidget>{

  String _changingStatus = "init...";
 String _batteryLevel = "leve";
 String _userInfo = "";
 bool _isShowingNav = false;

  @override
  void initState() {
    // 接收Native调用
    //callbakcs["isShowNavBar"]= _responseNative;

    super.initState();
//    _getIsNavShow();
  }
//  void _responseNative(Object event){
//      setState(() {
//        _changingStatus  = "${event}";
//      });
//  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Route'),
        leading: IconButton(icon: Icon(Icons.arrow_back),
          onPressed:(){
            FlutterBoost.singleton.closePageForContext(context);
          } ,
        ),
      ),
      body: Column(
        children: [
          new RaisedButton(
          child: Text('Flutter call Native'),
          onPressed: _getBatteryLevel
        ),
        new Text(_batteryLevel),
        new RaisedButton(
        child: Text('Flutter call updateUserInfo'),
        onPressed: _updateUserInfo
      ),
          new Text(_userInfo),
          new RaisedButton(
              child: Text(_isShowingNav ? 'hideNav222' : "showNav222"),
              onPressed: _showOrHideNav
          ),
          new RaisedButton(
              child: Text('hideNav'),
              onPressed: (){
                platform.invokeMethod("hideNav");
              }
          ),

          new RaisedButton(
          child: Text('Open second 3333'),
          onPressed: () {
            FlutterBoost.singleton.openPage("second", {}, animated: true);
          },
        ),
        new Text(_changingStatus)
      ]
      ),
    );
  }


  Future<Null> _getIsNavShow() async
  {

    bool result = false;
    try{
      result = await platform.invokeMethod("isShowNav");
    } on PlatformException catch(e) {
      result = false;
    }
//    Scaffold
//        .of(context)
//        .showSnackBar(new SnackBar(content: new Text("$result")));
    setState(() {
      _isShowingNav = result;
    });
  }
  Future<Null> _showOrHideNav() async
  {

    bool result = _isShowingNav;
    try{
      String name = _isShowingNav ? "hideNav" : "showNav";
      result = await platform.invokeMethod(name,false);
    } on PlatformException catch(e) {

    }

    setState(() {
      _isShowingNav = result;
    });
  }

  Future<Null> _getBatteryLevel() async
  {
    String batteryLevel;
    try{
      final String result = await platform.invokeMethod("getBatteryLevel");
      batteryLevel = "Battery level at $result % .";
    } on PlatformException catch(e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }

  Future<Null>  _updateUserInfo() async
  {
    String msg;
    try{
      final Map result = await platform.invokeMethod("updateUserInfo",{"id":10,"name":"Kusina"});
      msg = "Battery level at $result % .";
    } on PlatformException catch(e) {
      msg = "Failed to get battery level: '${e.message}'.";
    }

    setState(() {
      _userInfo = msg;
    });
  }
}

class SecondRouteWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Route"),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            // Navigate back to first route when tapped.
            FlutterBoost.singleton.closePageForContext(context);
          },
          child: Text('Go back!'),
        ),
      ),
    );
  }
}

class TabRouteWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Tab Route"),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            FlutterBoost.singleton.openPage("second", {}, animated: true);
          },
          child: Text('Open second route'),
        ),
      ),
    );
  }
}

class FlutterRouteWidget extends StatelessWidget {
  final String message;

  FlutterRouteWidget({this.message});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('flutter_boost_example'),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Container(
            margin: const EdgeInsets.only(top: 80.0),
            child: Text(
              message ?? "This is a flutter activity",
              style: TextStyle(fontSize: 28.0, color: Colors.blue),
            ),
            alignment: AlignmentDirectional.center,
          ),
          Expanded(child: Container()),
          InkWell(
            child: Container(
                padding: const EdgeInsets.all(8.0),
                margin: const EdgeInsets.all(8.0),
                color: Colors.yellow,
                child: Text(
                  'open native page',
                  style: TextStyle(fontSize: 22.0, color: Colors.black),
                )),

            ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
            ///例如:sample://nativePage?aaa=bbb
            onTap: () =>
                FlutterBoost.singleton.openPage("sample://nativePage", {
                  "query": {"aaa": "bbb"}
                }),
          ),
          InkWell(
            child: Container(
                padding: const EdgeInsets.all(8.0),
                margin: const EdgeInsets.all(8.0),
                color: Colors.yellow,
                child: Text(
                  'open flutter page',
                  style: TextStyle(fontSize: 22.0, color: Colors.black),
                )),

            ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
            ///例如:sample://nativePage?aaa=bbb
            onTap: () =>
                FlutterBoost.singleton.openPage("sample://flutterPage", {
                  "query": {"aaa": "bbb"}
                }),
          ),
          InkWell(
            child: Container(
                padding: const EdgeInsets.all(8.0),
                margin: const EdgeInsets.all(8.0),
                color: Colors.yellow,
                child: Text(
                  'push flutter widget',
                  style: TextStyle(fontSize: 22.0, color: Colors.black),
                )),
            onTap: () {
              Navigator.push(
                  context, MaterialPageRoute(builder: (_) => PushWidget()));
            },
          ),
          InkWell(
            child: Container(
                padding: const EdgeInsets.all(8.0),
                margin: const EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 80.0),
                color: Colors.yellow,
                child: Text(
                  'open flutter fragment page',
                  style: TextStyle(fontSize: 22.0, color: Colors.black),
                )),
            onTap: () => FlutterBoost.singleton
                .openPage("sample://flutterFragmentPage", {}),
          )
        ],
      ),
    );
  }
}

class FragmentRouteWidget extends StatelessWidget {
  final Map params;

  FragmentRouteWidget(this.params);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('flutter_boost_example'),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Container(
            margin: const EdgeInsets.only(top: 80.0),
            child: Text(
              "This is a flutter fragment",
              style: TextStyle(fontSize: 28.0, color: Colors.blue),
            ),
            alignment: AlignmentDirectional.center,
          ),
          Container(
            margin: const EdgeInsets.only(top: 32.0),
            child: Text(
              params['tag'] ?? '',
              style: TextStyle(fontSize: 28.0, color: Colors.red),
            ),
            alignment: AlignmentDirectional.center,
          ),
          Expanded(child: Container()),
          InkWell(
            child: Container(
                padding: const EdgeInsets.all(8.0),
                margin: const EdgeInsets.all(8.0),
                color: Colors.yellow,
                child: Text(
                  'open native page',
                  style: TextStyle(fontSize: 22.0, color: Colors.black),
                )),
            onTap: () =>
                FlutterBoost.singleton.openPage("sample://nativePage", {}),
          ),
          InkWell(
            child: Container(
                padding: const EdgeInsets.all(8.0),
                margin: const EdgeInsets.all(8.0),
                color: Colors.yellow,
                child: Text(
                  'open flutter page',
                  style: TextStyle(fontSize: 22.0, color: Colors.black),
                )),
            onTap: () =>
                FlutterBoost.singleton.openPage("sample://flutterPage", {}),
          ),
          InkWell(
            child: Container(
                padding: const EdgeInsets.all(8.0),
                margin: const EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 80.0),
                color: Colors.yellow,
                child: Text(
                  'open flutter fragment page',
                  style: TextStyle(fontSize: 22.0, color: Colors.black),
                )),
            onTap: () => FlutterBoost.singleton
                .openPage("sample://flutterFragmentPage", {}),
          )
        ],
      ),
    );
  }
}

class PushWidget extends StatefulWidget {
  @override
  _PushWidgetState createState() => _PushWidgetState();
}

class _PushWidgetState extends State<PushWidget> {
  VoidCallback _backPressedListenerUnsub;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();

//    if (_backPressedListenerUnsub == null) {
//      _backPressedListenerUnsub =
//          BoostContainer.of(context).addBackPressedListener(() {
//        if (BoostContainer.of(context).onstage &&
//            ModalRoute.of(context).isCurrent) {
//          Navigator.pop(context);
//        }
//      });
//    }
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _backPressedListenerUnsub?.call();
  }

  @override
  Widget build(BuildContext context) {
    return FlutterRouteWidget(message:"Pushed Widget");
  }
}

完整内容参考:HybridFlutter

推荐阅读更多精彩内容