[译] 为何 Flutter 能最好地改变移动开发

为何 Flutter 能最好地改变移动开发

若是你是一个 Android 开发者,那么你应该据说过 Flutter。这是一个相对来讲比较新的,用于制做跨平台原生应用的简单框架。这不是同类产品中的第一款,但它正被谷歌使用,这让它有了必定的可信度。尽管我一开始听到这个框架的时候对此有所保留,但我仍是心血来潮地决定给它一个机会 —— 这在一周内极大地改变了我对移动开发的见解。如下是我学到的。前端

“长喙蜂鸟在空中飞翔”,摄影者:来自 UnsplashRandall Ruizjava

在咱们开始以前,让我增长一个简短的免责声明。我在这篇文章中编写的和将要引用的应用程序是相对基础的,而且不会包含大量的商业逻辑。这虽然没什么特别的,可是我想把我从原生 Android 应用移植到 Flutter 中学到的知识和经验分享给你们,这也是我最好的例子。该应用没有在优化架构方面做出任何努力,这纯粹是为了体验开发和使用框架自己。android


整整一年前,我在 Play Store 上发布了个人第一个 Android 应用。这个应用 (Github) 在架构和编码规范方面都很是基础。这是个人第一个大型开源项目,这代表,我从事 Android 应用开发已经好久了。我在一家代理公司工做,平时会花时间在一些有着不一样技术和架构的项目上,包括 Kotlin、Dagger、RxJava、MVP、MVVM、VIPER 等等,这些都很大程度上提升了个人 Android 开发能力。ios

话虽这么说,在过去的几个月里,我一直对 Android 框架很是失望,尤为是其兼容性差,在开发应用时常常会发现它违反直觉的地方。更别提编译构建的时间了……(我推荐你读一下这篇文章,其中有更深刻的细节分析),尽管 Kotlin 和相似 Databinding 这样的工具能让问题有所改善,但整个状况仍是感受在一个太大而没法愈合的伤口上贴创可贴。下面开始了解 Flutter。git


几个星期前,当 Flutter 进入 beta 测试版的时候,我就开始使用它了。我看了一下官方文档(顺便一提,写的很棒),而后开始浏览代码实验室和指南。我开始逐渐理解 Flutter 背后的基本理念,并决定本身试一试,看能不能把它付诸于实践。我开始思考我应该先作一个什么样的项目,我决定重写个人第一个 Andriod 项目。这彷佛是一个恰当的选择,由于这能让我将一样的“第一次的努力”在两个对应的框架下进行比较,而同时对应用架构等等的方面不做太多关注。它纯粹是经过开发一组定义好的功能特性来了解 SDK。github

我首先建立了网络请求,解析 JSON 数据,并逐渐习惯 Dart 的单线程并发模型(单单这个就能够做为另外一整文章的主题)。我开始在个人应用中运行一些电影数据,而后开始为列表和列表项建立布局。在 Flutter 中建立布局和扩展无状态或有状态的小控件类加上一些方法的重写同样简单。我将比较 Flutter 和 Andriod 之间在实现这些功能方面的差别。让咱们从在 Andriod 中构建这个列表的步骤开始:后端

  1. 在 XML 文件中建立列表项布局文件
  2. 建立 adapter 来扩充 item-views 和设置数据
  3. 建立 list 的布局(在 Activity 或 Fragment 中)
  4. 在 Fragment 或 Activity 中调用 inflate 方法来建立 list 布局
  5. 在 Fragment 或 Activity 中建立 adapter 实例,layout-manager 等等
  6. 在后台线程上,下载来自网络上的电影数据
  7. 回到主线程上,将 item 设置在 adapter 上
  8. 如今咱们须要考虑一些细节,好比保存和恢复 list-state 等
  9. …… 列表一直这样继续下去

固然,这很乏味。若是你想到这样一件事,开发这些功能是一个至关常见的任务 —— 说真的,这不是一些你不可能碰到的特别罕见的用例 —— 你可能会想:真的没有更好的方法来实现吗?一种不那么容易出错的方法也许是可以涉及更少的模板代码,而且能够提升开发速度。 这时候 Flutter 诞生了。bash


你能够把 Flutter 看做是人们多年来在移动应用开发、状态管理、应用架构等方面所学到经验的结果,这就是为何它和 React.js 如此类似的缘由。一旦你开始编写代码,Flutter 就会变得有意义。让咱们看看如何运用 Flutter 来实现上面的例子:markdown

  1. 为电影 item 建立一个无状态的控件(无状态,由于咱们只有静态属性),该控件的构造函数参数是 movie(例如Dart类),并以一种声明的方式描述该布局,同时将 movie 的值(电影名称,上映日期等)绑定到控件中。
  2. 一样地为 list 也建立一个控件。(为了这篇文章,我尽可能把例子保持地简单些。显然,咱们须要添加错误状态等等,这只是开发过程的其中一件事情而已。)
@override
  Widget build(BuildContext context) {
    return new FutureBuilder(
        future: widget.provider.loadMedia(widget.category),
        builder: (BuildContext context, AsyncSnapshot<List<MediaItem>> snapshot) {
          return !snapshot.hasData
              ? new Container(
                  child: new CircularProgressIndicator(),
                )
              : new ListView.builder(
                  itemCount: snapshot.data.length,
                  itemBuilder: (BuildContext context, int index) =>
                      new MovieListItem(snapshot.data[index]),
                );
        }
    );
  }
复制代码

Movie-List-Screen 布局部分。网络

为了解决这个问题,让咱们来看看这里发生了什么。最重要的是,咱们使用了 FutureBuilder (Flutter SDK 的一部分),它要求咱们指定一个 Future (咱们例子中的 API 调用) 和 builder 函数。builder 函数给了咱们一个 BuildContext 和要返回 item 的索引值。 利用这个,咱们能够检索一部电影,根据 Future 和快照结果的 list,而且建立一个 MovieListItem-Widget(在步骤 1 中建立)做为构造函数的参数。

而后,当 build 方法第一次被调用时,咱们就开始等待 Future 的值。一旦有值以后,builder 会再次被数据(快照)调用,咱们就能够用它来构建咱们的 UI 界面。

这两个类,加上 API 的调用,将会有如下这样的效果:

已完成的电影列表功能。


嗯,这很简单。几乎是太简单了…… 意识到用 Flutter 来建立一个 list 是多么容易,这就激起了个人好奇心,让我更加兴奋地用它来继续开发。

下一步来弄清楚如何使用更加复杂的布局。原生应用程序的电影细节页面有一个至关复杂的布局,包括约束布局和一个应用程序栏。我认为这是用户所指望和欣赏的功能,若是 Flutter 真的想有机会与 Andriod 对抗,它须要可以提供更复杂的布局,就像这样。让咱们看看我建立了什么:

电影细节的页面。

这个布局由一个 SliverAppBar 组成,里面包含了电影图片的层叠布局、渐变、小气泡和文本覆盖。可以以模块化的方式表达布局使得建立这个至关复杂的布局变得很是简单。这个页面的实现方法以下所示:

@override
Widget build(BuildContext context) {
  return new Scaffold(
      backgroundColor: primary,
      body: new CustomScrollView(
        slivers: <Widget>[
          _buildAppBar(widget._mediaItem),
          _buildContentSection(widget._mediaItem),
        ],
      )
  );
}
复制代码

详细页面的主要构建方法。

在构建布局的时候,我发现本身把布局的一部分模块化为变量、方法或者其余小部件。例如,图片顶部的文本气泡只是另外一个小部件,它以文本和背景颜色做为参数。建立一个自定义视图简直就像这样简单:

import 'package:flutter/material.dart';

class TextBubble extends StatelessWidget {
  final String text;
  final Color backgroundColor;
  final Color textColor;

  TextBubble(this.text,
      {this.backgroundColor = const Color(0xFF424242),
      this.textColor = Colors.white});

  @override
  Widget build(BuildContext context) {
    return new Container(
      decoration: new BoxDecoration(
          color: backgroundColor,
          shape: BoxShape.rectangle,
          borderRadius: new BorderRadius.circular(12.0)),
      child: new Padding(
        padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 6.0),
        child: new Text(
          text,
          style: new TextStyle(color: textColor, fontSize: 12.0),
        ),
      ),
    );
  }
}
复制代码

TextBubble 部件类。

想象一下在安卓系统中创建这样的自定义视图会有多难。然而,在 Flutter 上,这只是一件几分钟就能完成的事情。可以将 UI 界面的一部分提取到像小部件这样的独立单元中,能够很容易地在应用程序中重用这些小部件,甚至跨越不一样的应用。你会注意到,这个布局的不少部分都是咱们在应用的不一样视图上重复使用的,让我告诉你:这实施起来小菜一碟,因此我决定将应用扩展到包含电视节目。几个小时以后,这件事情就完成了。这款应用集电影和电视节目于一体,在这个过程当中并无让人很头疼。我经过构建用于加载和显示数据的泛型类来作到这一点,这让我能够重用每一个布局用于电影和节目。可是,为了在 Android 上完成一样的事情,我不得不在电影和节目中使用不一样的 Activity。你能够想象这维护起来的速度有多快,可是我以为 Andriod 不够灵活,没法以一种更干净、更简单的方式去共享这些布局。


在 Flutter 实验的最后,我得出了很是直接和更有说服力的结论:

我编写出了同时运行在 iOS 和 Andriod 上的更好、更容易维护的代码,而且只须要至关少的时间和更少的代码数量。

其中最好的部分是不用处理像 fragments 和 SupportCompatFragmentManagerCompat 这样的事情,而且以一种单调、容易出错的方式保存和手动管理状态。它没有像 Andriod 开发那样使人沮丧…… 不用再等待 30 秒的“即时重载”来更改 TextView 的字体大小。再也不使用 XML 来布局。再也不使用 findViewById(我知道有 Butterknife, Databinding, Kotlin-Extensions 这样的工具,但你应该明白个人意思)。再也不有冗杂的样板代码 —— 只有结果。

一旦这两个应用在功能上或多或少都写在同一页面上时,我很想知道代码行数之间有没有什么区别。一个 repository 仓库和另外一个之间相好比何?(快速免责声明:我尚未在 Flutter 应用中集成持久存储,并且原始应用的代码库至关混乱)。让咱们用 Cloc 来比较下代码,为了简单起见,让咱们看看 Android 上的 Java 和 XML 文件,Flutter 应用上的 Dart 文件数量(不包括第三方库,这可能会大大增长 Android 的度量)。

用 Java 编写原生 Android 应用:

Meta-Data for the native Android app

http://cloc.sourceforge.net v 1.60  T=0.42 s (431.4 files/s, 37607.1 lines/s)
--------------------------------------------------------------------------------
Language                      files          blank        comment           code
--------------------------------------------------------------------------------
Java                             83           2405            512           8599
XML                              96            478             28           3577
Bourne Again Shell                1             19             20            121
DOS Batch                         1             24              2             64
IDL                               1              2              0             15
--------------------------------------------------------------------------------
SUM:                            182           2928            562          12376
复制代码

Flutter:

Meta-Date for the Flutter app

http://cloc.sourceforge.net v 1.60  T=0.16 s (247.5 files/s, 14905.1 lines/s)
--------------------------------------------------------------------------------
Language                      files          blank        comment           code
--------------------------------------------------------------------------------
Dart                             31            263             39           1735
Bourne Again Shell                1             19             20            121
DOS Batch                         1             24              2             64
XML                               3              3             22             35
YAML                              1              9              9             17
Objective C                       2              4              1             16
C/C++ Header                      1              2              0              4
--------------------------------------------------------------------------------
SUM:                             40            324             93           1992
--------------------------------------------------------------------------------
复制代码

为了解决这个问题,让咱们先比较一下文件数量: Android: 179 (.java 和 .xml) Flutter: 31 (.dart) 哇!还有文件中的代码行数: Android:12176 Flutter: 1735

这让人难以置信!我原觉得 Flutter 应用的代码量可能只有原生 Andriod 应用的一半,结果居然减小了 85%?这真的让我始料未及。可是当你开始思考这个问题的时候,你会发现颇有意义:由于全部的布局、背景、图标等都须要在 XML 中指定,可是仍然须要使用 Java 或 Kotlin 代码连接到应用中,固然会存在大量的代码。另外一方面,Flutter 能够同时完成全部这些操做,同时将这些值绑定到 UI 界面中。你能够作到这一切,而不须要处理 Andriod 数据绑定的缺陷,好比设置监听器或处理生成的绑定代码。我开始意识到在 Android 上开发这些基本的功能是多么的麻烦。为何咱们要为 Fragment/Activity 参数、adapter、状态管理和恢复写一堆一样的代码呢?

经过 Flutter,你只会关注你的产品和如何开发产品。SDK 给人的感受更多的是帮助,而不是一种负担。

固然,这仅仅是 Flutter 的开始,由于它仍然处于测试阶段,还没有达到像 Android 的成熟程度。然而,相比之下,Android 彷佛已经达到了极限,咱们可能很快就会用 Flutter 去编写咱们的Andriod 应用。如今还有一些问题有待解决,但总的来讲, Flutter 的将来一片光明。咱们已经为 Android Studio、VS Code 和 IntelliJ 、分析器和视图检查工具提供了很好的插件,并且还会有更多的工具。这一切都让我相信 Flutter 不只会是另外一个跨平台的框架,更是一个更大的开端 —— 应用开发新纪元的开始。

而且 Flutter 能够远远超越 Android 和 iOS 领域。若是你一直在关注小道消息,你可能已经据说谷歌正在开发一款名为 Fuchsia 的新操做系统。事实证实,Fuchsia 的 UI 界面是用 Flutter 所构建的。


固然,你可能会问本身:我如今是否是必须学习一个全新的框架吗?咱们刚刚开始学习关于 Kotlin 和使用一些架构组件,如今一切都很好。为何咱们要去了解 Flutter 呢?可是让我告诉你:在使用 Flutter 以后,你将开始了解 Android 开发的问题,而且能够清楚地看到,Flutter 的设计更适合现代的、响应式的应用。

当我第一使用 Android 的 数据绑定框架 Databinding 时候,我认为它是革命性的,但它也感受像一个不完整的产品。在处理布尔表达式的时候,监听器和更复杂的布局对 Databinding 来讲是冗长乏味的一个步骤,这让我意识到 Andriod 不该是为这样的工具设计的。如今若是你看一下 Flutter,它使用了与 Databinding 相同的理念,它将你的视图或控件绑定到变量中,而无需手动在 Java 或 Kotlin 中实现,同时它不须要经过生成绑定文件来链接 XML 和 Java。这让你能够将以前至少一个 XML 和 Java 文件压缩成一个可重用的 Dart 类。

我还认为,Android 上的布局文件不能单独地作任何事情。它们首先必须调用 inflate 方法,只有这样咱们才能设值。同时引入了状态管理的问题,并提出一个问题:当基础值改变的时候,咱们怎么办?手动抓取对应视图的引用并从新赋值?这种解决方法很是容易出错,我不认为像这样管理视图的方法是好的。相反,咱们应该使用状态来描述咱们的布局,而且每当状态发生变化时,让框架经过从新呈现其值发生变化的视图来接管。这样,咱们的应用程序状态就不会与视图显示的内容不一样步了。Flutter 就是这么作的!

可能还有更多的问题:你有没有曾经问过本身为何在 Android 上建立一个工具栏菜单是如此复杂?为何咱们要用 XML 来描述菜单项,并且在这里咱们不能将任何业务逻辑绑定它上面(这就是菜单的所有目的),咱们只能在 Activity/Fragment 的回调中编写,而后再在另外一个回调中绑定点击监听器。为何咱们不能像 Flutter 同样一次性完成这些事情?

class ToolbarDemo extends StatelessWidget {
  
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        actions: <Widget>[
          new IconButton(
              icon: new Icon(Icons.star), 
              onPressed: _handleClickFavorite
          ),
          new IconButton(
              icon: new Icon(Icons.add), 
              onPressed: _handleClickAdd
          )
        ],
      ),
      body: new MovieDetailScreen(),
    );
  }

  _handleClickFavorite() {}

  _handleClickAdd() {}
}
复制代码

用 Flutter 将菜单 items 添加至 Toolbar。

正如在代码段所见,咱们将菜单 items 做为 Action 添加在 AppBar。这就是你接下来要作的 —— 再也不将图标导入到 XML 文件中,不须要再重写回调了。这就像在控件树上添加一些控件同样简单。


虽然我能够一直往下说,可是你要知道:想一想你不喜欢 Andriod 开发的全部事情,而后考虑如何解决这些问题的同时,从新设计框架。这是一项艰巨的任务,可是这样作能够帮你理解为何 Flutter 会出现,更重要的是,它为何能够留下来。公平地说,有不少应用(从如今开始)我仍然会用原生的 Andriod 和 Kotilin 一块儿编写,原生 Android 也许有它的缺点,但它也有它的好处。可是说到底,我认为用了 Flutter 以后,仍使用原生 Andriod 来开发一个应用会变得愈来愈难。


顺带一提,这两个应用都是开源的,并且都在 PlayStore 上。你能够在这找到: 原生 Android:GithubPlayStore Flutter:GithubPlayStore


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索