Flutter 生成執行小程式的混合App開發實踐

FinFish發表於2023-02-08

目前的疑惑

微信小程式發展的越來越快,目前小程式甚至取代了大部分 App 的生態位,公司的坑位不增反降,只能讓原生應用開發兼顧或換崗進行小程式的開發。
以我的實際情況來講,公司應用採用的 Flutter 框架,同樣的功能不可避免的就會存在 Flutter 應用開發和微信小程式開發兼顧的情況,這種重複造輪子的工作非常低效。

為什麼會存在這種情況?

隨著 2019 年5月 Google I/O 上 Flutter 1.5.4 的釋出,宣示著 Flutter 真正開始進入全終端時代,意味著只需要寫一份程式碼,不需要任何額外的修正改,就可以執行在 iOS、Android、Web、PC 上。Flutter 正在革命性的改變移動開發的生態系統,從面向各個終端的開發,轉向面向框架開發,不僅會改變開發者的開發方式,也有越來越多的公司開始關注使用 Flutter。
Flutter 作為一個跨平臺的框架,其開發技術棧融合了 Native 和前端的技術,不僅涉及到了 Native(Android、iOS )的開發知識,又吸取了很多前端(例如 React)的技術理念和框架,並且在此基礎上又有提升,形成 Flutter 自己獨特的技術思維。

Flutter 生成執行小程式的混合App開發實踐


但目前來講,Flutter 並不支援小程式,Flutter for Web 雖然最後也會生成 JS 程式碼,但是 Flutter 生成的 JS 和 CSS 都是不能修改的。而在 Flutter 中也沒辦法透過 Dart 直接呼叫小程式的介面,所以現階段用 Flutter 開發小程式不是太好的選擇。

一些解決思路的產生

但是公司和業務也不得不向著網際網路巨頭的流量低頭,同時小程式的逐漸風靡,也使得使用者下載 App 的習慣產生變化,不管購物、訂餐還是辦事都會首先查詢“開啟即用,即用即走”的小程式可以使用,省去了下載 App 的繁瑣流程。
當然也知道很多開發者對於小程式是有非常多意見的,App 也不會說死就死,畢竟 App 相對於小程式來講,還是有很多優勢。所以 App 和小程式開發都共存的情況下,如何解決效率問題? 能否讓過往開發的小程式直接執行在 Flutter 開發的應用中呢?同樣一個功能業務僅需一次小程式開發,即可實現在除了微信端的其它 App 中也執行起來。
在 Google 找相關的解決方案和資料的時候,發現國外幾乎沒有這種方案,國內倒是有廠商在做這塊,想想也確實符合情理。基於公司 Flutter 框架的基礎現實情況下,名為 FinClip 小程式容器技術的產品是能夠支援除原生 iOS、Android 之外的 Flutter 和 React Native ,於是大概測試了下這個產品。

實操上手過程

原理其實挺簡單的,FinClip 提供了小程式 SDK 給 Flutter 應用進行整合,這樣以來 App 即擁有了一套可執行小程式業務程式碼的宿主環境。


Flutter 生成執行小程式的混合App開發實踐

1、獲取憑據

整合 SDK 需要在 FinClip平臺 中建立應用並繫結小程式,獲得每個應用專屬的 SDK KEY 及 SDK SECRET ,隨後可以在整合 SDK 時填寫對應的引數。開啟小程式時 SDK 會自動初始化,並校驗 SDK KEY,SDK SECRET 與BundleID (Application ID) 是否正確。

2、整合外掛

在專案 pubspec.yaml 檔案中新增依賴。
mop: latest.version
如果電腦是 mac M1 晶片,還需要在 iOS 資料夾的 Podfile 檔案增加以下3行程式碼
config.build_settings['ENABLE_BITCODE'] = 'NO'config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386'


示例:

post_install do |installer|  installer.pods_project.targets.each do |target|    flutter_additional_ios_build_settings(target)    target.build_configurations.each do |config|      config.build_settings['ENABLE_BITCODE'] = 'NO'      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'      config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386'    end  endend

3、Flutter API

在整合後,使用 SDK 提供的 API 之前必須要初始化 SDK 。下面我羅列官方的一些必要的 API ,更具體的也可以查閱 官方文件
1)初始化 sdk 介面
/// /// /// initialize mop miniprogram engine. /// 初始化小程式 /// [sdkkey] is required. it can be getted from api.finclip.com /// [secret] is required. it can be getted from api.finclip.com /// [apiServer] is optional. the mop server address. default is /// [apiPrefix] is optional. the mop server prefix. default is /api/v1/mop /// [cryptType] is optional. cryptType, should be MD5/SM /// [disablePermission] is optional. /// [encryptServerData] 是否對伺服器資料進行加密,需要伺服器支援 /// [userId] 使用者id /// [finStoreConfigs] 多服務配置 /// [uiConfig] UI配置 /// [debug] 設定debug模式,影響除錯和日誌 /// [customWebViewUserAgent] 設定自定義webview ua /// [appletIntervalUpdateLimit] 設定小程式批次更新週期 /// [maxRunningApplet] 設定最大同時執行小程式個數 /// Future<Map> initialize(   String sdkkey,   String secret, {   String? apiServer,   String? apiPrefix,   String? cryptType,   bool encryptServerData = false,   bool disablePermission = false,   String? userId,   bool debug = false,   bool bindAppletWithMainProcess = false,   List<FinStoreConfig>? finStoreConfigs,   UIConfig? uiConfig,   String? customWebViewUserAgent,   int? appletIntervalUpdateLimit,   int? maxRunningApplet, })



2)開啟小程式

/// open the miniprogram [appId] from the  mop server./// 開啟小程式/// [appId] is required./// [path] is miniprogram open path. example /pages/index/index/// [query] is miniprogram query parameters. example key1=value1&key2=value2/// [sequence] is miniprogram sequence. example 0,1.2.3,4,5.../// [apiServer] is optional. the mop server address. default is /// [apiPrefix] is optional. the mop server prefix. default is /api/v1/mop/// [fingerprint] is optional. the mop sdk fingerprint. is nullable/// [cryptType] is optional. cryptType, should be MD5/SMFuture<Map> openApplet(  final String appId, {  final String? path,  final String? query,  final int? sequence,  final String? apiServer,  final String? scene,})


3)獲取當前正在使用的小程式資訊當前小程式資訊包括的欄位有appId,name,icon,description,version,thumbnail

///  ///  get current using applet  ///  獲取當前正在使用的小程式資訊  ///  {appId,name,icon,description,version,thumbnail}  ///  ///  Future<Map<String, dynamic>> currentApplet()


4)關閉當前開啟的所有小程式

///  /// close all running applets  /// 關閉當前開啟的所有小程式  ///  Future closeAllApplets()

4、官方示例

官方給了一個例項,我也直接放上來,大家可以參照下。
import 'package:flutter/material.dart';import 'dart:async';import 'dart:io';import 'package:mop/mop.dart'; void main() => runApp(MyApp()); class MyApp extends StatefulWidget {    @override    _MyAppState createState() => _MyAppState();} class _MyAppState extends State<MyApp> {    @override    void initState() {        super.initState();        init();    }     // Platform messages are asynchronous, so we initialize in an async method.    Future<void> init() async {        if (Platform.isIOS) {            //com.finogeeks.mopExample            final res = await Mop.instance.initialize(                '22LyZEib0gLTQdU3MUauARlLry7JL/2fRpscC9kpGZQA', // SDK Key                '1c11d7252c53e0b6', // SDK Secret                apiServer: ' // 伺服器地址                apiPrefix: '/api/v1/mop' // 伺服器介面請求路由字首                );            print(res);        } else if (Platform.isAndroid) {            //com.finogeeks.mopexample            final res = await Mop.instance.initialize(                '22LyZEib0gLTQdU3MUauARjmmp6QmYgjGb3uHueys1oA', // SDK Key                '98c49f97a031b555', // SDK Secret                apiServer: ' // 伺服器地址                apiPrefix: '/api/v1/mop' // 伺服器介面請求路由字首                );            print(res);        }        if (!mounted) return;    }     @override    Widget build(BuildContext context) {        return MaterialApp(            home: Scaffold(            appBar: AppBar(            title: const Text(' FinClip 小程式 Flutter 外掛'),        ),            body: Center(            child: Container(            padding: EdgeInsets.only(            top: 20,        ),            child: Column(            children: <Widget>[            Container(                decoration: BoxDecoration(                    borderRadius: BorderRadius.all(Radius.circular(5)),                    gradient: LinearGradient(                        colors: const [Color(0xFF12767e), Color(0xFF0dabb8)],                        stops: const [0.0, 1.0],                    begin: Alignment.topCenter,                end: Alignment.bottomCenter,            ),        ),            child: FlatButton(            onPressed: () {            Mop.instance.openApplet('5e3c147a188211000141e9b1'); // 小程式 AppID        },        child: Text(            '開啟示例小程式',            style: TextStyle(color: Colors.white),            ),            ),            ),            SizedBox(height: 30),            Container(            decoration: BoxDecoration(            borderRadius: BorderRadius.all(Radius.circular(5)),            gradient: LinearGradient(            colors: const [Color(0xFF12767e), Color(0xFF0dabb8)],            stops: const [0.0, 1.0],            begin: Alignment.topCenter,            end: Alignment.bottomCenter,            ),            ),            child: FlatButton(            onPressed: () {            Mop.instance.openApplet('5e4d123647edd60001055df1', sequence: 1); // 小程式 AppID            },            child: Text(            '開啟官方小程式',            style: TextStyle(color: Colors.white),            ),            ),            ),            ],            ),            ),            ),            ),            );            }            }




最後的話

目前我是基於我個人的實際情況而找到的方案,如果大家有更好的方案也歡迎留言討論交流。



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70021577/viewspace-2934365/,如需轉載,請註明出處,否則將追究法律責任。

相關文章