前言 Dart 一開始是由 Google 所推出的一種網頁程式語言,它改善了許多 JavaScript 的歷史包袱,但是很不幸的並沒有受到大眾的支持,反倒是 Microsoft 以 JavaScript 為基礎所發展的 TypeScript 因為原生支援 JavaScript,所以反而使用者日漸增長,不過 Dart 仍然持續默默的發展,在 Flutter 的加持下又重新受到大家的關注,如今 Flutter 藉由 Dart 可轉譯為 JavaScript 的特性,開始計畫性的侵蝕 Web App。
建立專案 伴隨著 Google I/O 2019 發布,Visual Studio Code 上的 Dart 與 Flutter 擴充功能也更新到 3.0 版,最大的特色就是增加 Flutter:New Web Project
指令,讓我們可以建立 Flutter 的 web 專案。
參考資料:http://dartcode.org/releases/v3-0/
我們在 VS Code 內開啟命令選擇區,並透過 Flutter:New Web Project
來建立專案。 一開始會需要我們輸入專案名稱,這邊筆者使用 web 。 接著選擇專案儲存路徑,這邊筆者選擇在 D:\Demo
資料夾內。 從 VS Code 的檔案總管可以看到專案結構跟開發 App 的專案雷同,會有一個 lib
資料夾讓我們存放所撰寫的 Dart 程式,以及一個 web
資料夾存放網頁,App 專案則是建立 android
與 ios
資料夾。
開啟專案設定檔 pubspec.yaml 可以看到引用的 package 會有差異。 pubspec.yaml
pubspec.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 name: web description: An app built using Flutter for web environment: sdk: '>=2.3.0-dev.0.1 <3.0.0' dependencies: flutter_web: any flutter_web_ui: any dev_dependencies: build_runner: ^1.4.0 build_web_compilers: ^2.0.0 pedantic: ^1.0.0 dependency_overrides: flutter_web: git: url: https://github.com/flutter/flutter_web path: packages/flutter_web flutter_web_ui: git: url: https://github.com/flutter/flutter_web path: packages/flutter_web_ui
因為我們還沒安裝過相關的 package,所以我們可以直接利用開發工具提供的快捷鈕執行安裝,當然也可以自己手動執行 flutter package get
指令。
執行 最後我們透過指令 webdev serve
來啟動專案。
如果沒安裝過 webdev 可以透過下列指令安裝:flutter packages pub global activate webdev
如果無法正常執行 webdev ,可以參考上一篇文章(本是同根生:Dart 開發環境 )修改設定試試,或是下載 Dart SDK 來安裝。
透過瀏覽器開啟 http://localhost:8080 ,具有 Flutter App 風格的 Hello, World! 頁面就出現了。
測試一 開啟 lib\main.dart
,可以看到程式結果幾乎完全與 Flutter App 相同,最大的差別就是 material.dart 匯入位置改變了,由原本的import 'package:flutter/material.dart';
變更為import 'package:flutter_web/material.dart';
接下來我們直接將 Flutter App 預設的範例直接覆蓋上去,並修改 material.dart
的 import 路徑。
main.dart 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import 'package:flutter_web/material.dart' ;void main() => runApp(MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo' , theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page' ), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this .title}) : super (key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State <MyHomePage > { int _counter = 0 ; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:' , ), Text( '$_counter ' , style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment' , child: Icon(Icons.add), ), ); } }
儲存 lib\main.dart
就可以發現 webdev 服務會自動編譯。 重新整理網頁可以發現跟 App 一樣,而且也可以正常操作。
Material Icons 從瀏覽器看起來整體效果一樣,但是仔細看就可以發現 icon 並沒有顯示出來,目前官方網頁 也只有一頁說明,但是從 GitHub 官方範例卻可以找到說明。flutter_web/examples/gallery/web/assets/README.md
README.md 1 2 3 4 5 Note: a reference to `MaterialIcons` is intentionally omitted because the corresponding font is not included in this source. If you add `MaterialIcons-Extended.ttf` to this directory, you can update `FontManifest.json` as follows:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 [ { "family" : "MaterialIcons" , "fonts" : [ { "asset" : "MaterialIcons-Extended.ttf" } ] }, { "family" : "GoogleSans" , "fonts" : [ { "asset" : "GoogleSans-Regular.ttf" } ] }, { "family" : "GalleryIcons" , "fonts" : [ { "asset" : "GalleryIcons.ttf" } ] } ]
我們依說明在 web 資料夾 內建立一個 assets
資料夾。 接著在其內建立一個 FontManifest.json
檔案,並加讓 MaterialIcons 。
FontManifest.json 1 2 3 4 5 6 7 8 9 10 [ { "family" : "MaterialIcons" , "fonts" : [ { "asset" : "https://fonts.gstatic.com/s/materialicons/v42/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2" } ] } ]
儲存檔案並重新整理網頁就可以發現右下角的按鈕已經可以顯示圖示了。
測試二 接下來筆者嘗試將之前在 Flutter App 上使用 RxDart 所做的範例移植到專案內,操作畫面如下: 首先直接將檔案 rx.dart
複製到專案內,並修改 import 路徑。rx.dart
程式碼如下:
rx.dart 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 import 'dart:async' ;import 'package:flutter_web/material.dart' ;import 'package:rxdart/subjects.dart' ;class RxPage extends StatefulWidget { @override _RxPageState createState() => _RxPageState(); } final subject = BehaviorSubject<bool >.seeded(false );class _RxPageState extends State <RxPage > { StreamSubscription subscription; bool isLogin = false ; @override void initState() { super .initState(); subscription = subject.listen((value) { setState(() => isLogin = value); }); } @override void dispose() { subscription?.cancel(); super .dispose(); } @override Widget build(BuildContext context) { List <Widget> list = <Widget>[]; if (isLogin) { list.add(UserAccountsDrawerHeader( accountEmail: Text("jonnyhuang@outlooj.com" ), accountName: Text("Jonny" ), currentAccountPicture: CircleAvatar( child: Text("J" ), ), )); } else { list.add(DrawerHeader( decoration: BoxDecoration( color: Colors.orange, ), child: Text("Guest" ), )); } list.add(ListTile( title: Text(isLogin ? "登出" : "登入" ), trailing: Icon(Icons.exit_to_app), onTap: () { subject.add(!isLogin); }, )); return Scaffold( appBar: AppBar( title: Text('RxDart' ), ), drawer: Drawer( child: ListView( children: list, ), ), body: Page1Page(), ); } } class Page1Page extends StatefulWidget { @override _Page1PageState createState() => _Page1PageState(); } class _Page1PageState extends State <Page1Page > { StreamSubscription subscription; bool isLogin = false ; @override void initState() { super .initState(); subscription = subject.listen((value) { setState(() => isLogin = value); }); } @override void dispose() { subscription?.cancel(); super .dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Container( color: isLogin ? Colors.green : Colors.grey, alignment: Alignment.center, child: MaterialButton( child: Text(isLogin ? "登出" : "登入" ), onPressed: () => subject.add(!isLogin), ), ), ); } }
接著因為需要用到 RxDart 這個 ,所以我們開啟 pubspec.yaml
並加入 rxdart package,在 VS Code 內只要儲存 pubspec.yaml 檔,它就會自動幫我們下載 package。 最後修改 lib\main.dart
,將 首頁(home) 換成 RxPage 。 因為有加入新的 package,所以需要重新執行 webdev serve
,開啟網頁可以看到整體操作與 App 上的一樣。
後記 目前 Flutter for Web 還在測試階段,所以仍有可能做大幅度的調整,不過就目前進度來看可以說是大大超出筆者預期,完成度相當高,只要我們 Flutter 專案所使用的 package 本身也支援 web 移植上應該不會有太大瓶頸,當然如果 package 會使用到 Android 或 iOS 的 API 就需要額外處理,相對個其實我們現在開發 App 時就應該要順便考量到 Web 部分。