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>

相关推荐
恋猫de小郭4 小时前
Flutter 全新真 3D 实现,用 flutter_scene 能开发一个「我的世界」
android·前端·flutter
woshihuanglaoshi5 小时前
数据迁移与版本管理 - Flutter在鸿蒙平台实现数据库升级策略
数据库·学习·flutter·华为·harmonyos·鸿蒙·鸿蒙系统
GitLqr14 小时前
玩转 WebSocket:从原理到 Flutter 实战
websocket·网络协议·flutter
世人万千丶15 小时前
参数管理_Flutter在鸿蒙平台路由参数最佳实践
学习·flutter·华为·harmonyos·鸿蒙
zhazhazsy1 天前
Flutter 多 Isolate 实战:搭建主 Isolate 数据与能力桥
flutter
math_hongfan1 天前
鸿蒙Flutter setState机制深入理解
学习·flutter·华为·harmonyos·鸿蒙
woshihuanglaoshi1 天前
参数验证_Flutter在鸿蒙平台确保路由参数类型安全
学习·flutter·华为·harmonyos·鸿蒙·鸿蒙系统
末代iOS程序员华仔1 天前
Swift 的角色演变:从“主角”到“最佳配角”——iOS 应用被 Flutter 替换,Swift 变成辅助?
flutter·ios·swift
_阿南_1 天前
不是每一个Widget都会生成一个RenderObject
flutter
世人万千丶2 天前
鸿蒙Flutter Flex多子组件权重分配
学习·flutter·华为·harmonyos·鸿蒙