[譯] 為什麼 Flutter 能最好地改變移動開發

ALVIN君發表於2018-04-23

為什麼 Flutter 能最好地改變移動開發

如果你是一個 Android 開發者,那麼你應該聽說過 Flutter。這是一個相對來說比較新的,用於製作跨平臺原生應用的簡單框架。這不是同類產品中的第一款,但它正被谷歌使用,這讓它有了一定的可信度。儘管我一開始聽到這個框架的時候對此有所保留,但我還是心血來潮地決定給它一個機會 —— 這在一週內極大地改變了我對移動開發的看法。以下是我學到的。

[譯] 為什麼 Flutter 能最好地改變移動開發

“長喙蜂鳥在空中飛翔”,攝影者:來自 UnsplashRandall Ruiz

在我們開始之前,讓我增加一個簡短的免責宣告。我在這篇文章中編寫的和將要引用的應用程式是相對基礎的,並且不會包含大量的商業邏輯。這雖然沒什麼特別的,但是我想把我從原生 Android 應用移植到 Flutter 中學到的知識和經驗分享給大家,這也是我最好的例子。該應用沒有在優化架構方面作出任何努力,這純粹是為了體驗開發和使用框架本身。


整整一年前,我在 Play Store 上釋出了我的第一個 Android 應用。這個應用 (Github) 在架構和編碼規範方面都非常基礎。這是我的第一個大型開源專案,這表明,我從事 Android 應用開發已經很久了。我在一家代理公司工作,平時會花時間在一些有著不同技術和架構的專案上,包括 Kotlin、Dagger、RxJava、MVP、MVVM、VIPER 等等,這些都很大程度上提高了我的 Android 開發能力。

話雖這麼說,在過去的幾個月裡,我一直對 Android 框架非常失望,尤其是其相容性差,在開發應用時經常會發現它違反直覺的地方。更別提編譯構建的時間了……(我推薦你讀一下這篇文章,其中有更深入的細節分析),儘管 Kotlin 和類似 Databinding 這樣的工具能讓問題有所改善,但整個情況還是感覺在一個太大而無法癒合的傷口上貼創可貼。下面開始瞭解 Flutter。


幾個星期前,當 Flutter 進入 beta 測試版的時候,我就開始使用它了。我看了一下官方文件(順便一提,寫的很棒),然後開始瀏覽程式碼實驗室和指南。我開始逐漸理解 Flutter 背後的基本理念,並決定自己試一試,看能不能把它付諸於實踐。我開始思考我應該先做一個什麼樣的專案,我決定重寫我的第一個 Andriod 專案。這似乎是一個恰當的選擇,因為這能讓我將同樣的“第一次的努力”在兩個對應的框架下進行比較,而同時對應用架構等等的方面不作太多關注。它純粹是通過開發一組定義好的功能特性來了解 SDK。

我首先建立了網路請求,解析 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 誕生了。


你可以把 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 能最好地改變移動開發

已完成的電影列表功能。


嗯,這很簡單。幾乎是太簡單了…… 意識到用 Flutter 來建立一個 list 是多麼容易,這就激起了我的好奇心,讓我更加興奮地用它來繼續開發。

下一步來弄清楚如何使用更加複雜的佈局。原生應用程式的電影細節頁面有一個相當複雜的佈局,包括約束佈局和一個應用程式欄。我認為這是使用者所期望和欣賞的功能,如果 Flutter 真的想有機會與 Andriod 對抗,它需要能夠提供更復雜的佈局,就像這樣。讓我們看看我建立了什麼:

[譯] 為什麼 Flutter 能最好地改變移動開發

電影細節的頁面。

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

相關文章