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>

相关推荐
狮恒6 小时前
OpenHarmony Flutter 分布式安全与隐私保护:跨设备可信交互与数据防泄漏方案
分布式·flutter·wpf·openharmony
Engineer-Jsp7 小时前
Flutter 开发 Android 原生开发神器 flutter_api_stub
android·flutter
狮恒8 小时前
OpenHarmony Flutter 分布式任务调度:跨设备资源协同与负载均衡方案
分布式·flutter·wpf·openharmony
名字被你们想完了8 小时前
Flutter 实现一个容器内部元素可平移、缩放和旋转等功能(三)
前端·flutter
测试人社区—小叶子8 小时前
移动开发新宠:用Flutter 4.0快速构建跨平台应用
运维·网络·人工智能·测试工具·flutter·自动化
小a杰.8 小时前
Flutter 测试驱动开发的基本流程
驱动开发·flutter
小a杰.8 小时前
Flutter 就业市场深度分析
flutter
嗝o゚8 小时前
Flutter适配鸿蒙多屏异构UI开发实战
flutter·开源·wpf·harmonyos
飛6799 小时前
玩转 Flutter 自定义 Painter:从零打造丝滑的仪表盘动效与可视化图表
开发语言·javascript·flutter