2024年7月11日 星期四

記錄前端工程師首次踏足flutter,把你的靜態網頁顯示出來

想來是任務來了,公司給了一項任務希望把網頁專案包進apk中,我原本想說這cordova可以搞定的事情,不用外包了我來做,說不定還能賺點獎金。事情就是這麼開始了

創建一個新的cordova專案,最後在cordova build android的時候卻噴錯

BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 66
> Unsupported class file major version 66

上網查好像跟gradle和JDK版本有關,這種鎖版本的事情最頭痛了,況且我跟這兩個傢伙又不熟,所幸來玩一下flutter。

但光是瀏覽器要啟動就不少問題,經過水哥大大的開導,原來除了裝好xcode,還要進到軟體中新增瀏覽器,然後建議開發使用官方推薦IDE, android studio.....

這樣確實點open ios simulator就會叫出ios模擬器了,但最終我是要包出apk啊,應該需要的是android模擬器吧,但在Tools > Device Manage裡面卻看到....



your CPU not support VT-x啥鬼,看似無法支援虛擬機,貌似是mac m2晶片導致,但我特地點ARM Images了怎麼還是不支持,要知道現在都2024年了啊,還有廠商敢不支持apple chip!上網查到這個github有人把模擬器做成了m1版本,可以當成app開啟,安裝方法跟dmg一樣,我想說將就著用吧。

當然我對flutter一竅不通的情況下,我請教了claude大神,最終總結出使用flutter_inappwebview這個套件在手機架設類似server的服務,把web資料夾下的index.html顯示出來。然後馬上遇到第一個問題

assets需要列出所有資料夾,我翻了一下文件,無法使用萬用字符去匹配,所以必須列出每一個子資料夾,然後我看了一下專案.....我是不是應該放棄呢。

最終我寫了一個nodejs腳本用遞迴去掃特定資料夾的所有子資料夾並輸出特定格式,可以直些複製貼上
const fs = require('fs');
const path = require('path');

// 指定要遍歷的目錄
const directoryPath = './web';

function logFolders(dir, baseDir) {
// 讀取目錄內容
fs.readdirSync(dir).forEach(file => {
const fullPath = path.join(dir, file);
const relativePath = path.relative(baseDir, fullPath);

// 檢查是否為目錄
if (fs.statSync(fullPath).isDirectory()) {
// 輸出相對路徑
console.log(` - web/${relativePath}/`);
// 遞迴處理子目錄
logFolders(fullPath, baseDir);
}
});
}

// 開始遍歷
console.log('Folders in the specified directory:');
logFolders(directoryPath, directoryPath);

// pubspec.yaml
flutter:

uses-material-design: true

assets:
- web/web/
- web/web/assets/
- web/web/public/
- web/web/public/assets/
- web/web/public/assets/app/
- web/web/public/assets/icons/
- web/web/public/assets/orderposttips/
- web/web/public/carousel
......最終就列出所有資料夾,順利闖過第一關
然後claude給了一個很天才的做法,把所有資源檔複製到瀏覽器目錄,然後在該目錄啟動伺服器...這乍聽之下好像沒什麼問題,但話鋒一轉,好像有點脫褲子放屁啊,在原本靜態資料夾啟動伺服器就好拉,所以最終程式碼簡化成以下

// lib/main.dart
import 'package:flutter/material.dart';
import 'web_view_page.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Web Project',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const WebViewPage(),
);
}
}

// lib/web_view_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';

class WebViewPage extends StatefulWidget {
const WebViewPage({Key? key}) : super(key: key);

@override
_WebViewPageState createState() => _WebViewPageState();
}

class _WebViewPageState extends State<WebViewPage> {
InAppWebViewController? _webViewController;
String _url = "";
late InAppLocalhostServer _localhostServer;

@override
void initState() {
super.initState();
_setupServer();
}

Future<void> _setupServer() async {
try {

_localhostServer = InAppLocalhostServer(
documentRoot: 'web/', //webDir.path,
port: 8080,
);
await _localhostServer.start();

setState(() {
_url = 'http://localhost:8080/web/index.html';
});

print('Server URL: $_url');
} catch (e) {
print('Error in _setupServer: $e');
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(title: const Text('')),
body: _url.isEmpty
? const Center(child: CircularProgressIndicator())
: InAppWebView(
initialUrlRequest: URLRequest(url: WebUri(_url)),
onWebViewCreated: (controller) {
_webViewController = controller;
},
onLoadError: (controller, url, code, message) {
print('WebView load error: $code, $message');
},
onConsoleMessage: (controller, consoleMessage) {
print('Console message: ${consoleMessage.message}');
},
),
);
}

@override
void dispose() {
_localhostServer.close();
super.dispose();
}
}
最後再把一些安全設置給設置上,因為android9以後是不允許連http的,所以要設定一些東西

// add file android/app/src/main/res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>

<network-security-config>

<base-config cleartextTrafficPermitted="true">

<trust-anchors>

<certificates src="system" />

</trust-anchors>

</base-config>

</network-security-config>

// android/app/src/main/AndroidMenifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:targetSandboxVersion="1">
<application
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config"
        ......
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
</manifest>

終於!!還是畫面全白...靠腰又沒有任何報錯,嘗試了一個下午的我,最終轉念一想,flutter既然主打跨平台畫面一樣,那ios可以開啟android應該也可以開啟吧。
所以我先嘗試用ios打開看看,確實可以開啟,正當我想打包時,我想到那個m1 android simulator該不會是個閹割產品吧!!但又會遇到CPU not support那個問題==這時不信邪在爬文一次看到了曙光,最終解法是
Android Studio > Tools > SDK Manager; SKD Tools > Android Emulator打勾,OK,然後安裝完,再去Device Manager 你就會看到剛剛所有的紅字都消失了,跟看到自己體檢沒有紅字一樣開心!!




就這樣我創建了全新的android simulator,再把上面的程式碼編譯後,你猜怎麼著??打包下班!!

7/12更新
這什麼鳥專案,https網站裡面參雜http圖片載入,我印象中應該不會允許啊,後來發現chrome會自動把http的圖片轉https,如果圖片沒有https,我上網查應該是header要加點什麼,總之~
這要我這個專案怎麼搞啊!!
結果還真讓我找到解法就在InAppWebView時增加一個設定,搞定~
InAppWebView(
initialUrlRequest: URLRequest(url: WebUri(_url)),
onWebViewCreated: (controller) {
_webViewController = controller;
},
onConsoleMessage: (controller, consoleMessage) {
print('Console message: ${consoleMessage.message}');
},
    // 加這段
initialSettings: InAppWebViewSettings(
mixedContentMode:
MixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW),
),

沒有留言:

張貼留言