Flutter For Web:人人都是大前端开发

2020-12-23 16:28

阅读:588

标签:分离   out   接口   互动   code   设置   官方   callback   pack   

Flutter For Web:人人都是大前端开发

技术图片
图片
作者|涂金林
编辑|王文婧
Flutter For Web 已经发布半年多时间,跑在 Flutter 实践道路上的腾讯企鹅辅导团队是如何应用的?
今年 9 月,作为腾讯 Flutter 实践团队之一的我们,有幸参与了 GDD 大会上 Flutter 应用视频的录制,感受到国内众多开发者对 Flutter 的热情。两天时间,讲道理,其实没有太多的干货,但收获还是满满的。有那么点空闲的时间,也关注了一下其他的同学,大家讨论的重心还是第一天主会场的内容。我想吸引大家的一直是 Flutter 新版本 stable1.9 的重磅发布。
新版本一个重要功能就是 Flutter For Web 仓库合入 Flutter master 主仓库,意味着我们可以真正地使用一套代码、一套资源部署大前端。辅导团队经过一段时间的准备,使用 Flutter 开发的 Web 页面也即将发布,希望和大家分享下实践过程和踩坑实例,欢迎一起交流探讨。

一、发布准备

我们对辅导 App 中上课页学习简报页面进行改造,支持浏览器中打开。

iOS 中打开 Flutter Web 页面

浏览器中打开 Flutter Web 页面
网络 cgi 接口现网暂时没有配置,先使用假数据展示。开发过程中,我们涉及到 Flutter 页面转换、网络请求、与 Native 交互、cookie 等,最终的页面发布我们还需要在发布系统中配置。有关基础开发及发布的知识,大家可以关注互动视频大神 weiwei 的一篇分享:《如何使用 Flutter For Web 开发一个需求并上线》,文章介绍的很清楚,最终发布上线需要的产物是:
index.html:h5 访问入口;
main.dart.js:dart 代码转化成 js 后的产物;
assets:静态资源,包括图片、字体,以及我们定制的 js 文件。技术图片

二、页面改造

Flutter For Web 谷歌官方一直建议暂时不要在生产环境使用。但如果不尝试,我们永远不知道这里会藏有什么样的秘密,什么样的场景适合。对现有 Flutter 改造,增加对浏览器的支持,突破而不失稳重。

  1. 增加 Web 支持
    我们原先的版本使用 Flutter 1.5.4,增加 Web 支持,我们需要切换到最新的 master 分支,改造运行。技术图片

如果我们只是新建一个新的项目,只需切换到最新的 master 分支,创建一个新的项目即可。创建完成之后,根目录下会多出一个 web 文件夹,里面只有一个 web 入口文件 index.html。技术图片

2. 修改 dart:io 库

原有项目中如果直接运行 flutter run -d chrome,会发现控制器中 import 报错,原因是 dart: io 库不支持 web。io 库是 Flutter 中非常常用的库,主要是平台相关的一些 api,改造的第二步我们就需要屏蔽 dart:io 的引入。
Flutter For Web 最终运行的是在浏览器中的 js 代码,Flutter For Phone 使用,Platform 引擎与 Native 通信。js 在平台上是通过其他的系统支持(iOS 中的 JavaScriptCore) 与 Native 通信,二者是完全不同的方式,所以 dart: io 无法继续支持。
既然 dart: io 不支持 web,那我们仍然想使用原先的 Flutter 业务 UI 代码,该 如何实现(上文我们说过,我们想使用同一套代码、同一套资源整合大前端)?我们使用不同平台下支持的能力库区分。


import ‘main_web.dart‘ if (dart.library.io) "main_io.dart"; //dart.library.io

技术图片
UI 侧我们仍然使用同一套代码,逻辑侧通过 library 库分区 native 还是 web,逻辑侧对应不同的平台实现。这几天,我看到一个新的区分方法,简单而且实用:web 侧我们可以判断 0 和 0.0 是否是一个对象。
技术图片
区分完不同平台,我们还需要辨别不同设备,这里我建议搭建 app 初始化的时候配置完成,后续方便实用。

//init
Config.inWeb = identical(0, 0.0);
/////////////////////////////////////

enum K12Platform {iPhone, iPad, android}

class Config {
  static bool inWeb = false;

  static K12Platform k12platform = K12Platform.iPhone;

  static bool inProduction = bool.fromEnvironment("dart.vm.product");
}

3. 网络请求

原有 Flutter 项目我们通过 MJFlutter 调用 Native 的网络接口,实现数据请求。辅导 web 侧是通过 CGI 请求获得后台数据,现在 web 需要另一种方式,给大家推荐几种方式:
a. package:http/http.dart
pub 链接:https://pub.dev/packages/http#-readme-tab-

import ‘dart:convert‘ as convert;
import ‘package:http/http.dart‘ as http;

main(List arguments) async {
  // This example uses the Google Books API to search for books about http.
  // https://developers.google.com/books/docs/overview
  var url = "https://www.googleapis.com/books/v1/volumes?q={http}";

  // Await the http get response, then decode the json-formatted responce.
  var response = await http.get(url);
  if (response.statusCode == 200) {
    var jsonResponse = convert.jsonDecode(response.body);
    var itemCount = jsonResponse[‘totalItems‘];
    print("Number of books about http: $itemCount.");
  } else {
    print("Request failed with status: ${response.statusCode}.");
  }
}

b. package:http/html.dart

import ‘dart:html‘ as html;

var req = html.HttpRequest.getString("https://www.googleapis.com/books/v1/volumes?q={http}");

c. package:dio/dio.dart
pub 链接:https://pub.dev/packages/dio#-installing-tab-
dio 这个网络库,大家之前可能用过,需要注意一点,3.0 之前的版本 Flutter For Web 不支持 (dart:io)。3.0 之后的版本,这个库经过官方大改造,现已支持 Flutter For Web 开发。
我推荐的是 dio 这个库,使用起来会比较方便,参数简单好理解。

var url = ‘https://fudao.qq.com/xxx‘;
    BaseOptions options = BaseOptions(
        method: ‘get‘,
        baseUrl: url,
        connectTimeout: 5000,
        receiveTimeout: 100000,
        contentType: ‘json‘,
        responseType: ResponseType.plain,
        headers: {
          ‘host‘ : "fudao.qq.com", // 一般就是 webview 的 url 的 host
          ‘cookieHeader‘: getWebCookie(),
          ‘accept‘: "*",
          ‘referer‘: "https://fudao.qq.com/"
        }
      );
    Dio dio = Dio(options);

    try {
      Response response = await dio.get(url);
      if (callback != null) {
        callback(response.statusCode, response.statusMessage, response.data);
      }
    } on DioError catch(e) {
        if(e.response != null) {
          print(e.response.data);
          print(e.response.headers);
          print(e.response.request);
        } else{
          print(e.request);
          print(e.message);
        }
      }

有了基本的网路请求,配置完 CGI,接下来就是界面的展示。但是你会发现,所有的业务请求都没有回包,无法得到数据。如果作为移动端开发,没有经常接触到 http 请求,一下子发蒙,我觉得可以理解。这个是 Web 开发中经常遇到的 跨域问题。前端开发们这里可以举手了,这个问题我会。简单的处理方式就是开发过程中挂上一个代理,具体就不详细展开了。
网路问题解决,进入发布调试,上文我们介绍过 Flutter For Web 的最终产物是三个文件。如何发布,前端同学又可以举手了。有一个简单的方式:github pages(配置方法:https://pages.github.com/)。

三、JavaScript 扩展

与使用 Flutter 实现的页面不同,Flutter For Web 的页面使用 Http 请求获取后台数据。Http 请求中很重要的一点是 header 中的 cookie,这里就需要通过 dart 与 js 之间的交互,还是要称赞下 Flutter 团队的实力。
官方说明:https://dart.dev/web/js-interop
pub 地址:https://pub.dev/packages/js![](https://s4.51cto.com/images/blog/202012/18/a8c0871db6523e254eabaa9b8bfa18f5.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

新建 native_api.js 文件技术图片

dart 封装 js 方法技术图片

index 入口文件中引入 js 文件技术图片

添加完 js 支持后,dart 侧调用 getStringFromJS 方法,并打印结果。
通过 js.dart 这个库,可以实现 web 侧的主要功能。例如 cookie、localStorage。技术图片

native_api.js 中声明基本方法技术图片

dart 封装 js 方法
处理完 Web 侧的基本能力后,需要部署 dart 转成 js 后,与 native 交互的能力。我们知道,目前 app 中 native 与 web 交互的方式主要通过 jsBridge。Native 侧我们已经有了成熟的体系主动调用和接收调用 web 的能力,这部分我们可以不需要修改,减少开发工作量。在 Flutter 侧我们需要添加交互支持,也就是需要在 native_api.js 中添加与 native 交互的能力。
JavaScript 调用 Native 的方式,主要有两种:注入 API 和 拦截 URL SCHEME,你可以根据自己的业务能力,选择合适的方式。相较于 JavaScript 调用 Native,Native 调用 JavaScript 比较简单,不管是 iOS 的 UIWebView 还是 WKWebView,还是 Android 的 WebView 组件,都以子组件的形式存在于 View/Activity 中,直接调用相应的 API 即可。
例如 iOS 中 JavaScript 调用 Native,设置 webView 的 title:

function callNativeFunction() {
  var url = ‘jsbridge://edu/setCenterTitle?p=%7B%22text%22%3A%22%E9%B1%BC%E9%A5%BC%22%7D#2?title=aaa&desc=bbb&link=http%3A%2F%2Fwww.baidu.com‘;

  var jsbridgeNode = document.createElement(‘iframe‘);
  var removeTimeStamp;
  jsbridgeNode.style.cssText = ‘display:none;width:0px;height:0px;‘;
  jsbridgeNode.onerror = function(e) {
    // 在 android 4.0-4.3 中,script 节点的 src 赋值成 jsbridge://ui/showDialog 的形式会报错
    e.stopPropagation();
  }
  /*
  ios 必须先赋值, 然后 append, 否者连续的 api 调用会间隔着失败
  也就是 api1(); api2(); api3(); api4(); 的连续调用,
  只有 api1 和 api3 会真正调用到客户端
  */
  jsbridgeNode.src = url;

  var root = document.body || document.documentElement;
  root.appendChild && root.appendChild(jsbridgeNode);

  setTimeout(function() {
    jsbridgeNode &&
      jsbridgeNode.parentNode &&
      jsbridgeNode.parentNode.removeChild(jsbridgeNode);
  }, 500);
}

四、展望

整体来说,Flutter For Web 达到成熟还有一段时间。在这段时间里,尽早布局,未免不是一件好事。Flutter For Web 现阶段的使用场景并不多,不如直接使用 Web 开发便捷、稳定。但我们可以通过 Flutter,拉近与大前端的距离,感受大前端的魅力,做一个真正的大前端开发。
腾讯企鹅辅导团队会继续实践,不管是 Flutter 实现的页面,还是 Flutter 转 Web 的页面。Flutter For Web 一个复杂的场景,我们会尝试降级及热更新能力,与 Web 同学通力合作,封装 App 和 Web 的基础 API、集成 CI 等,欢迎与我们一起交流。技术图片

技术服务于业务,业务推动技术,二者并行,提升用户体验,Flutter 道路上期待大家的反馈。
专家介绍
涂金林,来自江苏苏州。2014 年和 2017 年分别获得东南大学本科和硕士学位。2017 年加入腾讯在线教育部,主要从事移动客户端开发。入职以来,参与腾讯企鹅辅导、腾讯课堂 iOS 和 Android 开发,腾讯企鹅辅导 iOS 负责人。目前主要担任腾讯在线教育 Flutter 项目技术负责人。
活动推荐
近年来,随着 jQuery 的落幕,三大框架的割据以及小程序的爆发,大前端的发展也经历了从静态页面到 JavaScript 跨时代的诞生,再从 PC 端到移动端的转向,以及由依赖后端到前后端分离的架构演变。
腾讯在线教育前端团队,近年来在大前端技术架构演进方面也有了不少突破,如 Hybird 方案、离线包方案、PWA 结合 *** 方案、以及 RN 动态化方案的落地和执行等。
这次 GMTC 全球大前端技术大会(深圳站)2019,我们专门请到了 腾讯的前端高级工程师曹海歌,希望可以从腾讯在线教育前端团队的实操案例中,深入了解腾讯为提升研发效率,进行的前端工程化体系建设过程。扫描下方二维码或点击阅读原文,查看详情。

Flutter For Web:人人都是大前端开发

标签:分离   out   接口   互动   code   设置   官方   callback   pack   

原文地址:https://blog.51cto.com/15057848/2566501

上一篇:netCDF4读数剧

下一篇:JQuery 高级


评论


亲,登录后才可以留言!