初識Flutter web

QiShare發表於2019-12-23

級別:★☆☆☆☆
標籤:「Flutter web」「Dart Server」「blocked by CORS Policy」「跨域」
作者: WYW
審校: QiShare團隊

前言 筆者最近了解了Flutter web相關的內容,本文會分享建立Flutter web專案、Flutter web專案預覽,Flutter web專案和Flutter mobile(Flutter Android/iOS)專案的差別、搭建簡易Dart伺服器(解決跨域問題)、上線Flutter web專案相關內容。

一、建立Flutter web 專案

準備Flutter web 環境

更新本地環境為 beta channel最新版。(dev channel 也可以)

flutter channel beta

flutter upgrade

Flutter有如下4個channel:

flutter channel
Flutter channels:
  beta
* dev
  master
  stable
複製程式碼

Flutter 官方建議使用 stable 的channel。

master 是當前最新的channel;

dev 是當前最新的充分測試後的channel;

beta是每個月Flutter官方調整選出來的最好的dev的channel,並提升為beta channel;

stable是Flutter 認為是當前最穩定的channel。

穩定性而言:master < dev < beta < stable 。更多內容可檢視:Flutter build release channels

開啟專案支援Flutter web

flutter config --enable-web

如果想在當前已有專案Flutter mobile專案的基礎上,新增Flutter web支援,可 cd 到 Flutter mobile 專案目錄下,新增Flutter web支援。

新建Flutter web專案

如果之前沒有建立過Flutter 專案,新建一個Flutter web專案可以使用如下命令。

flutter create 專案名(小寫) 如:flutter create qi_flutter_web_demo

現有專案生成Flutter web 相關檔案

如果之前建立過Flutter 專案,想現有專案生成web資料夾及index.html等檔案可使用如下命令。

flutter create .

Flutter web 新增index.html 等

執行專案命令:flutter run -d chrome

遇到問題:執行失敗

執行失敗報錯如下:

wangyongwangdeiMac:qi_flutter_page wangyongwang$ flutter run -d chrome

Flutter assets will be downloaded from storage.flutter-io.cn. Make sure you trust

this source!

Downloading Web SDK... 1.1s

Launching lib/main.dart on Chrome in debug mode...

Error compiling dartdevc module:qi_flutter_page|lib/main_web_entrypoint.ddc.js

packages/qi_flutter_page/main_web_entrypoint.dart:9:18: Error: Too few positional arguments:

1 required, 0 given.

entrypoint.main();

       ^                                                             
複製程式碼

AssetNotFoundException: qi_flutter_page|lib/main_web_entrypoint.ddc.js

Failed after 23.3s

Building application for the web... 33.5s

Failed to build application for the Web.

猜測原因:訪問網址https://storage.flutter-io.cn.不可達

起初,筆者猜測原因是這個網址https://storage.flutter-io.cn.訪問不可達;不過試過執行新建立的Flutter web專案,發現新建的Flutter web專案可以正常執行,可以排除問題不在於網址https://storage.flutter-io.cn.訪問不可達。

Document not found

繼續看這段報錯,可以發現Flutter web 專案的main 方法中不能有引數。

packages/qi_flutter_page/main_web_entrypoint.dart:9:18: Error: Too few positional arguments: 1 required, 0 given. entrypoint.main(); ^

AssetNotFoundException: qi_flutter_page|lib/main_web_entrypoint.ddc.js
Failed after 22.9s

問題在於main方法中引數

執行Flutter web 專案的時候,main方法中不能有引數。

void main(List<String> args) {

}

// 刪除main方法名中的引數後,可以正常執行。

void main() {

}
複製程式碼

筆者以之前寫的專案qi_flutter_page為例:執行起來的效果如下:

Flutter web 專案預覽

Flutter web 專案預覽

上週和同事CH聊天學到的內容:Flutter web專案顯示的網頁的特點:

顯示網頁原始碼的時候,可以網頁發現顯示的內容是html的body 中巢狀的main.dart.js。

顯示頁面原始檔

頁面原始檔

二、Flutter web 專案預覽

執行Flutter web專案 預設會在Chrome瀏覽器中顯示,不過在本機的Safari 瀏覽器中及模擬器中的瀏覽器中輸入相應的網址,也可以顯示相應的檢視。

Flutter web 專案預覽

三、Flutter web專案 與 Flutter mobile 專案的不同

筆者在把現有Flutter mobile專案,直接支援Flutter web 的過程中遇到了網路請求報異常的問題,另外簡單測試了2個三方庫的在Flutter web專案中的體現。

HttpClient() 不能用於Flutter web 專案

try {
  HttpClient client = HttpClient();
} catch (e) {
  print('捕獲異常:$e');
}
複製程式碼

捕獲異常:NoSuchMethodError: invalid member on null: 'indexOf'

Flutter web專案的網路請求可以使用html.httpRequest。

import 'dart:html' as html;

html.HttpRequest.request(url).then((responseValue) {
     
 });
複製程式碼

三方庫支援情況

筆者這裡舉2個自己使用過三方庫,shared_preferences、url_launcher。

下列程式碼對於Flutter web 專案中仍然支援開啟載入url的視窗。

String soUrl = 'https://www.so.com';
if (await canLaunch(soUrl)) {
  await launch(soUrl);
}
複製程式碼

由如下程式碼及相應結果可知,shared_preferences 也支援 Flutter Web 專案。

void _incrementCounter() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    int counter = (prefs.getInt('counter') ?? 0) + 1;
    print('Pressed $counter times.');
    await prefs.setInt('counter', counter);
  }
複製程式碼

ListTile2 Pressed 1 times. ListTile2 Pressed 2 times. ListTile2 Pressed 3 times. ListTile2 Pressed 4 times. ListTile2 Pressed 5 times.

三方庫一般會註明支援的平臺(Android、iOS或Web)。 url_launcher 5.4.1支援 Flutter web 專案。 shared_preferences 支援 Flutter web 專案。 sqflite自己註明了支援Android和iOS,是否支援Flutter web沒有做說明。

如下圖所示:

url_launcher

shared_preferences

sqflite

四、簡易Dart伺服器

使用如下程式碼,可以本地啟動一個Dart服務。

main() async {
  var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 9988);
  await for (var request in server) {
    request.response
      ..headers.contentType = ContentType('text', 'plain', charset: 'utf-8')
      ..write('Hello Dart! 你好Dart')
      ..close();
  }
 }
複製程式碼

瀏覽器中直接請求http://127.0.0.1:9988 示意圖如下:

簡易Dart 伺服器示意

筆者在Flutter web專案中請求,http://127.0.0.1:9988的時候,遇到了跨域問題,下邊分享下相關問題及處理方式。

跨域問題

跨域問題描述

跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓執行在一個 origin (domain) 上的Web應用被准許訪問來自不同源伺服器上的指定的資源。當一個資源從與該資源本身所在的伺服器不同的域、協議或埠請求一個資源時,資源會發起一個跨域 HTTP 請求

比如,站點 domain-a.com 的某 HTML 頁面通過 的 src 請求 domain-b.com/image.jpg。網…

出於安全原因,瀏覽器限制從指令碼內發起的跨源HTTP請求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 這意味著使用這些API的Web應用程式只能從載入應用程式的同一個域請求HTTP資源,除非響應報文包含了正確CORS響應頭。 引自HTTP訪問控制(CORS)

舉2個例子比如我們自身當前域名為abc.com, 訪問def.com 會出現跨域的問題。 比如我們自身當前域名為abc.com埠號為1234(abc.com:1234),那麼訪問abc.com:5678也會出現跨域問題。

筆者使用Flutter web專案請求服務端資源的時候遇到了跨域問題。

http://localhost:55355/#/ 中的內容訪問http://127.0.0.1:9988

Flutter web專案跨域現象圖現象圖如下:

Flutter web專案跨域問題

出現當前跨域問題的原因是埠號不同,訪問Flutter web專案的url 和 請求服務端資源的url的 埠號 不同。

請求的響應頭中設定可跨域的origin,解決跨域問題

設定跨域的url 有2種設定方式:

  • 1.設定一個或多個url;
    • 如:request.response ..headers .add('Access-Control-Allow-Origin', request.headers['origin'])
  • 2.設定跨域的值為*;
    •   ..headers.add('Access-Control-Allow-Origin', '*')
      複製程式碼

注意:如果在本地測試使用,可以使用第二種方式,直接了當。但是一般線上的話最好使用第一種方式設定是否可以跨域請求。因為設定是否可以跨域,算是伺服器在響應瀏覽器請求資料時的一種保護策略。

其中重點是在響應頭中新增可以跨域的請求域。

..headers.add('Access-Control-Allow-Origin', 'http://localhost:55355')

如'Access-Control-Allow-Origin'可以指定特定的url,使url能夠跨域請求。

如有需要指定允許多個url進行跨域請求。可以根據請求的origin的值,判斷是否要做跨域響應頭的處理。

如:如下程式碼設定了當請求的origin 為http://localhost:63062http://localhost:55355 的時候,會新增跨域處理的響應頭。

main() async {
  var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 9988);

  var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 9988);
  await for (var request in server) {
    var accessControlAllowOrigin = [
      'http://localhost:63062',
      'http://localhost:55355'
    ];
    if (request.headers['origin'] != null) {
      for (String tempAllowOrigin in accessControlAllowOrigin) {
        if (request.headers['origin'].first.contains(tempAllowOrigin)) {
          request.response
            ..headers
                .add('Access-Control-Allow-Origin', request.headers['origin'])
            // ..headers.add('Access-Control-Allow-Origin', '*')
            ..headers.contentType =
                ContentType('text', 'plain', charset: 'utf-8')
            ..write('Hello Dart! 你好Dart 跨域')
            ..close();
        }
      }
    } else {
      request.response
        ..headers.contentType = ContentType('text', 'plain', charset: 'utf-8')
        ..write('Hello Dart! 你好Dart 不需要跨域')
        ..close();
    }
  }
 }
複製程式碼

解決跨域問題

筆者在上邊說明了處理執行Flutter web專案的時候,處理本地服務端介面和Flutter web專案執行網址出現跨域問題的處理方式。(其實對於編譯後的Flutter web專案的產物直接放到自己的服務端專案的的靜態檔案目錄下的時候,不會出現上述問題) 有時候我們的請求內容可能就需要跨域去請求資料,而且對方如果也不便新增相應的跨域響應頭。此時,可使用Nginx 做反向代理來處理跨域問題。

Nginx 反向代理解決遠端跨域問題
server {
        listen       9080;
        server_name  localhost;
        
        location ~ /columns/Qtest {
            proxy_pass https://testerhome.com;
        }
    }
複製程式碼

經上述處理,可以在本地的127.0.0.1:9080/columns/Qtest請求到 Qtest測試之道testerhome.com/columns/Qte… 相應資料。

本地Flutter web 專案跨域訪問TesterHome Qtest測試之道

Nginx 配置反向代理及rewrite訪問路徑可實現訪問遠端檔案不跨域。

        location / {
           proxy_pass https://weekly.75team.com;
        }
        
        location ~ /api/qiwuzhoukanWeb {
            rewrite /api/qiwuzhoukanWeb /;
            proxy_pass https://weekly.75team.com;
        }
複製程式碼

經上述處理,可以在本地的127.0.0.1:9080/api/qiwuzhoukanWeb請求到 奇舞週刊weekly.75team.com 相應資料。

本地Flutter web 專案跨域訪問奇舞週刊

五、Flutter web 專案上線

flutter build web會在專案的build 目錄中生成相應的資原始檔及html 和js檔案,把相關檔案放置到服務端靜態檔案目錄下即可。實現上線Flutter Web專案。

Flutter web專案編譯產物

參考學習網址


瞭解更多iOS及相關新技術,請關注我們的公眾號:

初識Flutter web

小編微信:可加並拉入《QiShare技術交流群》。

初識Flutter web

關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)

推薦文章:
用AdHoc來測試iOS線上推送 Swift 5.1 (9) - 結構體和類
Swift 實現一個相容iOS、tvOS、OSX的抽象層
iOS Password AutoFill
iOS 給UILabel新增點選事件
用SwiftUI給檢視新增動畫
用SwiftUI寫一個簡單頁面
Swift 5.1 (8) - 列舉型別 iOS App啟動優化(三)—— 自己做一個工具監控App的啟動耗時
iOS App啟動優化(二)—— 使用“Time Profiler”工具監控App的啟動耗時
iOS App啟動優化(一)—— 瞭解App的啟動流程
奇舞週刊

相關文章