前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter Web - 优雅的兼容 Flutter App 代码

Flutter Web - 优雅的兼容 Flutter App 代码

作者头像
Swift社区
发布2022-12-12 17:32:18
1.4K0
发布2022-12-12 17:32:18
举报
文章被收录于专栏:Swift社区Swift社区

前言

算最近工作里产出的干货,记录下心得。

与上文一脉相承,上文展示了如何使用 Flutter UI 绘制 Web 页面的架构形态。

但其实还是过于理想了,真实项目里除非是为了折腾而折腾,大部分应该都是奔着降本增效的目的来使用 Flutter UI 渲染代替 Web UI 渲染。

那如何降本增效?复用 App 的 Flutter UI 其实还没办法完全达到目的,最好的方式是整个 App 的 Flutter UI + 业务 Core 都能无缝迁移到 Web 上。达成开发一套 App 送一套 H5,甚至可以套在各种小程序里,一把梭哈(bu shi)。

实现方案

当然是可以的(只要肯献祭程序猿,新世界的大门总能打开)[手动狗头]。

总体分析下 App 现有的 Flutter Code,可以发现需要改造的点有:桥接适配、路由适配、第三方插件库适配、FFI 环境隔离等。

桥接适配

原有桥接只是针对 App 开发的,通过 Flutter MethodChannel 跟 App Native Code 通信。比如 NetworkPlugin 网络桥接,就是调用 Native 的网络层统一进行网络通信,来保证业务逻辑的一致性。

那在 Flutter Web 下,继续去使用 MethodChannel 并不合适,官方针对不同平台的适配,也是提供了一种最佳实践,每个功能独立提供自身的实现,让外部使用者无感知。

比如 flutter_svg 在针对 Web 的实现上:

export '_file_io.dart' if (dart.library.html) '_file_none.dart';

就是通过判断是否是 Web 环境,来是否引用 _file_none.dart。

但并不适合我们桥接改造,原因是对于 App 项目来说,Web 项目是不存在的。我们期望的也不是侵入式实现,让底层承载更多的事,甚至要最少限度修改原有代码(危楼高百起,能不动就别动)。

抽象层独立成一个 lib,减少无关依赖。

示例代码:

  • 抽象层入口
代码语言:javascript
复制
/// 桥接能力套件
///
/// * 桥定义必传,表示各端都需实现
/// * 桥定义非必传,表示差异化实现,使用前需判断是否支持
class GDBridgeKit {
  final INetwork network;
  
  ...
  
  GDBridgeKit({
    required this.network,
  });
}

/// 桥接 API
class GDBridgeAPI {
  /// 网络
  static INetwork get network {
    assert(_kit != null, "必须注册使用");
    return _kit!.network;
  }
  
  ...
  
  static GDBridgeKit? _kit;

  /// 注册套件
  static register(GDBridgeKit kit) {
    _kit = kit;
  }
}
  • 网络桥接抽象
代码语言:javascript
复制
/// 网络相关
abstract class INetwork {
  /// 网络请求
  /// 
  /// [url] 请求地址
  Future request(
    String url,
    ...
  });
}
  • App 注册实现者
代码语言:javascript
复制
class NativeBridgeRegister {
  static init() {
    GDBridgeAPI.register(
      GDBridgeKit(
        network: _Network(),
      ),
    );
  }
}

class _Network extends INetwork {
  @override
  Future request(
    String url,
    ...
  }) {
    // 调用原有 Plugins 实现
  }
}

在 main() 调用注册

代码语言:javascript
复制
void main() {
  /// 注册 Native 桥接
  NativeBridgeRegister.init();
  ...
}

这样,针对 Native Bridge 的架构改造就算完成了,后面就是体力活,把项目中 Bridge 的调用方式替换成 GDBridgeAPI.xxx.xxx。(由于原有代码还是有封装一层,所以改造上只要改封装的那一层即可,量并不算多。)

在 Web 项目里也是如此,构造 WebBridgeRegister 实现相同的接口。但实际上就不是调用 MethodChannel 的桥接,而是上文所说的 TS 通信 API,与 TS 业务层通信。

具体也是举例 Network 这个例子

示例代码:

代码语言:javascript
复制
class _Network extends INetwork {
  @override
  Future request(
    String url,
    ...
  }) async {
    var request = GDRequest();
    request.path = url;
    ...
    var response = await GDPlugin.network.request(request);
    if (response.ok != true) {
      throw Exception(response.error);
    }
    return response.data;
  }
}

关键通信就是 GDPlugin.network.request, 这个是由 TS codegen 生成的代码。

顺便放一下在 Typescript 中是如何定义的。

示例代码:

代码语言:javascript
复制
/*
 * 网络插件
 */
export interface PluginNetwork {
  /**
   * 调用 JS 网络请
   * @param request Request
   */
  request(request: GDRequest): Promise<GDResponse>
}

...

/**
 * Gaoding Web 插件
 */
export class GDPlugin {
  /**
   * 网络请求
   */
  static network: PluginNetwork
  ...
}

...

declare global {
  interface Window {
    GDPlugin: GDPlugin
  }
}

if (!window.GDGlobal) {
  window.GDGlobal = GDGlobal
}

这样在 TS codegen 工具链下就会生成相应的 Flutter 代码,直接链式调用 GDPlugin.network.request

路由适配

在桥接适配中解决了重要的业务调用问题,但还有重要的一点就是路由跳转,这个也是分为2部分需要改造。

路由挂载页面

在 App 中还是用的闲鱼的 flutter_boost (上山容易下山难),所以并没有办法能直接用在 Web 项目中。

在 Web 项目中是用的正统官方推荐的 go_router。

但好处是 App 上页面开发时都是 Page 形式开发的,那需要做的就是 go_router 挂载所需的页面即可。麻烦的是需处理一下每个页面需要的入参,做一些处理。

示例代码:

代码语言:javascript
复制
  // 搜索完成页
  GoRoute(
    name: RouterURL.searchResult,
    path: "/contents",
    builder: (context, state) {
      return DeferredWidget(
        search.loadLibrary,
        () {
          return search.SearchPage().buildPage({
            'keyword': state.queryParams['q'] ?? '',
            'page_source': state.queryParams['from'] ?? '',
          });
        },
        placeholder: const DeferredLoadingPlaceholder(),
      );
    },
  ),

DeferredWidget 是延迟加载,减少首屏加载时间,这个可以从官方示例中找到写法。

路由重定向

只处理页面挂载还是不够的,App 项目里还会有统一的 URL 路由管理,比如 [custom]://search/search 来处理 App 中各个 Native Page、Flutter Page、Web Page 的跳转关系。

这一部分也不能在 App 项目变更,那我们能做的就是把 RouterPlugin 接出来,做一个统一处理。当然,也就是路由桥接适配在 Web 中的实现。

示例代码:

代码语言:javascript
复制
class _Router extends IRouter {
  @override
  Future<bool> pop({
    ...
  }) async {
    var context = GDNavigatorObserver.instance.navigator?.context;
    if (context != null && context.canPop()) {
      context.pop();
    } else {
      GDPlugin.location.href('/');
    }
    return true;
  }
  
  @override
  Future push(
    String url, 
    ...
  ) async {
    if (redirectFlutterRoute.containsKey(url)) {
      // 如果是跳转到 Flutter 页面的路由
      GDNavigatorObserver.instance.navigator?.context.pushNamed(
        redirectFlutterRoute[url]!,
        queryParams: params ?? {},
      );
    } else if (redirectWebRoute.containsKey(url)) {
      // 如果是跳转到 Web 页面的路由
      GDPlugin.location.href(redirectFlutterRoute[url]!); // 调用 window.location.href
    } else if (url.startsWith("http")) {
      // 如果是 Web 链接
      GDPlugin.location.open(url);
    } else {
      debugPrint('url 需接入:$url');
    }
  }
}

第三方库处理

这里我们项目还好,现只有2个坑:

  • flutter_boost 的生命周期兼容问题

我们的解决方式是在 Web 项目中使用一个空实现,page_lifecycle_widget_web.dart

例如:

代码语言:javascript
复制
import 'package:XXX/page_lifecycle_widget.dart'
    if (dart.library.html) 'package:XXX/page_lifecycle_widget_web.dart';
  • flutter_svg 在 web 上出现的坑

报错如上,原因是它自身的实现 export '_file_io.dart' if (dart.library.html) '_file_none.dart'; 在 web 中是使用 _file_none.dart 这里面伪造了一个 File 类产生了冲突。

解决方式 google 了蛮久,其实很简单:

代码语言:javascript
复制
+        dynamic file = File(widget.imageUrl);
         return SvgPicture.file(
-          File(widget.imageUrl),
+          file,

把 file 定义成 dynamic 绕过编译检查就行了 ...

FFI 处理

对于我们项目来说,用到 FFI 的地方都是有 Web 的方式实现了,所以直接屏蔽掉即可。

成效

比如在 App 上较为复杂的搜索页面,适配到 H5 上正常展示也就不到 1 天时间

人力(shi)释放(ye)的又一个途径。

后续

这项目也还在进行中,还有哪些坑后续笔者遇到再分享

- EOF -

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-11-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Swift社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 实现方案
    • 桥接适配
      • 路由适配
        • 路由挂载页面
        • 路由重定向
      • 第三方库处理
        • FFI 处理
        • 成效
        • 后续
        相关产品与服务
        云开发 CloudBase
        云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档