flutter 练习
Last year we started using Flutter at EyeEm. With a single codebase we managed to develop and integrate a brand new feature into our existing apps. Now we’re taking the next step and putting Flutter upfront with the new onboarding experience.
去年,我们开始在EyeEm使用Flutter 。 通过单一代码库,我们设法开发了一项全新功能并将其集成到我们现有的应用程序中。 现在,我们迈出了下一步,将Flutter置于全新的入门体验之上。
现状与新概念 (The Status Quo & A New Concept)
Over years our Android & iOS apps drifted apart. One of the areas where this became visible was our onboarding flow. You only get one chance to make a first impression and we weren’t that great at it.
多年来,我们的Android和iOS应用程序渐行渐远。 我们的入职流程是其中一个显而易见的领域。 您只有一次留下深刻印象的机会,但我们做的并不那么好。
![Image for post](https://miro.medium.com/max/9999/1*oSjEx-_Xcaiimo5ZLTJeMQ.png)
![Image for post](https://miro.medium.com/max/9999/1*CwNzmmmWf-LsutLUKEOZgw.png)
![Image for post](https://miro.medium.com/max/9999/1*pORIN72FNRIpv3Sc4sBvFA.png)
Now if you think about it, our onboarding is a pretty self-contained. As such it’s a perfect candidate for the Flutter Add-To-App feature. We decided to take this opportunity and push forward with a new unified flow.
现在,如果您考虑一下,我们的入职培训是相当独立的 。 因此,它是Flutter附加应用功能的理想选择。 我们决定抓住这个机会,以新的统一流程推进。
![Image for post](https://miro.medium.com/max/9999/1*-2QVEfqwyWtHzorfH3vUmA.png)
Once the conceptual phase was over, this is what our new onboarding designs would look like — all we had to do is just convert these specs into shiny Flutter widgets, maybe throw an A/B test here or there. Easy, right? Well, not quite.
一旦概念阶段结束,这就是我们新的入职设计的样子–我们要做的就是将这些规范转换为闪亮的Flutter小部件,也许在此处或此处进行A / B测试。 容易吧? 好吧,不是。
技术启动 (Tech Kick-Off)
Last year, when we started with Flutter, we established our design system and decided on the app architecture. Our app is essentially a loosely coupled collection of pages routed by Voyager with states managed by Bloc all grouped into individual feature packages. On top of everything, we have an overarching application bloc to share things like default theme, account, system services or settings.
去年,当我们开始使用Flutter时,我们建立了设计系统并决定了应用程序架构。 我们的应用程序实质上是由Voyager路由的,具有由Bloc管理的状态的所有页面的松散耦合集合,所有状态都分组为单独的功能包。 最重要的是,我们拥有一个总体的应用程序块来共享诸如默认主题,帐户,系统服务或设置之类的内容。
![Image for post](https://miro.medium.com/max/9999/1*9nI937tCMTW_l5mAQjfiZQ.png)
Having all the needed infrastructure already in place, we kicked off tech planning by creating a navigation map of the feature (one you can see below):
在所有必要的基础架构已经就绪的情况下,我们通过创建功能导航图(如下图所示)启动了技术规划:
![Image for post](https://miro.medium.com/max/9999/1*iIoUfoMCED91yaylFqC7xA.jpeg)
We took key screens from Figma designs and organized them into a navigation flow using a tool called Miro. Once this was done we assigned names and paths to every screen which then we would use for routing with Voyager. We continued our planning with adding sticky notes to relevant pages to map out all possible tech tasks.
我们从Figma设计中获取了关键屏幕,并使用名为Miro的工具将它们组织成一个导航流程。 完成此操作后,我们将为每个屏幕分配名称和路径,然后将其用于与Voyager进行路由。 我们通过在相关页面上添加便签来规划所有可能的技术任务,从而继续进行计划。
![Image for post](https://miro.medium.com/freeze/max/9999/1*mqYu-QBoXORaILhVC_tT8w.gif)
Finally, it was time to transfer those sticky notes to a sad place called JIRA. Luckily for us Miro, has this integration which allowed us to embed JIRA tickets directly on top of our navigation map. This created an “eagle” view of the project — a visual progress tracking method offering way more insight than traditional Kanban/Scrum boards.
最后,是时候将这些便签纸转移到一个名为JIRA的悲伤地方了。 幸运的是,米罗(Miro)具有此集成功能,使我们能够将JIRA票证直接嵌入到我们的导航地图顶部。 这样就创建了项目的“鹰图”视图-一种可视的进度跟踪方法,提供了比传统看板/ Scrum面板更多的洞察力。
Now let’s take a look at some of the challenges we encountered during the implementation phase.
现在让我们看一下在实现阶段遇到的一些挑战 。
授权状态和多个Flutter进入/退出点。 (Authorisation States & Multiple Flutter Entry/Exit Points.)
MKT (Market Keywording Tool), our first Flutter feature, would only run in already authenticated environment — host app would launch Flutter engine and pass authorisation token using the platform channels — easy.
MKT(市场关键字工具)是我们的第一个Flutter功能 ,仅在已经过身份验证的环境中运行-主机应用程序将启动Flutter引擎并使用平台渠道传递授权令牌-很容易。
This suddenly starts to look a bit more complex with Onboarding. You are facing a full blow authorisation on the Flutter side — once you’re done, you need to make sure the host app (which is still not Flutter) works properly as if it was coming from the old onboarding flow.
对于“入门”,这突然看起来变得有些复杂。 您将面临Flutter方面的全面授权-完成后,您需要确保主机应用程序(仍不是Flutter)正常运行,就像它来自旧的入职流程一样。
On top of that, MKT and Onboarding features exist in the same app and have the same main.dart entry point. How do you handle it?
最重要的是,MKT和Onboarding功能存在于同一应用程序中,并且具有相同的main.dart入口点。 您如何处理?
![Image for post](https://miro.medium.com/max/9999/1*y9vYKafNXZCrTsCilGD5fQ.png)
In short, we have an abstract class which decides what Flutter’s next screen should be. We distinguish the entry point using initalRoute*. If our entry point is a MKT path we use different strategy implementation from one we would use for Onboarding… and if we use the Onboarding feature in e.g. standalone runner app environment we can provide yet another path strategy for this.
简而言之,我们有一个抽象类来决定Flutter的下一个屏幕应该是什么。 我们使用initalRoute *区分入口点。 如果我们的入口点是MKT路径,那么我们将使用与Onboarding所用的策略实施方法不同的策略实现;如果我们在独立的Runner App环境中使用Onboarding功能,则可以为此提供另一种路径策略。
Anyway, once our user is finished with the onboarding we need to do the handover and return to the host app. It turns out having these two platform methods was just enough to make it all work.
无论如何,一旦我们的用户完成了入门,我们就需要进行切换并返回到主机应用程序。 事实证明,拥有这两种平台方法仅足以使其全部工作。
![Image for post](https://miro.medium.com/max/9999/1*CzegjJAumvplsp_FUOM5sQ.png)
First method passes account object we produce on the Flutter side to the host platform as serialized json map. Second method asks the host to kill the Flutter instance and move to the next screen.
第一种方法将在Flutter端生成的帐户对象作为序列化的json映射传递到主机平台。 第二种方法要求主机杀死Flutter实例并移至下一个屏幕。
* We actually don’t use initalRoute directly but have a custom platform method that returns initalRoute value — Flutter’s initalRoute is making weird assumptions that mess up your navigation stack.
* 实际上,我们实际上并不 直接 使用 initalRoute ,而是有一个返回 initalRoute 值 的自定义平台方法 -Flutter 的 initalRoute 做出了 奇怪的假设 ,使您的导航堆栈混乱。
授权插件:Facebook,Google和Apple (Authorisation Plugins: Facebook, Google & Apple)
The first step in the onboarding migration was getting the e-mail sign in method working and then integrating it with the rest of the app. Tackling one big problem at the time.
入门迁移的第一步是使电子邮件登录方法正常运行,然后将其与应用程序的其余部分集成。 解决当时的一个大问题。
Next, Facebook and Google buttons were on the list (…and Apple very very soon). Now the question is — if you have e.g. Facebook SDK already integrated in your app — does it prevent you from using Flutter Facebook plugin?
接下来,Facebook和Google按钮出现在列表中(…很快就出现了Apple)。 现在的问题是-如果您的应用程序中已经集成了Facebook SDK,是否会阻止您使用Flutter Facebook插件?
No, it should work just fine— e.g. your existing native Facebook code will continue working along Flutter one. You should, however, make sure that the SDK versions used by the plugin and your host app match.
不,它应该可以正常工作-例如,您现有的本机Facebook代码将继续沿Flutter 1运行。 但是,您应确保插件使用的SDK版本与您的主机应用匹配。
So, does it just work?
那么,它是否有效?
丑陋的插件 (Ugly Plugins)
Don’t get me wrong here — Flutter plugins are a great concept in general, they take away the pain of implementing platform specific code. You usually get a concise API to consume on the Flutter side. This is a massive relief to those (including myself) who can’t keep up with all the system updates and API changes. AndroidX, New Location Permissions, Next XCode version… name your pain here.
不要误会我的意思-Flutter插件通常是一个很棒的概念,它们消除了实现平台特定代码的痛苦。 通常,您可以在Flutter端使用简洁的API。 对于那些无法跟上所有系统更新和API更改的人(包括我自己),这是极大的缓解。 AndroidX,新位置权限,下一个XCode版本…在此输入您的痛苦。
It takes one person to do the integration effort with plugins. Once it’s out there, others can simply profit from it without deep diving into platform code. It’s the open-source at its finest. For instance, unofficial Facebook Login Plugin by Iiro Krankka is an excellent example of a good job. It has exhaustive documentation, works great and enjoys well deserved popularity.
与插件进行集成工作需要一个人。 一旦推出,其他人就可以直接从中受益,而无需深入研究平台代码。 它是最好的开源。 例如, Iiro Krankka的 非官方 Facebook登录插件就是出色工作的一个很好的例子。 它具有详尽的文档资料,效果很好,并享有当之无愧的人气。
Things, however, can get ugly when one of the plugins:
但是,当使用以下插件之一时,事情可能会变得难看:
- doesn’t quite work on one of the platforms you want to support 在您要支持的平台之一上无法正常工作
- pulls massive dependency tree on the host side 在主机端拉出大量的依赖树
- requires you to add extra work on the platform side. 需要您在平台方面添加额外的工作。
- prevents you from using Flutter Web/Flutter Desktop 阻止您使用Flutter Web / Flutter桌面
- isn’t testable 无法测试
- hasn’t updated to add-to-app plugin API 尚未更新为“添加到应用程序”插件API
- has maintenance issues 有维护问题
The more plugins you add to your project, the more likely you are to run into one of the above issues.
您向项目中添加的插件越多,您遇到上述问题之一的可能性就越大。
For instance, adding Facebook plugin requires you to change Android manifest to include a Facebook ID of your app — otherwise the SDK crashes an App on start. It’s not a problem if you are writing only one app. However, if you have multiple feature packages with multiple runner apps (like our company) and you decide to make Facebook plugin a base dependency, you essentially make all of your runner apps crash.
例如,添加Facebook插件要求您更改Android清单以包含您应用程序的Facebook ID-否则SDK会在启动时使应用程序崩溃。 如果您只编写一个应用程序,这不是问题。 但是,如果您具有包含多个运行器应用程序的多个功能包(例如我们的公司),并且决定将Facebook插件作为基本依赖项,则实际上会使所有运行器应用程序崩溃。
Then there is Google Sign In Plugin by Google where you’d expect things to be rock solid. We most certainly thought we’d get Google integration for free on iOS —unfortunately the Cocoa Pods issues we’ve run into have successfully discouraged us from pursuing this goal. That’s not all of it. We were shocked to discover serverAuthCode wasn’t supported (PR opened in September 2019, still open at the time of writing this article — April 2020).
然后是Google提供的Google登录插件 ,您可以在此牢牢掌握一切。 我们最肯定地以为我们可以在iOS上免费获得Google集成-不幸的是,我们遇到的可可豆问题成功地阻止了我们追求这一目标。 这还不是全部。 我们震惊地发现serverAuthCode不支持( PR打开九月2019年, 仍处于打开状态在写这篇文章的时候-四月2020年)。
![Image for post](https://miro.medium.com/max/9999/1*anO0Xf5Zd2E8Cd_tPWwzYg.png)
Good part, it’s all open-source so you can fix it yourself — I just copied the entire package folder from the official repo, removed any non Android code so that iOS would compile, applied changes from the aforementioned PR and finally added some of my own changes to get the serverAuthCode support.
好的部分是全部开源的,因此您可以自己修复-我只是从官方仓库中复制了整个程序包文件夹,删除了所有非Android代码,以便iOS可以编译,应用了上述PR中的更改,最后添加了一些自己进行更改以获得serverAuthCode支持。
The Power of Abstraction (and Mocks)
抽象的力量(和嘲弄)
Anyway, it became apparent to us that plugins can be a liability blocking the development progress. Obvious question — is there anything we could do to mitigate it?
无论如何,对我们来说显而易见的是,插件可能成为阻碍开发进度的责任。 显而易见的问题-我们可以采取什么措施来缓解它?
Stick to pure Dart/Flutter. Avoid plugins as long as you can.
坚持纯Dart / Flutter。 尽量避免使用插件。
In short, you can create an abstract class that will mirror the API of the plugin you want to use. You don’t need to mirror the entire API — just specify the parts that are going to be used, e.g. Facebook abstraction could be something like this:
简而言之,您可以创建一个抽象类,该抽象类将镜像您要使用的插件的API。 您不需要镜像整个API,只需指定将要使用的部分,例如Facebook抽象可能是这样的:
![Image for post](https://miro.medium.com/max/9999/1*TVgpZYhHoIKl5bRx6HurmQ.png)
Now, when actually need to use Facebook, you provide a concrete implementation — in all other cases though, you can just use a mocked instance.
现在,当实际需要使用Facebook时,您可以提供一个具体的实现-尽管在所有其他情况下,您都可以使用模拟实例。
![Image for post](https://miro.medium.com/max/9999/1*qCoO9uTKKlLYA17gi4wEMw.png)
That’s some extra work added but there are immediate pay offs:
这增加了一些额外的工作,但有立竿见影的效果:
- Your plugin dependency becomes programmatic and optional. 您的插件依赖项变为编程的和可选的。
- You can easily mock the plugin in widget tests. 您可以在小部件测试中轻松模拟插件。
- You can use mocks in runner apps and avoid blowing your dependency tree. 您可以在运行程序应用程序中使用模拟,并避免破坏依赖树。
- You get flexibility in refining concrete implementation — e.g. you can use 2 different plugins for different platforms. If there is a better plugin you can switch to that without the need to refactor the rest of your code. 您可以灵活地改进具体的实现方式-例如,可以将2个不同的插件用于不同的平台。 如果有更好的插件,则可以改用该插件,而无需重构其余代码。
An important side note here — Flutter dependency system doesn’t appear to come with any “flavor” mechanism.
这里有一个重要的注意事项-Flutter依赖系统似乎没有任何“味道”机制。
Flavors (or Variants) is a general technique that allows you to change dependencies during build time based on a target you are building for.
风味 (或变体)是一种通用技术,可让您在构建期间根据要构建的目标更改相关性。
While this feature might be missing in Flutter — it’s quite easy though to write a script that will modify pubspec.yaml and put that in a CI pipeline:
尽管Flutter中可能缺少此功能,但是编写一个修改pubspec.yaml并将其放入CI管道的脚本相当容易:
![Image for post](https://miro.medium.com/max/9999/1*_3eAZlexMePty9D9s51ytw.png)
减轻重资产问题 (Mitigating Heavy Assets Problem)
If you haven’t noticed yet, our onboarding comes with some big hi-res background images — they are roughly 1.5MB large each in design assets.
如果您还没有注意到,我们的入职流程会附带一些高分辨率的背景图片-设计资产中的每个背景图片的大小约为1.5MB。
Now, the app size has a direct impact on the app install rate. If you follow Google Play, in their article they state the following:
现在,应用程序大小直接影响应用程序安装率。 如果您关注Google Play,则他们在其文章中指出以下内容:
For every 6 MB increase to an APK’s size, we see a decrease in the install conversion rate of 1%.
APK大小每增加6 MB,安装转换率就会降低1%。
Since our onboarding would ship with something like extra 3MB of assets, this would mean possible ~0.6–0.8% drop in installs. As a developer, you might suggest downloading the image from the internet. Remember though, this is our first screen and some network connections can be crappy. You might end up with a completely black background and your chance to make a first good impression is gone.
由于我们的入职时会附带额外的3MB资产,这意味着安装量可能会下降〜0.6–0.8% 。 作为开发人员,您可能建议从Internet下载图像。 但是请记住,这是我们的第一个屏幕,某些网络连接可能很糟糕。 您最终可能会得到一个完全黑色的背景,而留下第一印象的机会就消失了。
Did you know Flutter engine has built in WebP support by default? Well, now you do! With this knowledge we converted JPEG images to WEBP using ImageMagick CLI:
您知道Flutter引擎默认内置WebP 支持吗? 好吧,现在你做到了! 有了这些知识,我们使用ImageMagick CLI将JPEG图像转换为WEBP:
![Image for post](https://miro.medium.com/max/9999/1*TBWZxPmcp6wj4joqCb455Q.png)
This allowed us to shrink total assets size by around ~30%.
这使我们能够将总资产规模缩小约30%。
These were some major challenges we had to face during the onboarding milestone. Let’s now talk about the output our team has delivered.
这些是我们在入职里程碑期间必须面对的一些主要挑战。 现在让我们讨论一下我们团队所提供的输出。
开源贡献 (Open Source Contributions)
First off product is a library called SpanBuilder — it simplifies API around building text spans — we use it in the onboarding screen to highlight and link footer text.
首先推出的产品是一个名为SpanBuilder的库-它简化了构建文本范围时的API-我们在入门屏幕中使用它来突出显示和链接页脚文本。
![Image for post](https://miro.medium.com/max/9999/1*KD29tDw5lbnEqHz8Oag13g.png)
The other tool we open sourced was a Dio Firebase Performance plugin for the excellent Dio package (HTTP client). Since we’re making more API calls from the Flutter side it’s worth knowing how do they perform.
我们开源的另一个工具是用于出色Dio软件包(HTTP客户端)的Dio Firebase Performance插件。 由于我们正在从Flutter端进行更多的API调用,因此有必要了解它们的性能。
![Image for post](https://miro.medium.com/max/9999/1*g5qJk1vRc0gNMSCRVnz2Pw.png)
发布摘要 (Release Summary)
It was a successful release and we’ve got some numbers to back it up.
这是一个成功的版本,我们有一些支持它的数据。
First, let’s talk about quality. The test code coverage for the feature is at 90% — we have auto generated test plans for all the screens listed in navigation map thanks to Voyager. Whenever we need to be super sure about implemented logic we add extra tests at Bloc level. Additionally, our mock environment makes things easy to test.
首先,让我们谈谈质量。 该功能的测试代码覆盖率为90% -感谢Voyager,我们为导航地图中列出的所有屏幕自动生成了测试计划。 每当我们需要对实现的逻辑超级确定时,我们都会在Bloc级别添加额外的测试。 此外,我们的模拟环境使事情易于测试。
We A/B tested the new release against old iOS and Android versions. Here’s what we found out:
我们A / B针对旧的iOS和Android版本测试了新版本。 这是我们发现的:
![Image for post](https://miro.medium.com/max/9999/1*fNw0bzRsFscYAb2Uy4WfAw.png)
Android sign up conversion rate didn’t change while on iOS it got much better. Also, the market sign-up numbers got better on both platforms.
Android注册转换率没有变化,而在iOS上却好得多。 同样,在两个平台上的市场注册人数都增加了。
The data also shows us iOS and Android users behave the same using the new flow — our design system brings a consistent look and feel across the product while staying true to the respective platforms.
数据还显示,iOS和Android用户使用新流程的行为相同-我们的设计系统为整个产品带来了一致的外观,同时又保持了各自平台的真实感。
Kudos to the amazing team that made this release possible — Adrienne, Anton, Dario, Ethan, Jędrzej & Vidu.
致敬使这次发布成为可能的出色团队-Adrienne,Anton,Dario,Ethan,Jędrzej和Vidu。
Thank you all for reading ❤️ If you want to stay connected, you can follow me on twitter/github or just reach out directly on fluttercommunity.slack. If you’re interested in EyeEm — wait no more and join 25M+ photographers today!
谢谢大家阅读 ❤️ 如果您想保持联系,可以在 Twitter / github 上关注我, 或者直接在 fluttercommunity.slack 上与我们 联系 。 如果您对 EyeEm 感兴趣,就 不用再 等待了, 今天就 加入 2500万+摄影师!
flutter 练习