Flutter和SAPUI5集成

需求背景:

由于本人一名 SAP 开发人员,对 Flutter 并不熟悉。在维护遗留的 Flutter 应用时,希望借助自己熟悉的 SAPUI5 技术栈,将基于 SAPUI5 开发的 Web 应用集成到现有 Flutter 应用中。未来新增的功能也将统一采用 SAPUI5 实现。

Flutter通过webview调用SAPUI5写的web应用

Dart 复制代码
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:provider/provider.dart';
import '../provider/user_model.dart';
import '../common/business_type.dart';

class SapFioriPage extends StatefulWidget {
  final String url;

  // 设置默认URL,以防调用时未提供URL参数
  SapFioriPage({Key key, this.url}) : super(key: key);

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

class _SapFioriPageState extends State<SapFioriPage> {
  WebViewController _controller;
  bool _canGoBack = false;
  int _currentIndex = 0;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: true,
      // appBar: AppBar(
      //   title: Text("SAP Fiori"),
      //   // leading: IconButton(
      //   //   icon: Icon(Icons.close),
      //   //   onPressed: () {
      //   //     Navigator.pop(context);
      //   //   },
      //   // ),
      // ),
      body: WebView(
        // 使用传入的URL或默认URL
        initialUrl: widget.url ?? 'http://你的ip:端口/zfiori_stock/index.html',
        javascriptMode: JavascriptMode.unrestricted,
        gestureNavigationEnabled: true,
        onWebViewCreated: (WebViewController controller) {
          _controller = controller;
        },
        onPageStarted: (String url) {
          print('页面开始加载: $url');
        },
        onPageFinished: (String url) {
          print('页面加载完成: $url');
        },
      ),
      bottomNavigationBar: BottomNavigationBar(
        //底部导航栏的创建需要对应的功能标签作为子项,这里我就写了3个,每个子项包含一个图标和一个title。
        items: [
          BottomNavigationBarItem(
              icon: Icon(
                Icons.arrow_back,
              ),
              title: new Text(
                '返回(F9)',
              )),
          BottomNavigationBarItem(
              icon: Icon(
                Icons.keyboard_return,
              ),
              title: new Text(
                '退出(F10)',
              )),
        ],
        //这是底部导航栏自带的位标属性,表示底部导航栏当前处于哪个导航标签。给他一个初始值0,也就是默认第一个标签页面。
        currentIndex: _currentIndex,
        //这是点击属性,会执行带有一个int值的回调函数,这个int值是系统自动返回的你点击的那个标签的位标
        onTap: (int i) {
          //进行状态更新,将系统返回的你点击的标签位标赋予当前位标属性,告诉系统当前要显示的导航标签被用户改变了。
          setState(() {
            _currentIndex = i;
          });
          UserModel userModel = Provider.of<UserModel>(context, listen: false);
          if (i == 0) {
            userModel.sapGotoPage(context, main_page);
          } else if (i == 1) {
            userModel.sapGotoPage(context, login_page);
          }
        },
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
  }
}

效果

Flutter APP上点击按钮

跳转到SAPUI5页面

坑1:真机上 WebView 输入框不弹键盘

原因

Flutter 1.22 + webview_flutter 使用 Virtual Display 组合方式,在部分真机与输入法冲突。Activity 未设为 adjustResize,或布局禁止调整。

修复1

android/app/src/main/AndroidManifest.xml (line 1) 的 <activity android:name=".MainActivity"> 添加:

android:windowSoftInputMode="adjustResize"

确保 android:hardwareAccelerated="true"

XML 复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.flutterDemo01">
    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
         calls FlutterMain.startInitialization(this); in its onCreate method.
         In most cases you can leave this as-is, but you if you want to provide
         additional functionality it is fine to subclass or reimplement
         FlutterApplication and put your custom class here. -->
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:icon="@mipmap/ic_launcher"
        android:label="flutterDemo01"
        android:usesCleartextTraffic="true"
        android:networkSecurityConfig="@xml/network_security_config">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
                android:name="io.flutter.embedding.android.NormalTheme"
                android:resource="@style/NormalTheme" />
            <!-- Displays an Android View that continues showing the launch screen
                 Drawable until Flutter paints its first frame, then this splash
                 screen fades out. A splash screen is useful to avoid any visual
                 gap between the end of Android's launch screen and the painting of
                 Flutter's first frame. -->
            <meta-data
                android:name="io.flutter.embedding.android.SplashScreenDrawable"
                android:resource="@drawable/launch_background" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

</manifest>

修复2

启用 Hybrid Composition(改善输入法与焦点):

在 lib/main.dart (line 1)(runApp 前):

import 'dart:io';

import 'package:webview_flutter/webview_flutter.dart';

void main() {

if (Platform.isAndroid) {

WebView.platform = SurfaceAndroidWebView();

}

runApp(MyApp());

}

Dart 复制代码
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutterDemo01/pages/splash_page.dart';
import 'package:bot_toast/bot_toast.dart';
import 'package:fluro/fluro.dart' as fluro;
import 'package:flutterDemo01/route/routes.dart';
import 'application.dart';
import 'package:provider/provider.dart';
import 'package:flutterDemo01/provider/user_model.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

void main() {
  // 新版本初始化需求
  WidgetsFlutterBinding.ensureInitialized();
  if (Platform.isAndroid) {
    // 启用 Hybrid Composition,解决部分真机输入法/焦点问题
    WebView.platform = SurfaceAndroidWebView();
  }
  fluro.Router router = fluro.Router();
  Routes.configureRoutes(router);
  Application.router = router;
  Application.initSp();
  runApp(MultiProvider(providers: [
    ChangeNotifierProvider<UserModel>.value(
      value: UserModel(),
    ),
  ], child: MyApp()));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BotToastInit(
      child: MaterialApp(
        title: '仓库PDA系统',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: SplashPage(title: '仓库PDA系统'),
        navigatorObservers: [BotToastNavigatorObserver()],
        onGenerateRoute: Application.router.generator,
        // 恢复使用动画主页
        localizationsDelegates: [
          // ... app-specific localization delegate[s] here
          // TODO: uncomment the line below after codegen
          // AppLocalizations.delegate,
          GlobalMaterialLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate,
          GlobalCupertinoLocalizations.delegate,
        ],
        supportedLocales: [
          const Locale('en', ''), // English, no country code
          const Locale('zh', ''), // Arabic, no country code
        ],
      ),
    );
  }
}

修复3

页面层级上,保证 Scaffold(resizeToAvoidBottomInset: true),避免外层 GestureDetector 抢占触控。

在 sap_fiori_page.dart (line 1) :

WebView(

initialUrl: 'http://你的ip:端口/zfiori_stock/index.html',

javascriptMode: JavascriptMode.unrestricted,

gestureNavigationEnabled: true,

)

坑2:模拟器中 SAPUI5 后端请求失败,待解决

尝试过解决方案,都没解决

怀疑是模拟器网络问题,现在真机能运行,不着急,待后续研究

启用明文 HTTP:

android/app/src/main/AndroidManifest.xml (line 1) 的 <application> 增加:android:usesCleartextTraffic="true"。

使用域名白名单:

android/app/src/main/AndroidManifest.xml (line 1) 增加 android:networkSecurityConfig="@xml/network_security_config",

并创建 android/app/src/main/res/xml/network_security_config.xml (line 1):

<?xml version="1.0" encoding="utf-8"?>

<network-security-config>

<domain-config cleartextTrafficPermitted="true">

<domain includeSubdomains="true">192.168.1.177</domain>

</domain-config>

</network-security-config>

相关推荐
愚者Pro1 天前
切换本地 Flutter SDK 版本
flutter
TT_Close1 天前
别再复制旧 Flutter 工程了,真正拖慢你的不是业务代码
flutter·npm·visual studio code
风华圆舞1 天前
鸿蒙 + Flutter 下 AI 助手为什么要支持流式输出
人工智能·flutter·harmonyos
风华圆舞1 天前
鸿蒙 + Flutter 下 AI 页面的状态协同设计
人工智能·flutter·harmonyos
风华圆舞1 天前
鸿蒙语音播报功能 的 Flutter 侧封装思路
flutter·华为·harmonyos
brycegao3211 天前
Flutter 国际化富文本解决方案:基于双层占位符的轻量化图文混排方案
flutter·国际化·i18n·富文本·rtl·移动端工程架构
风华圆舞1 天前
鸿蒙 + Flutter 下美食探索场景为什么 AI 推荐比传统搜索更自然
flutter·harmonyos·美食
MemoriKu1 天前
Flutter 相册 APP 收尾优化实战:未分析任务横幅持久隐藏与标签回归测试补强
大数据·人工智能·flutter·elasticsearch·机器学习·搜索引擎·重构
风华圆舞2 天前
鸿蒙 + Flutter 如何把 AI 助手嵌进应用页面里——以食界探味为
人工智能·flutter·harmonyos
风华圆舞2 天前
鸿蒙 + Flutter 下如何管理 AI 会话——AgentService 设计解析
人工智能·flutter·harmonyos