Skip to content

Commit

Permalink
feat(functions): add support for 2nd gen functions (#10545)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lyokone committed Mar 22, 2023
1 parent f80948a commit 204ba39
Show file tree
Hide file tree
Showing 22 changed files with 446 additions and 250 deletions.
359 changes: 167 additions & 192 deletions .github/workflows/scripts/functions/package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions .github/workflows/scripts/functions/package.json
Expand Up @@ -14,8 +14,8 @@
},
"main": "lib/index.js",
"dependencies": {
"firebase-admin": "^11.4.1",
"firebase-functions": "^3.24.1"
"firebase-admin": "^11.5.0",
"firebase-functions": "^4.2.1"
},
"devDependencies": {
"firebase-functions-test": "^0.2.0",
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/scripts/functions/src/index.ts
@@ -1,12 +1,17 @@
import * as assert from 'assert';
import * as functions from 'firebase-functions';
import * as functionsv2 from 'firebase-functions/v2';

// For example app.
// noinspection JSUnusedGlobalSymbols
export const listFruit = functions.https.onCall(() => {
return ['Apple', 'Banana', 'Cherry', 'Date', 'Fig', 'Grapes'];
});

export const listfruits2ndgen = functionsv2.https.onCall(() => {
return ['Apple', 'Banana', 'Cherry', 'Date', 'Fig', 'Grapes'];
});

// For e2e testing a custom region.
// noinspection JSUnusedGlobalSymbols
export const testFunctionCustomRegion = functions
Expand Down
Expand Up @@ -23,6 +23,7 @@
import io.flutter.plugins.firebase.core.FlutterFirebasePlugin;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -69,7 +70,8 @@ private Task<Object> httpsFunctionCall(Map<String, Object> arguments) {

FirebaseFunctions firebaseFunctions = getFunctions(arguments);

String functionName = (String) Objects.requireNonNull(arguments.get("functionName"));
String functionName = (String) arguments.get("functionName");
String functionUri = (String) arguments.get("functionUri");
String origin = (String) arguments.get("origin");
Integer timeout = (Integer) arguments.get("timeout");
Object parameters = arguments.get("parameters");
Expand All @@ -79,8 +81,16 @@ private Task<Object> httpsFunctionCall(Map<String, Object> arguments) {
firebaseFunctions.useEmulator(originUri.getHost(), originUri.getPort());
}

HttpsCallableReference httpsCallableReference =
firebaseFunctions.getHttpsCallable(functionName);
HttpsCallableReference httpsCallableReference;

if (functionName != null) {
httpsCallableReference = firebaseFunctions.getHttpsCallable(functionName);
} else if (functionUri != null) {
httpsCallableReference =
firebaseFunctions.getHttpsCallableFromUrl(new URL(functionUri));
} else {
throw new IllegalArgumentException("Either functionName or functionUri must be set");
}

if (timeout != null) {
httpsCallableReference.setTimeout(timeout.longValue(), TimeUnit.MILLISECONDS);
Expand Down
Expand Up @@ -25,7 +25,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 29
compileSdkVersion 31

lintOptions {
disable 'InvalidPackage'
Expand Down
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -216,6 +216,7 @@
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
Expand Down Expand Up @@ -268,6 +269,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
Expand Down
Expand Up @@ -48,5 +48,7 @@
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
98 changes: 69 additions & 29 deletions packages/cloud_functions/cloud_functions/example/lib/main.dart
Expand Up @@ -3,10 +3,12 @@
// found in the LICENSE file.

import 'dart:core';
import 'dart:io';

import 'package:cloud_functions/cloud_functions.dart';
import 'package:cloud_functions_example/firebase_options.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

Future<void> main() async {
Expand Down Expand Up @@ -34,6 +36,9 @@ class _MyAppState extends State<MyApp> {

@override
Widget build(BuildContext context) {
final localhostMapped =
kIsWeb || !Platform.isAndroid ? 'localhost' : '10.0.2.2';

return MaterialApp(
home: Scaffold(
appBar: AppBar(
Expand All @@ -50,39 +55,74 @@ class _MyAppState extends State<MyApp> {
),
),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton.extended(
onPressed: () async {
// See index.js in .github/workflows/scripts for the example function we
// are using for this example
HttpsCallable callable = FirebaseFunctions.instance.httpsCallable(
'listFruit',
options: HttpsCallableOptions(
timeout: const Duration(seconds: 5),
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
FloatingActionButton.extended(
onPressed: () async {
// See .github/workflows/scripts/functions/src/index.ts for the example function we
// are using for this example
HttpsCallable callable =
FirebaseFunctions.instance.httpsCallable(
'listFruit',
options: HttpsCallableOptions(
timeout: const Duration(seconds: 5),
),
);

await callingFunction(callable, context);
},
label: const Text('Call Function'),
icon: const Icon(Icons.cloud),
backgroundColor: Colors.deepOrange,
),
);
const SizedBox(height: 10),
FloatingActionButton.extended(
onPressed: () async {
// See .github/workflows/scripts/functions/src/index.ts for the example function we
// are using for this example
HttpsCallable callable =
FirebaseFunctions.instance.httpsCallableFromUrl(
'http://$localhostMapped:5001/flutterfire-e2e-tests/us-central1/listfruits2ndgen',
options: HttpsCallableOptions(
timeout: const Duration(seconds: 5),
),
);

try {
final result = await callable();
setState(() {
fruit.clear();
result.data.forEach((f) {
fruit.add(f);
});
});
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('ERROR: $e'),
),
);
}
},
label: const Text('Call Function'),
icon: const Icon(Icons.cloud),
backgroundColor: Colors.deepOrange,
),
await callingFunction(callable, context);
},
label: const Text('Call 2nd Gen Function'),
icon: const Icon(Icons.cloud),
backgroundColor: Colors.deepOrange,
),
],
);
},
),
),
);
}

Future<void> callingFunction(
HttpsCallable callable,
BuildContext context,
) async {
try {
final result = await callable();
setState(() {
fruit.clear();
result.data.forEach((f) {
fruit.add(f);
});
});
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('ERROR: $e'),
),
);
}
}
}
Expand Up @@ -81,6 +81,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)flutter
- (void)httpsFunctionCall:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result {
NSString *appName = arguments[@"appName"];
NSString *functionName = arguments[@"functionName"];
NSString *functionUri = arguments[@"functionUri"];
NSString *origin = arguments[@"origin"];
NSString *region = arguments[@"region"];
NSNumber *timeout = arguments[@"timeout"];
Expand All @@ -93,7 +94,17 @@ - (void)httpsFunctionCall:(id)arguments withMethodCallResult:(FLTFirebaseMethodC
[functions useEmulatorWithHost:[url host] port:[[url port] intValue]];
}

FIRHTTPSCallable *function = [functions HTTPSCallableWithName:functionName];
FIRHTTPSCallable *function;

if (![functionName isEqual:[NSNull null]]) {
function = [functions HTTPSCallableWithName:functionName];
} else if (![functionUri isEqual:[NSNull null]]) {
function = [functions HTTPSCallableWithURL:[NSURL URLWithString:functionUri]];
} else {
result.error(@"IllegalArgumentException", @"Either functionName or functionUri must be set",
nil, nil);
return;
}
if (timeout != nil && ![timeout isEqual:[NSNull null]]) {
function.timeoutInterval = timeout.doubleValue / 1000;
}
Expand Down
Expand Up @@ -60,12 +60,43 @@ class FirebaseFunctions extends FirebasePluginPlatform {
String? _origin;

/// A reference to the Callable HTTPS trigger with the given name.
HttpsCallable httpsCallable(String name, {HttpsCallableOptions? options}) {
///
/// Should be the name of the Callable function in Firebase
/// or the URL of the 2nd gen Callable function in Firebase.
HttpsCallable httpsCallable(
String name, {
HttpsCallableOptions? options,
}) {
assert(name.isNotEmpty);
options ??= HttpsCallableOptions();
return HttpsCallable._(delegate.httpsCallable(_origin, name, options));
}

/// A reference to the Callable HTTPS trigger with the given URL.
///
/// Should be URL of the 2nd gen Callable function in Firebase.
HttpsCallable httpsCallableFromUrl(
String url, {
HttpsCallableOptions? options,
}) {
final uri = Uri.parse(url);
options ??= HttpsCallableOptions();
return HttpsCallable._(
delegate.httpsCallableWithUri(_origin, uri, options));
}

/// A reference to the Callable HTTPS trigger with the given Uri.
///
/// Should be Uri of the 2nd gen Callable function in Firebase.
HttpsCallable httpsCallableFromUri(
Uri uri, {
HttpsCallableOptions? options,
}) {
options ??= HttpsCallableOptions();
return HttpsCallable._(
delegate.httpsCallableWithUri(_origin, uri, options));
}

/// Changes this instance to point to a Cloud Functions emulator running locally.
///
/// Set the [host] of the local emulator, such as "localhost"
Expand Down
14 changes: 11 additions & 3 deletions packages/cloud_functions/cloud_functions/test/mock.dart
Expand Up @@ -29,8 +29,8 @@ void resetFirebaseCoreMocks() {

class MockHttpsCallablePlatform extends HttpsCallablePlatform {
MockHttpsCallablePlatform(FirebaseFunctionsPlatform functions, String? origin,
String name, HttpsCallableOptions options)
: super(functions, origin, name, options);
String? name, HttpsCallableOptions options, Uri? uri)
: super(functions, origin, name, options, uri);

@override
Future<dynamic> call([dynamic parameters]) async {
Expand All @@ -47,7 +47,15 @@ class MockFirebaseFunctionsPlatform extends FirebaseFunctionsPlatform {
HttpsCallablePlatform httpsCallable(
String? origin, String name, HttpsCallableOptions options) {
HttpsCallablePlatform httpsCallablePlatform =
MockHttpsCallablePlatform(this, origin, name, options);
MockHttpsCallablePlatform(this, origin, name, options, null);
return httpsCallablePlatform;
}

@override
HttpsCallablePlatform httpsCallableWithUri(
String? origin, Uri uri, HttpsCallableOptions options) {
HttpsCallablePlatform httpsCallablePlatform =
MockHttpsCallablePlatform(this, origin, null, options, uri);
return httpsCallablePlatform;
}

Expand Down
Expand Up @@ -42,6 +42,12 @@ class MethodChannelFirebaseFunctions extends FirebaseFunctionsPlatform {
@override
HttpsCallablePlatform httpsCallable(
String? origin, String name, HttpsCallableOptions options) {
return MethodChannelHttpsCallable(this, origin, name, options);
return MethodChannelHttpsCallable(this, origin, name, options, null);
}

@override
HttpsCallablePlatform httpsCallableWithUri(
String? origin, Uri uri, HttpsCallableOptions options) {
return MethodChannelHttpsCallable(this, origin, null, options, uri);
}
}
Expand Up @@ -7,15 +7,14 @@ import 'dart:async';

import '../../cloud_functions_platform_interface.dart';
import 'method_channel_firebase_functions.dart';

import 'utils/exception.dart';

/// Method Channel delegate for [HttpsCallablePlatform].
class MethodChannelHttpsCallable extends HttpsCallablePlatform {
/// Creates a new [MethodChannelHttpsCallable] instance.
MethodChannelHttpsCallable(FirebaseFunctionsPlatform functions,
String? origin, String name, HttpsCallableOptions options)
: super(functions, origin, name, options);
String? origin, String? name, HttpsCallableOptions options, Uri? uri)
: super(functions, origin, name, options, uri);

@override
Future<dynamic> call([Object? parameters]) async {
Expand All @@ -24,6 +23,7 @@ class MethodChannelHttpsCallable extends HttpsCallablePlatform {
.invokeMethod('FirebaseFunctions#call', <String, dynamic>{
'appName': functions.app!.name,
'functionName': name,
'functionUri': uri?.toString(),
'origin': origin,
'region': functions.region,
'timeout': options.timeout.inMilliseconds,
Expand Down
Expand Up @@ -66,4 +66,10 @@ abstract class FirebaseFunctionsPlatform extends PlatformInterface {
String? origin, String name, HttpsCallableOptions options) {
throw UnimplementedError('httpsCallable() is not implemented');
}

/// Creates a [HttpsCallablePlatform] instance from a [Uri]
HttpsCallablePlatform httpsCallableWithUri(
String? origin, Uri uri, HttpsCallableOptions options) {
throw UnimplementedError('httpsCallable() is not implemented');
}
}

0 comments on commit 204ba39

Please sign in to comment.