很简单的Flutter填小坑

栏目: IOS · 发布时间: 5年前

内容简介:以下开始,我们假设你已经有了一定的Flutter基础概念, 比如Release/Debug版本,比如如何跑起来一个HelloWord

FlutterRN 不同的是前者和原生iOS/Android组件比如 UIButton 其实没半毛线关系,所有元素都是 Flutter 自己画的,而 RN 则只是做了个桥调用而已,明白这个可能对你下决定/忽悠老板有非常大的意义

以下开始,我们假设你已经有了一定的Flutter基础概念, 比如Release/Debug版本,比如如何跑起来一个HelloWord

2. 混合栈:

爸爸的指南戳这里

谷歌写的很清楚了大家找着1234567就可以两者混合了,当然你还可以谷歌搜【flutter混合】

配置中有一些爸爸们没告诉你的事,还是以iOS工程为主,毕竟Android不熟:

0. flutter create -t module xxx 中的 -t

有很多实例里是没有 -t 的,简单来说不加 -t ,你的iOS/android的工程将会随git一起提交,而加了后他们永远不会被提交,别人拉新代码都需要 flutter create 来正常跑起来。

咋看一下 -t 会更高端一点,你不需要改任何原生工程全用 flutter 写就行,而且也不会因为sdk改动或者xcode改动影响到你的iOS文件夹,但是请诚实点面对这个社会:

  • 默认生成的iOS文件夹的 podfile 是没有 !use_framwork
  • 你确定不需要改plist来添加白名单什么嘛
  • 你确定appdelegate里不需要加些配置来协助你的第三方库比如微信嘛

所以如果 -t 了,作为架构师/研究者的你请自觉做好写脚本去修改上面这些文件的准备。

同时请妥善运行 flutter packages get ,因为他会重新搞一下你的iOS文件夹的内容, 而 flutter build --release 会默认帮你 flutter packages get ,所以如果你有自己的初始化脚本,那执行顺序应该是:

  • flutter packages get
  • 你的脚本
  • flutter build --release -no-pub -no-pub 会忽略掉 build 时的那次 get

1. Build Phases 中的 script 其实不是每次都需要run的

很简单的Flutter填小坑
上面这个勾推荐你勾上后再提交,因为这个脚本其实只会对 Flutter Release

版本的构建有影响,

  • 说白了,如果你只是普通调试,跑不跑这个脚本,结果是一样的都是 Flutter Debug 模式
  • 这个脚本会增加编译时间,所以无需 flutter 更新的工程师不需要关心他是不是跑了
  • 如果你是 Debug 的主工程想跑 ReleaseFlutter ,不跑这个Script是会crash的,这就是因为 Flutter Release 的配置需要这个脚本来完成

2. 请老老实实按照谷歌推荐的方式集成,刚玩时候你的目标是跑起来然后迅速去熟悉dart以及 flutter 布局,并不是研究原生与 flutter 的耦合以及框架解藕

3. 打包脚本中 Flutter run 是不合理的

可能你也会用jenkins或者fastlane去给QA打包,这时候如果要生成产物请走 Flutter build --release 命令,因为 Flutter run 会直接把进程卡住然后你就无法持续了,虽然你可以选择后台运行 run ,但是你没法保证同步~

4. 跳转还是老老实实走 flutter_boost

至于为什么,可以翻翻咸鱼写的文章,还是从简化的说就是:

  • 谷歌给的flutter混合栈中, FlutterViewController 肚子里就是 Flutter引擎 ,所以如果纯flutter项目你会发现,其实vc就只有一个,他是在单个vc中画新页面来完成push操作的
  • 但是混合栈中你一定会 FlutterViewController 去push下一个 FlutterViewController ,这样无限增加的引擎会让你的应用在短时间内就崩溃~
  • 咸鱼的方案就是保留一个引擎,然后在push的时候把引擎单例从前一个vc拿到后一个,然后通过截图等操作重现滑动返回或者pop等操作

当然如果你是很厉害的那种一定要自己玩,那也可以,否则请看2里说的在这里一样适用~

用过的孩子一定对 query 这个key很愤怒,确实你也不明白为什么咸鱼官方并没有说这个key,但事实上安卓侧原生接到的参数都是通过这个包着的,即 { "query": { "name" : xxx }} , 所以iOS的也可以注意下

5. MethodChannel系列交互

普通的交互注册你点这里就行,很简单

但是其实这样并不好看~虽然官方推荐,但是官方还有个很优雅的姿势叫 FlutterPlugin

我是个优雅的:chestnut:

说穿了 MethodChannel 只是需要原生有个时机注册进 Flutter 引擎而已,至于什么时候,随时都行,所以我们找到了 FlutterPlugin 的时机,所有第三方的flutter插件(这些插件多数其实也是通过channel调用原生来解决的),其实也是通过注册的方式注入引擎的,如果你想看看怎么来的,你可以在你的工程里找 GeneratedPluginRegistrant.m 这个文件,这是个系统生成的文件,他会帮你注册所有你引入flutter的插件:

@implementation GeneratedPluginRegistrant

+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
  [FLTDeviceInfoPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTDeviceInfoPlugin"]];
  ...
}

@end
复制代码

所以我们有个大胆的想法,如果我们自己也搞个 plugin ,里面塞个 channel ,用来做所有 flutter 与我们现有主工程的进行交互,不就可以装的很优雅了嘛? 但是问题是:系统生成的 GeneratedPluginRegistrant 我怎么在里面加上我自己的插件? 因为我自己的插件在主工程,并不是在flutter里引入的鸭...

这时候聪明的你可能已经想到了那在Swift时代被遗忘的黑魔法:

- (instancetype)init
{
    if (self = [super init]) {
        _viewController = [FLBFlutterViewControllerAdaptor new];
        [_viewController view];
        Class clazz = NSClassFromString(@"GeneratedPluginRegistrant");
        if (clazz) {
            if ([clazz respondsToSelector:NSSelectorFromString(@"registerWithRegistry:")]) {
                [clazz performSelector:NSSelectorFromString(@"registerWithRegistry:")
                            withObject:_viewController];
            }
        }
    }
    
    return self;
}
复制代码

上面这段是从 flutter_boost 中找到的,所以你应该明白其实你也可以进行偷换 GeneratedPluginRegistrant 中的 registerWithRegistry 方法来先注册flutter自己的插件,再注册主工程插件已保持优雅的姿态了吧~

6. 与原生通信该传些什么

FlutterResult 类里规定了记得传 String ,当然传人能读得懂的字符串了,请重复这句【传人能读得懂的字符串】,所以千万不要耍心机把一个 data 搞成字符串然后传给 flutter 让他变回 data 甚至变回 image ,记得,你读不懂 image 字符串, flutter 也不懂。而 json 字符串你看得懂,所以 flutter 也看得懂

所以复杂的文件,你只要知道, flutter 虽然读不懂 data ,但是他也能访问 NSTemporaryDirectory , 所以你可以通过暂时文件夹路径的方式来支持双方共享同一个数据,虽然也不太推荐,但是你好像也只能这么干了

private func saveToFile(image: UIImage) -> String? {
    guard let data = image.jpg(compressionQuality: 1.0) else {
        return nil
    }
    let tempDir = NSTemporaryDirectory()
    let imageName = "image_picker_\(ProcessInfo().globallyUniqueString).jpg"
    let filePath = tempDir.appending(imageName)
    if FileManager.default.createFile(atPath: filePath, contents: data, attributes: nil) {
        return filePath
    } else {
        return nil
    }
}
复制代码

比如像楼上这样

3. Dart 里我要准备些啥

1. 抽象底层服务

如果一开始规划是有原生的基础服务就调原生的,比如请求登录拍照等等,那理想中其实你可以觉得 flutter 本身代码你只需要做业务就行了。

嗯,回想一下那个 RN 中编码5分钟,联调5小时的你吗?所以如果有时间,请为 flutter 搭建所有用得到的基础设施,来大幅度加快调试速度:

下面这个模式可供大家参考,他可以最大限度保证代码可扩展性

我们通过

上帝模式: isSonMode= falseflutter 自己跑的模式

儿子模式: isSonMode= true 即在主工程里跑的模式

上述的区分来判断我们需要调用哪种基础服务

比如这是我们的dart端请求调用:

class RequestService {
  static RequestService shared = RequestService();
  factory RequestService() =>
      GlobalConfig.isSonMode ? SonRequestService() : GodRequestService();

  Future<Map> post(String url, Map para) async {
    throw UnimplementedError("saveElement 方法木有实现哦");
  }

  Future<UploadItem> upload(UploadItem file) async {
    throw UnimplementedError("saveElement 方法木有实现哦");
  }
}


class GodRequestService implements RequestService  {
	Future<Map> post(String url, Map para) async {
		// 你可以在这里调用dio或者http请求
	}
	
	Future<UploadItem> upload(UploadItem file) async {	
		...
	}
}
复制代码

Son 就是调用原生的channel,所以不贴了,所以我们根据 GlobalConfig.isSonMode 来切换使用哪个具体的 RequestService 实现,来隔离每个 RequestService 不会由于判断而造成的污染,

当然还有更花哨的判断:

static Router _route =
      GlobalConfig.isAndroid ? AndroidRouteBox() : GlobalConfig.isSonMode ? FlutterBoostRouteBox() : GodRouteBox();
复制代码

比如路由我们在 flutter 上帝模式下用的是 flutter 自己跳转, iOS 主工程下是 flutter_boost , 安卓主工程下依旧是自己的跳转

而在 dart 业务代码中你只需要做 RequestService.shared.post... 的调用,就可以正常请求,不需要关心切换的问题了。

(当然请求抽象会在下期着重讲,敬请期待~)

2. Model.fromJSON

工具到处都有 稍微推荐下这个 ,当然极其赞美手写毅力担当~但是注意点:

  • dart 相比 Swift 来说对类型的严格性更高
  • int / double 如果定义反了会炸,比如把 1.0 传给 int 型属性,在 Swift 中毫无波澜,在 dart 中就是波涛汹涌, dart 中妥善点可以用 num 来修饰所有数字
  • String 类型推荐都加上 toString()

额外加个优雅的,Swift中的

var isUser : Bool {
	return xxx == 1 && xxxx = 2
}
这样的计算属性,在dart中可以写成:
bool get isUser => xxx == 1 && xxxx = 2 
或者
bool get isUser {
	return xxx == 1 && xxxx = 2 
} 
复制代码

这样可以降低你的 Widget 中过多的业务计算,这些定义还是让 Model 来做吧

3. 忘记所谓的页面生命周期

由于引擎的绘制不同,导致 flutter 的页面生命周期并没有合适的回调/代理等来触发,不要想着 didUpdateWidget 或者 Dependency 里来做些奇怪的请求刷新,那地方不是让你用来做这个事情的~ 唯一你可以做的好像就只有在 initState 里做完所有事情;

这里就讲点虚的,所以所有的操作需要严格从动作触发,而不要是再从页面级别触发了,

举个例子

Swift :button点击 -> 跳转 -> 返回 -> ViewWillAppear 刷新全页

Flutter : button点击 -> 刷新对应的Widget -> 跳转

其实 Swift 的例子也不好,但是其实我们因为懒基本都这样干了,但是在 dart 中我们没法知道页面生命周期,所以请用正确的时间做正确的事,当然这里就容忍了满屏幕的 setState ,肯定会有人告诉你这样是不合理的了~怎么才算合理,这里就先不说了,毕竟这里的目的是为了让大家把应用跑起来能上线,至于优雅不优雅后面熟悉了自然就有感觉了。

4. 资源图片

很简单的Flutter填小坑

首先这样你肯定没问题,但是记得外层的1x的图一定要放的,否则认不出来~

pubspec.yaml中这样就行了

assets:
  - assets/images/
  - assets/images/2.0x/
  - assets/images/3.0x/	
复制代码

有时候你会发现你新加了个图结果位置都放对了结果没出来,没事你 debug 多点几次他就有了,如果确认名字没问题的话,确实是可能有时候不会及时显示出来的

5. 字体

比较有意思的是对于main入口:

return MaterialApp(
  title: '我是个demo',
  theme: ThemeData(
      fontFamily: Platform.isIOS ? 'PingFang SC' : null,)
}
复制代码

你需要这样设置后,在iOS手机上你的字体才会好看,否则会出现各种奇奇怪怪的样子,不过又有个隐藏坑就是:

你的所有页面的顶层 widget 必须是 material 系列的这个才会生效, 这些是 Material 系列

所以如果你的页面里直接是:

class DemoPage extends StatelessWidget {
	@override
  Widget build(BuildContext context) {
  		return Container(
	        child: 页面元素
		);
  }
}
复制代码

你会发现你的字体依旧很奇怪,虽然页面长得没问题

是不是很简单,看到这里,坑肯定还有,但至少大问题应该没有了,你应该已经可以愉快的跑起来你的应用了~


以上所述就是小编给大家介绍的《很简单的Flutter填小坑》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Squid: The Definitive Guide

Squid: The Definitive Guide

Duane Wessels / O'Reilly Media / 2004 / $44.95 US, $65.95 CA, £31.95 UK

Squid is the most popular Web caching software in use today, and it works on a variety of platforms including Linux, FreeBSD, and Windows. Squid improves network performance by reducing the amount of......一起来看看 《Squid: The Definitive Guide》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码