从 Flutter 应用程序连接到本地 Firebase 函数模拟器时出错

Posted

技术标签:

【中文标题】从 Flutter 应用程序连接到本地 Firebase 函数模拟器时出错【英文标题】:Error connecting to local Firebase functions emulator from Flutter app 【发布时间】:2020-11-09 01:31:36 【问题描述】:

在使用本地 Firebase 函数模拟器作为我的后端设置我的项目并从我的 android 模拟器调用我的 Firebase onCall 函数后,我收到了这个非常无信息性的错误消息 PlatformException(functionsError, Cloud function failed with exception., code: INTERNAL, details: null, message: INTERNAL)。完整的错误信息如下:

E/flutter (20862): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: PlatformException(functionsError, Cloud function failed with exception., code: INTERNAL, details: null, message: INTERNAL)
E/flutter (21445): #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:569:7)
E/flutter (21445): #1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:156:18)
E/flutter (21445): <asynchronous suspension>
E/flutter (21445): #2      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:329:12)
E/flutter (21445): #3      MethodChannelCloudFunctions.callCloudFunction (package:cloud_functions_platform_interface/src/method_channel_cloud_functions.dart:43:15)
E/flutter (21445): #4      HttpsCallable.call (package:cloud_functions/src/https_callable.dart:33:12)
E/flutter (21445): #5      ApiService.loadUserLessonsByLessonIds (package:kim/services/api.dart:28:21)
E/flutter (21445): #6      MapScreen.build.<anonymous closure> (package:kim/screens/map.dart:170:34)
E/flutter (21445): #7      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:779:19)
E/flutter (21445): #8      _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:862:36)
E/flutter (21445): #9      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
E/flutter (21445): #10     TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:504:11)
E/flutter (21445): #11     BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:282:5)
E/flutter (21445): #12     BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:217:7)
E/flutter (21445): #13     PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:475:9)
E/flutter (21445): #14     PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:76:12)
E/flutter (21445): #15     PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:122:9)
E/flutter (21445): #16     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:379:8)
E/flutter (21445): #17     PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:120:18)
E/flutter (21445): #18     PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:106:7)
E/flutter (21445): #19     GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:218:19)
E/flutter (21445): #20     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:198:22)
E/flutter (21445): #21     GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:156:7)
E/flutter (21445): #22     GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:102:7)
E/flutter (21445): #23     GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:86:7)
E/flutter (21445): #24     _rootRunUnary (dart:async/zone.dart:1196:13)
E/flutter (21445): #25     _CustomZone.runUnary (dart:async/zone.dart:1085:19)
E/flutter (21445): #26     _CustomZone.runUnaryGuarded (dart:async/zone.dart:987:7)
E/flutter (21445): #27     _invoke1 (dart:ui/hooks.dart:275:10)
E/flutter (21445): #28     _dispatchPointerDataPacket (dart:ui/hooks.dart:184:5)
E/flutter (21445): 

服务器代码index.ts

import * as functions from 'firebase-functions'

interface LoadUserLessonsData 
  lessonIds: string[]


export const loadUserLessons = functions.https.onCall((data: LoadUserLessonsData, context) => 
  const uid = context.auth?.uid
  const ids = data.lessonIds
  console.log(uid, ids)
)

运行后的服务器控制台窗口firebase emulators:start --only functions

...
+  functions[loadUserLessons]: http function initialized (http://localhost:5001/project-name/us-central1/loadUserLessons).
...
┌───────────┬────────────────┬─────────────────────────────────┐
│ Emulator  │ Host:Port      │ View in Emulator UI             │
├───────────┼────────────────┼─────────────────────────────────┤
│ Functions │ localhost:5001 │ http://localhost:4000/functions │
└───────────┴────────────────┴─────────────────────────────────┘

客户端应用代码api.dart:

import 'dart:io';

import 'package:cloud_functions/cloud_functions.dart';
import 'package:myapp/services/config.dart';

class ApiService 
  static final ApiService _apiService = ApiService._internal();
  static final _functions = CloudFunctions.instance;

  ApiService._internal();

  factory ApiService() 
    init();
    return _apiService;
  

  static void init() 
    // 10.0.2.2 is the special IP address to connect to the 'localhost' of the host computer from an Android emulator.
    final origin = Platform.isAndroid ? 'http://10.0.2.2:5001' : 'http://localhost:5001';
    _functions.useFunctionsEmulator(origin: origin);
  

  static Future<dynamic> loadUserLessons(List<String> lessonIds) 
    final HttpsCallable callable = _functions.getHttpsCallable(
      functionName: 'loadUserLessons',
    );
    return callable.call(
      'lessonIds': lessonIds,
    );
  

如何正确设置 Flutter 应用以连接到本地模拟器?

需要注意的是,无论模拟器是否运行,错误信息都是一样的。模拟器也不会在控制台日志中注册任何事件。但是,当模拟器运行时,向函数端点发送自定义请求(例如通过“REST Api Client”之类的 Android 应用程序)确实会使模拟器做出反应。

这意味着问题仅出在 Flutter 代码中(我的或cloud_functions Flutter 包)。至少,这是我目前的结论。

【问题讨论】:

【参考方案1】:

幸运的是,我设置了 Crashlytics,收到了更详细的错误消息,其中之一是:

CLEARTEXT communication to 10.0.2.2 not permitted by network security policy

这让我想到了这个 *** 问题和 Ashish John 的评论:

OkHttp: <-- HTTP FAILED: java.net.UnknownServiceException: CLEARTEXT communication to 10.0.2.2 not permitted by network security policy

您可以在清单文件的“应用程序”标签中设置“android:usesCleartextTraffic="true"'。如果您的 API/Link 不支持 https 并且您使用的是“Android P”或更高版本,则会出现此问题。

阅读更多关于android:usesCleartextTraffichttps://developer.android.com/guide/topics/manifest/application-element

指示应用程序是否打算使用明文网络流量,例如明文 HTTP。 面向 API 级别 27 或更低级别的应用的默认值为“true”。以 API 级别 28 或更高级别为目标的应用默认为“false”

换句话说,如果您的 Android 模拟器运行的是 Android API 级别 28 或更高级别,您需要将 android:usesCleartextTraffic="true" 添加到您应用的 &lt;application&gt; 标记中的 AndroidManifest.xml

现在我们需要允许 Android 模拟器和 Firebase 函数模拟器之间的流量,Ahmed Ghrib 解决了这个问题。

OkHttp: <-- HTTP FAILED: java.net.UnknownServiceException: CLEARTEXT communication to 10.0.2.2 not permitted by network security policy

创建一个文件network_security_config.xml,并将其放入[PROJECT]/android/app/src/main/res/xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain>10.0.2.2</domain>
    </domain-config>
</network-security-config>

这是对 Ahmed Ghrib 回答的修改版本,以限制明文流量只能在 Android 模拟器和 Firebase 函数模拟器之间使用。

【讨论】:

以上是关于从 Flutter 应用程序连接到本地 Firebase 函数模拟器时出错的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 API 将我的 Flutter 应用程序连接到本地 sql server 数据库?

如何在 Flutter 应用程序中连接到本地实时数据库模拟器?

将 Flutter 连接到真实设备中的 localhost 服务器

如何从 EC2 连接到本地 ***?

无法使用 sequelize 从本地节点应用程序连接到 heroku postgresql 数据库

从 docker 容器连接到本地 mongodb