Error connecting to local Firebase functions emulator from Flutter app

2,763

Luckily I had set up Crashlytics that had been given more detailed error messages, one of which was:

CLEARTEXT communication to 10.0.2.2 not permitted by network security policy

which pointed me to this Stackoverflow question and the comment made by Ashish John:

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

You can you 'android:usesCleartextTraffic="true"' in 'Application' tag in manifest file. This problem occurs if your API/Link doe not support https & you are using 'Android P' or above.

Reading up more on android:usesCleartextTraffic https://developer.android.com/guide/topics/manifest/application-element

Indicates whether the app intends to use cleartext network traffic, such as cleartext HTTP. The default value for apps that target API level 27 or lower is "true". Apps that target API level 28 or higher default to "false".

In other words, if your Android emulator is running Android API level 28 or higher you need to add android:usesCleartextTraffic="true" to the <application> tag in your app's AndroidManifest.xml

Now we need to permit the traffic between Android emulator and the Firebase functions emulator, which Ahmed Ghrib solved.

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

Create a file network_security_config.xml and place it in [PROJECT]/android/app/src/main/res/xml with the following code:

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

This is a modified version made to Ahmed Ghrib's answer, to limit clear text traffic to only be used between the Android emulator and the Firebase functions emulator.

Share:
2,763
Henrik Wassdahl
Author by

Henrik Wassdahl

Updated on December 17, 2022

Comments

  • Henrik Wassdahl
    Henrik Wassdahl over 1 year

    After setting up my project with a local Firebase functions emulator as my backend, and calling my Firebase onCall function from my Android emulator, I get this very non-informative error message PlatformException(functionsError, Cloud function failed with exception., {code: INTERNAL, details: null, message: INTERNAL}). Full error message below:

    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): 
    
    

    The server code 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)
    })
    
    

    The server console window after running 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 │
    └───────────┴────────────────┴─────────────────────────────────┘
    

    The client app code 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,
        });
      }
    }
    

    What can I do to correctly setup my Flutter app to connect to my local emulator?

    It should be noted that the error message is the same regardless if the emulator is running or not. The emulator doesn't register any events in the console logs either. However, when the emulator is running, sending an custom request (e.g. through an Android app like "REST Api Client") to the function endpoint does make the emulator react.

    This means that the problem is solely in the Flutter code (mine or cloud_functions Flutter package). At least, that's my conclusion so far.