[譯] 爲何 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 來實現上面的例子:網絡

  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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索