diff --git a/.github/workflows/firebase_dynamic_links.yaml b/.github/workflows/firebase_dynamic_links.yaml new file mode 100644 index 000000000000..003911e13a09 --- /dev/null +++ b/.github/workflows/firebase_dynamic_links.yaml @@ -0,0 +1,68 @@ +name: firebase_dynamic_links + +on: + pull_request: + paths: + - "packages/firebase_dynamic_links/**" + - ".github/workflows/firebase_dynamic_links.yaml" + push: + branches: + - master + paths-ignore: + - "docs/**" + - "**.md" + +env: + FLUTTERFIRE_PLUGIN_SCOPE: "*firebase_dynamic_links*" + FLUTTERFIRE_PLUGIN_SCOPE_EXAMPLE: "*firebase_dynamic_links_example*" + # TODO(Salakar): `flutter_driver` does not yet support NNBD + FLUTTER_COMMAND_FLAGS: "--no-sound-null-safety" + +jobs: + android: + if: github.event_name == 'pull_request' + runs-on: macos-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v1 + with: + fetch-depth: 0 + - name: "Install Flutter" + run: ./.github/workflows/scripts/install-flutter.sh stable + - name: "Install Tools" + run: ./.github/workflows/scripts/install-tools.sh + - name: "Build Example" + run: ./.github/workflows/scripts/build-example.sh android + - name: "Drive Example" + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 28 + arch: x86_64 + # Firebase Database works without Google Play Services, so we don't use the `googleapis` + # emulator target as it's considerably slower on CI. + target: default + profile: Nexus 5X + script: ./.github/workflows/scripts/drive-example.sh android + + apple: + runs-on: macos-latest + timeout-minutes: 35 + steps: + - uses: actions/checkout@v1 + with: + fetch-depth: 0 + - name: "Install Flutter" + run: ./.github/workflows/scripts/install-flutter.sh stable + - name: "Install Tools" + run: | + ./.github/workflows/scripts/install-tools.sh + flutter config --enable-macos-desktop + - name: "Build iOS Example" + run: ./.github/workflows/scripts/build-example.sh ios + - name: "Drive iOS Example" + run: ./.github/workflows/scripts/drive-example.sh ios +# TODO: Currently does not have macOS support. +# - name: "Build MacOS Example" +# run: ./.github/workflows/scripts/build-example.sh macos +# - name: "Drive MacOS Example" +# run: ./.github/workflows/scripts/drive-example.sh macos diff --git a/packages/firebase_dynamic_links/example/android/app/build.gradle b/packages/firebase_dynamic_links/example/android/app/build.gradle index ffbba7441917..61748d009e2c 100644 --- a/packages/firebase_dynamic_links/example/android/app/build.gradle +++ b/packages/firebase_dynamic_links/example/android/app/build.gradle @@ -1,60 +1,52 @@ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { - flutterVersionCode = '1' + flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { - flutterVersionName = '1.0' + flutterVersionName = '1.0' } apply plugin: 'com.android.application' +apply plugin: 'com.google.gms.google-services' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 29 - - lintOptions { - disable 'InvalidPackage' - } - - defaultConfig { - applicationId "io.flutter.plugins.firebasedynamiclinksexample" - minSdkVersion 16 - targetSdkVersion 28 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - signingConfig signingConfigs.debug - } + compileSdkVersion 29 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + applicationId "io.flutter.plugins.firebasedynamiclinksexample" + minSdkVersion 16 + targetSdkVersion 29 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + signingConfig signingConfigs.debug } + } } flutter { - source '../..' -} - -dependencies { - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test:rules:1.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + source '../..' } - -apply plugin: 'com.google.gms.google-services' diff --git a/packages/firebase_dynamic_links/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_dynamic_links/example/android/app/src/main/AndroidManifest.xml index e156bd08f6fc..a53f556e5e51 100644 --- a/packages/firebase_dynamic_links/example/android/app/src/main/AndroidManifest.xml +++ b/packages/firebase_dynamic_links/example/android/app/src/main/AndroidManifest.xml @@ -1,21 +1,47 @@ - - - - - - - - - - - - + package="io.flutter.plugins.firebasedynamiclinksexample"> + + + + + + + + + + + + + + + diff --git a/packages/firebase_dynamic_links/example/android/app/src/main/java/io/flutter/plugins/firebasedynamiclinksexample/EmbeddingV1Activity.java b/packages/firebase_dynamic_links/example/android/app/src/main/java/io/flutter/plugins/firebasedynamiclinksexample/EmbeddingV1Activity.java deleted file mode 100644 index bc1f40e5b98a..000000000000 --- a/packages/firebase_dynamic_links/example/android/app/src/main/java/io/flutter/plugins/firebasedynamiclinksexample/EmbeddingV1Activity.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.flutter.plugins.firebasedynamiclinksexample; - -import android.os.Bundle; -import dev.flutter.plugins.e2e.E2EPlugin; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.firebase.core.FlutterFirebaseCorePlugin; -import io.flutter.plugins.firebasedynamiclinks.FirebaseDynamicLinksPlugin; - -public class EmbeddingV1Activity extends FlutterActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - FlutterFirebaseCorePlugin.registerWith( - registrarFor("io.flutter.plugins.firebase.core.FlutterFirebaseCorePlugin")); - // TODO(Salakar) rename as part of re-work FirebaseDynamicLinksPlugin -> FlutterFirebaseDynamicLinksPlugin - FirebaseDynamicLinksPlugin.registerWith( - registrarFor("io.flutter.plugins.firebasedynamiclinks.FirebaseDynamicLinksPlugin")); - E2EPlugin.registerWith(registrarFor("dev.flutter.plugins.e2e.E2EPlugin")); - } -} diff --git a/packages/firebase_dynamic_links/example/android/app/src/main/java/io/flutter/plugins/firebasedynamiclinksexample/MainActivity.java b/packages/firebase_dynamic_links/example/android/app/src/main/java/io/flutter/plugins/firebasedynamiclinksexample/MainActivity.java new file mode 100644 index 000000000000..e2dac8d0905e --- /dev/null +++ b/packages/firebase_dynamic_links/example/android/app/src/main/java/io/flutter/plugins/firebasedynamiclinksexample/MainActivity.java @@ -0,0 +1,5 @@ +package io.flutter.plugins.firebasedynamiclinksexample; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity {} diff --git a/packages/firebase_dynamic_links/example/android/app/src/main/res/values/styles.xml b/packages/firebase_dynamic_links/example/android/app/src/main/res/values/styles.xml index 00fa4417cfbe..11beaaf68414 100644 --- a/packages/firebase_dynamic_links/example/android/app/src/main/res/values/styles.xml +++ b/packages/firebase_dynamic_links/example/android/app/src/main/res/values/styles.xml @@ -1,8 +1,17 @@ - + + + + diff --git a/packages/firebase_dynamic_links/example/android/settings.gradle b/packages/firebase_dynamic_links/example/android/settings.gradle index 5a2f14fb18f6..44e62bcf06ae 100644 --- a/packages/firebase_dynamic_links/example/android/settings.gradle +++ b/packages/firebase_dynamic_links/example/android/settings.gradle @@ -1,15 +1,11 @@ include ':app' -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } -} +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory -} +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/firebase_dynamic_links/example/pubspec.yaml b/packages/firebase_dynamic_links/example/pubspec.yaml index cec44cbeff92..fcce1b116517 100644 --- a/packages/firebase_dynamic_links/example/pubspec.yaml +++ b/packages/firebase_dynamic_links/example/pubspec.yaml @@ -23,7 +23,11 @@ dependency_overrides: path: ../../firebase_core/firebase_core_web dev_dependencies: - pedantic: ^1.11.0 + drive: 0.1.0 + flutter_driver: + sdk: flutter + flutter_test: + sdk: flutter flutter: uses-material-design: true diff --git a/packages/firebase_dynamic_links/example/test_driver/firebase_dynamic_links_e2e.dart b/packages/firebase_dynamic_links/example/test_driver/firebase_dynamic_links_e2e.dart new file mode 100644 index 000000000000..41f72515d0db --- /dev/null +++ b/packages/firebase_dynamic_links/example/test_driver/firebase_dynamic_links_e2e.dart @@ -0,0 +1,72 @@ +// @dart = 2.9 +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:drive/drive.dart' as drive; +import 'package:firebase_dynamic_links/firebase_dynamic_links.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void testsMain() { + group('DynamicLinks', () { + test('buildUrl', () async { + const String androidPackageName = + 'io.flutter.plugins.firebasedynamiclinksexample'; + const String iosBundleId = + 'com.google.FirebaseCppDynamicLinksTestApp.dev'; + const String urlHost = 'cx4k7.app.goo.gl'; + const String link = 'https://dynamic.link.example/helloworld'; + + final DynamicLinkParameters parameters = DynamicLinkParameters( + uriPrefix: 'https://$urlHost', + link: Uri.parse(link), + androidParameters: AndroidParameters( + packageName: androidPackageName, + minimumVersion: 1, + ), + dynamicLinkParametersOptions: DynamicLinkParametersOptions( + shortDynamicLinkPathLength: ShortDynamicLinkPathLength.short, + ), + iosParameters: IosParameters( + bundleId: iosBundleId, + minimumVersion: '2', + ), + ); + + final Uri uri = await parameters.buildUrl(); + + // androidParameters.minimumVersion + expect( + uri.queryParameters['amv'], + '1', + ); + // iosParameters.minimumVersion + expect( + uri.queryParameters['imv'], + '2', + ); + // androidParameters.packageName + expect( + uri.queryParameters['apn'], + androidPackageName, + ); + // iosParameters.bundleId + expect( + uri.queryParameters['ibi'], + iosBundleId, + ); + // link + expect( + uri.queryParameters['link'], + Uri.encodeFull(link), + ); + // uriPrefix + expect( + uri.host, + urlHost, + ); + }); + }); +} + +void main() => drive.main(testsMain); diff --git a/packages/firebase_dynamic_links/example/test_driver/firebase_dynamic_links_e2e_test.dart b/packages/firebase_dynamic_links/example/test_driver/firebase_dynamic_links_e2e_test.dart new file mode 100644 index 000000000000..84950df392c9 --- /dev/null +++ b/packages/firebase_dynamic_links/example/test_driver/firebase_dynamic_links_e2e_test.dart @@ -0,0 +1,8 @@ +// @dart = 2.9 +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:drive/drive_driver.dart' as drive; + +void main() => drive.main(); diff --git a/packages/firebase_dynamic_links/ios/Classes/FLTFirebaseDynamicLinksPlugin.m b/packages/firebase_dynamic_links/ios/Classes/FLTFirebaseDynamicLinksPlugin.m index 8ee4a9d681f1..f3b89bfa6d04 100644 --- a/packages/firebase_dynamic_links/ios/Classes/FLTFirebaseDynamicLinksPlugin.m +++ b/packages/firebase_dynamic_links/ios/Classes/FLTFirebaseDynamicLinksPlugin.m @@ -138,20 +138,18 @@ - (void)onDeepLinkResult:(FIRDynamicLink *_Nullable)dynamicLink error:(NSError * } } -- (BOOL)checkForDynamicLink:(NSURL *)url { +- (void)checkForDynamicLink:(NSURL *)url { FIRDynamicLink *dynamicLink = [[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url]; if (dynamicLink) { [self onDeepLinkResult:dynamicLink error:nil]; - return YES; } - return NO; } - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { [self checkForDynamicLink:url]; - // results of this are ORed and NO doesn't affect other delegate interceptors' result + // Results of this are ORed and NO doesn't affect other delegate interceptors' result. return NO; } @@ -160,49 +158,48 @@ - (BOOL)application:(UIApplication *)application sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { [self checkForDynamicLink:url]; - // results of this are ORed and NO doesn't affect other delegate interceptors' result + // Results of this are ORed and NO doesn't affect other delegate interceptors' result. return NO; } - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity - restorationHandler: -#if defined(__IPHONE_12_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_0) - (nonnull void (^)(NSArray> *_Nullable))restorationHandler { -#else - (nonnull void (^)(NSArray *_Nullable))restorationHandler { -#endif // __IPHONE_12_0 + restorationHandler:(nonnull void (^)(NSArray *_Nullable))restorationHandler { __block BOOL retried = NO; + void (^completionBlock)(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error); + void (^__block __weak weakCompletionBlock)(FIRDynamicLink *_Nullable dynamicLink, + NSError *_Nullable error); + weakCompletionBlock = completionBlock = ^(FIRDynamicLink *_Nullable dynamicLink, + NSError *_Nullable error) { + if (!error && dynamicLink && dynamicLink.url) { + [self onDeepLinkResult:dynamicLink error:nil]; + } - id completion = - ^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) { - if (!error && dynamicLink && dynamicLink.url) { - [self onDeepLinkResult:dynamicLink error:nil]; - } - } - // Per Apple Tech Support, a network failure could occur when returning from background on - // iOS 12. https://github.com/AFNetworking/AFNetworking/issues/4279#issuecomment-447108981 So - // we'll retry the request once - if (error && !retried && [NSPOSIXErrorDomain isEqualToString:error.domain] && - error.code == 53) { - retried = YES; - [[FIRDynamicLinks dynamicLinks] handleUniversalLink:userActivity.webpageURL - completion:completion]; - } - // We could send this to Dart and maybe have a onDynamicLinkError stream but there's also - // a good chance the `userActivity.webpageURL` might not be for a Firebase dynamic link, - // which needs consideration - so we'll log this for now, logging will get picked up by - // Crashlytics automatically if its integrated. - if (error) - NSLog(@"FLTFirebaseDynamicLinks: Unknown error occurred when attempting to handle a universal " + // Per Apple Tech Support, a network failure could occur when returning from background on + // iOS 12. https://github.com/AFNetworking/AFNetworking/issues/4279#issuecomment-447108981 So + // we'll retry the request once + if (error && !retried && [NSPOSIXErrorDomain isEqualToString:error.domain] && + error.code == 53) { + retried = YES; + [[FIRDynamicLinks dynamicLinks] handleUniversalLink:userActivity.webpageURL + completion:weakCompletionBlock]; + } + // We could send this to Dart and maybe have a onDynamicLinkError stream but there's also + // a good chance the `userActivity.webpageURL` might not be for a Firebase dynamic link, + // which needs consideration - so we'll log this for now, logging will get picked up by + // Crashlytics automatically if its integrated. + if (error) + NSLog( + @"FLTFirebaseDynamicLinks: Unknown error occurred when attempting to handle a universal " @"link: %@", error); -}; + }; -[[FIRDynamicLinks dynamicLinks] handleUniversalLink:userActivity.webpageURL completion:completion]; + [[FIRDynamicLinks dynamicLinks] handleUniversalLink:userActivity.webpageURL + completion:completionBlock]; -// results of this are ORed and NO doesn't affect other delegate interceptors' result -return NO; + // Results of this are ORed and NO doesn't affect other delegate interceptors' result. + return NO; } - (FIRDynamicLinkShortenerCompletion)createShortLinkCompletion:(FlutterResult)result { diff --git a/packages/firebase_dynamic_links/pubspec.yaml b/packages/firebase_dynamic_links/pubspec.yaml index 156e642f194f..a889d0f272a8 100644 --- a/packages/firebase_dynamic_links/pubspec.yaml +++ b/packages/firebase_dynamic_links/pubspec.yaml @@ -17,7 +17,6 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - pedantic: ^1.11.0 url_launcher: ^6.0.2 flutter: diff --git a/packages/firebase_dynamic_links/test/firebase_dynamic_links_e2e.dart b/packages/firebase_dynamic_links/test/firebase_dynamic_links_e2e.dart deleted file mode 100644 index 393bf35c078b..000000000000 --- a/packages/firebase_dynamic_links/test/firebase_dynamic_links_e2e.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:firebase_dynamic_links/firebase_dynamic_links.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('buildUrl', (WidgetTester tester) async { - final DynamicLinkParameters parameters = DynamicLinkParameters( - uriPrefix: 'https://cx4k7.app.goo.gl', - link: Uri.parse('https://dynamic.link.example/helloworld'), - androidParameters: AndroidParameters( - packageName: 'io.flutter.plugins.firebasedynamiclinksexample', - minimumVersion: 0, - ), - dynamicLinkParametersOptions: DynamicLinkParametersOptions( - shortDynamicLinkPathLength: ShortDynamicLinkPathLength.short, - ), - iosParameters: IosParameters( - bundleId: 'com.google.FirebaseCppDynamicLinksTestApp.dev', - minimumVersion: '0', - ), - ); - - final Uri uri = await parameters.buildUrl(); - expect( - uri.toString(), - 'https://cx4k7.app.goo.gl?amv=0&apn=io.flutter.plugins.firebasedynamiclinksexample&ibi=com.google.FirebaseCppDynamicLinksTestApp.dev&imv=0&link=https%3A%2F%2Fdynamic.link.example%2Fhelloworld', - ); - }); -}