После настройки моего проекта с локальным эмулятором функций Firebase в качестве серверной части и вызова моей функции Firebase onCall из моего эмулятора Android я получаю это очень неинформативное сообщение об ошибке 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 (https://localhost:5001/project-name/us-central1/loadUserLessons).
...
┌───────────┬────────────────┬─────────────────────────────────┐
│ Emulator │ Host:Port │ View in Emulator UI │
├───────────┼────────────────┼─────────────────────────────────┤
│ Functions │ localhost:5001 │ https://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 ? 'https://10.0.2.2:5001' : 'https://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 для подключения к локальному эмулятору?
Следует отметить, что сообщение об ошибке одинаково независимо от того, запущен эмулятор или нет. Эмулятор также не регистрирует никаких событий в журналах консоли. Однако, когда эмулятор запущен, отправка настраиваемого запроса (например, через приложение Android, такое как REST Api Client) в конечную точку функции заставляет эмулятор реагировать.
Это означает, что проблема заключается исключительно в коде Flutter (мой или cloud_functions
пакет Flutter). По крайней мере, таков мой вывод.
К счастью, я настроил Crashlytics, который получал более подробные сообщения об ошибках, одно из которых было:
который указал мне на этот вопрос Stackoverflow и комментарий, сделанный Ашишем Джоном:
Okhttps: ‹- HTTP FAILED: java.net.UnknownService Связь CLEARTEXT с 10.0.2.2 не разрешена политикой безопасности сети
Дополнительная информация на
android:usesCleartextTraffic
https://developer.android.com/guide/topics/manifest/application-elementДругими словами, если ваш эмулятор Android работает под управлением Android API уровня 28 или выше, вам необходимо добавить
android:usesCleartextTraffic="true"
в тег<application>
вAndroidManifest.xml
вашего приложения.Теперь нам нужно разрешить трафик между эмулятором Android и эмулятором функций Firebase, что решил Ахмед Гриб.
Okhttps: ‹- HTTP FAILED: java.net. UnknownServiceException: связь CLEARTEXT с 10.0.2.2 не разрешена политикой безопасности сети
Создайте файл
network_security_config.xml
и поместите его в[PROJECT]/android/app/src/main/res/xml
со следующим кодом:Это измененная версия, сделанная для ответа Ахмеда Гриба, чтобы ограничить трафик открытого текста, который будет использоваться только между эмулятором Android и эмулятором функций Firebase.