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>

相关推荐
Flutter OH4 小时前
Flutter OH 多设备适配问题定位指南
flutter
律宏阔5 小时前
Flutter Hooks 与 flutter_map_animations 冲突:第二次地图动画报错的解决方案
flutter
Flutter OH9 小时前
Flutter OH 卡死冻屏问题定位指南
flutter·命令模式
2501_9160074710 小时前
Flutter与游戏App加固避坑指南:如何为混合开发与高交互应用选对安全方案?
安全·flutter·游戏·ios·小程序·uni-app·iphone
恋猫de小郭11 小时前
Flutter A2UI 深度解析,它是怎么提供动态生产力的,然后为什么 A2UI 不只是 Flutter
android·前端·flutter
恋猫de小郭1 天前
Android 17 + OkHttp 5.5.0 ,全新 ECH 下你的 HTTPS 域名可以请求时被安全隐藏
android·前端·flutter
「、皓子~2 天前
海狸IM 2.1 正式发布
flutter·微服务·golang·electron·开源软件·im·海狸im
里欧跑得慢2 天前
AI 驱动的 UI 国际化方案自动适配:从单一语言到多语言布局的智能转换
前端·css·flutter·web
恋猫de小郭2 天前
AI 时代,一个优化 Flutter 的重复代码工具 Deslop
android·前端·flutter