Ошибка подключения к локальному эмулятору функций Firebase из приложения Flutter

После настройки моего проекта с локальным эмулятором функций 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 для подключения к локальному эмулятору?

См. также:  Использование grep для печати одной из перечисленных строк, но для печати их всех, если существует более одной

Следует отметить, что сообщение об ошибке одинаково независимо от того, запущен эмулятор или нет. Эмулятор также не регистрирует никаких событий в журналах консоли. Однако, когда эмулятор запущен, отправка настраиваемого запроса (например, через приложение Android, такое как REST Api Client) в конечную точку функции заставляет эмулятор реагировать.

Это означает, что проблема заключается исключительно в коде Flutter (мой или cloud_functions пакет Flutter). По крайней мере, таков мой вывод.

Понравилась статья? Поделиться с друзьями:
IT Шеф
Комментарии: 1
  1. Henrik Wassdahl

    К счастью, я настроил Crashlytics, который получал более подробные сообщения об ошибках, одно из которых было:

    CLEARTEXT communication to 10.0.2.2 not permitted by network security policy
    

    который указал мне на этот вопрос Stackoverflow и комментарий, сделанный Ашишем Джоном:

    Okhttps: ‹- HTTP FAILED: java.net.UnknownService Связь CLEARTEXT с 10.0.2.2 не разрешена политикой безопасности сети

    Вы можете указать android: usesCleartextTraffic = true в теге Application в файле манифеста. Эта проблема возникает, если ваш API / ссылка не поддерживает https и вы используете Android P или выше.

    Дополнительная информация на android:usesCleartextTraffic https://developer.android.com/guide/topics/manifest/application-element

    Указывает, намеревается ли приложение использовать сетевой трафик с открытым текстом, например HTTP с открытым текстом. Значение по умолчанию для приложений, нацеленных на уровень API 27 или ниже, — true. Приложения, нацеленные на уровень API 28 или выше, по умолчанию имеют значение false.

    Другими словами, если ваш эмулятор 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 со следующим кодом:

    <?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>
    

    Это измененная версия, сделанная для ответа Ахмеда Гриба, чтобы ограничить трафик открытого текста, который будет использоваться только между эмулятором Android и эмулятором функций Firebase.

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: