很简单的Flutter填小坑

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

内容简介:以下开始,我们假设你已经有了一定的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填小坑》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Introduction to Programming in Java

Introduction to Programming in Java

Robert Sedgewick、Kevin Wayne / Addison-Wesley / 2007-7-27 / USD 89.00

By emphasizing the application of computer programming not only in success stories in the software industry but also in familiar scenarios in physical and biological science, engineering, and appli......一起来看看 《Introduction to Programming in Java》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

html转js在线工具
html转js在线工具

html转js在线工具