diff --git a/.gitignore b/.gitignore index d520d057..219ee230 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,9 @@ GeneratedPluginRegistrant.m GeneratedPluginRegistrant.java GeneratedPluginRegistrant.swift generated_plugin_registrant.dart +generated_plugin_registrant.h generated_plugin_registrant.cc +generated_plugins.cmake build/ .flutter-plugins .flutter-plugins-dependencies diff --git a/docs/firebase-ui-auth/README.md b/docs/firebase-ui-auth/README.md index b21d729b..66cabc83 100644 --- a/docs/firebase-ui-auth/README.md +++ b/docs/firebase-ui-auth/README.md @@ -17,8 +17,6 @@ Install dependencies ```sh flutter pub add firebase_core flutter pub add firebase_auth -# required for email link sign in and email verification -flutter pub add firebase_dynamic_links flutter pub add firebase_ui_auth ``` diff --git a/packages/firebase_ui_auth/example/.gitignore b/packages/firebase_ui_auth/example/.gitignore index a8e938c0..221b4225 100644 --- a/packages/firebase_ui_auth/example/.gitignore +++ b/packages/firebase_ui_auth/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/packages/firebase_ui_auth/example/android/app/build.gradle b/packages/firebase_ui_auth/example/android/app/build.gradle index 07d4638f..5f864f3e 100644 --- a/packages/firebase_ui_auth/example/android/app/build.gradle +++ b/packages/firebase_ui_auth/example/android/app/build.gradle @@ -1,3 +1,10 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" + id "com.google.gms.google-services" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +13,6 @@ if (localPropertiesFile.exists()) { } } -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.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,25 +23,18 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -// START: FlutterFire Configuration -apply plugin: 'com.google.gms.google-services' -// END: FlutterFire Configuration -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { namespace 'io.flutter.plugins.firebase_ui_example' compileSdkVersion flutter.compileSdkVersion ndkVersion flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } sourceSets { @@ -66,7 +61,3 @@ android { flutter { source '../..' } - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/packages/firebase_ui_auth/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_ui_auth/example/android/app/src/main/AndroidManifest.xml index 36f5b767..dcc81eac 100644 --- a/packages/firebase_ui_auth/example/android/app/src/main/AndroidManifest.xml +++ b/packages/firebase_ui_auth/example/android/app/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true"> - + @@ -33,6 +33,14 @@ + + + + + + diff --git a/packages/firebase_ui_auth/example/android/build.gradle b/packages/firebase_ui_auth/example/android/build.gradle index 2551d225..bc157bd1 100644 --- a/packages/firebase_ui_auth/example/android/build.gradle +++ b/packages/firebase_ui_auth/example/android/build.gradle @@ -1,19 +1,3 @@ -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - mavenCentral() - } - - dependencies { - // START: FlutterFire Configuration - classpath 'com.google.gms:google-services:4.3.15' - // END: FlutterFire Configuration - classpath 'com.android.tools.build:gradle:7.4.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() diff --git a/packages/firebase_ui_auth/example/android/gradle.properties b/packages/firebase_ui_auth/example/android/gradle.properties index 0ccde7ee..ef8d51b1 100644 --- a/packages/firebase_ui_auth/example/android/gradle.properties +++ b/packages/firebase_ui_auth/example/android/gradle.properties @@ -2,4 +2,7 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.defaults.buildfeatures.buildconfig=true android.nonTransitiveRClass=false -android.nonFinalResIds=false \ No newline at end of file +android.nonFinalResIds=false + +# Java 17 settings to suppress Java 8 warnings +kotlin.jvm.target.validation.mode=warning \ No newline at end of file diff --git a/packages/firebase_ui_auth/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/firebase_ui_auth/example/android/gradle/wrapper/gradle-wrapper.properties index da1db5f0..fce403e4 100644 --- a/packages/firebase_ui_auth/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/firebase_ui_auth/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/packages/firebase_ui_auth/example/android/settings.gradle b/packages/firebase_ui_auth/example/android/settings.gradle index 44e62bcf..0cfdd0fb 100644 --- a/packages/firebase_ui_auth/example/android/settings.gradle +++ b/packages/firebase_ui_auth/example/android/settings.gradle @@ -1,11 +1,26 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -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" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.9.25" apply false + id "com.google.gms.google-services" version "4.4.2" apply false +} + +include ":app" \ No newline at end of file diff --git a/packages/firebase_ui_auth/example/ios/Flutter/AppFrameworkInfo.plist b/packages/firebase_ui_auth/example/ios/Flutter/AppFrameworkInfo.plist index 9625e105..7c569640 100644 --- a/packages/firebase_ui_auth/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/firebase_ui_auth/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/packages/firebase_ui_auth/example/ios/Podfile b/packages/firebase_ui_auth/example/ios/Podfile index 2c068c40..fe628cb8 100644 --- a/packages/firebase_ui_auth/example/ios/Podfile +++ b/packages/firebase_ui_auth/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +platform :ios, '17.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/firebase_ui_auth/example/ios/Runner.xcodeproj/project.pbxproj b/packages/firebase_ui_auth/example/ios/Runner.xcodeproj/project.pbxproj index ac92c860..175cb443 100644 --- a/packages/firebase_ui_auth/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/firebase_ui_auth/example/ios/Runner.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 615AB19345F2CB9C4AFB55AE /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5CBE04B4787B566D8CAE0579 /* GoogleService-Info.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 83A0F86F233458219B2DC55F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 416FB58C991096C1726F437F /* Pods_Runner.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -36,6 +37,7 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 416FB58C991096C1726F437F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 467989B22DF71C4F0093BE6F /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 5CBE04B4787B566D8CAE0579 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; 6DF47F8C0940BEBB4259C546 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; @@ -56,6 +58,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 83A0F86F233458219B2DC55F /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -97,6 +100,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 467989B22DF71C4F0093BE6F /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -149,6 +153,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -177,6 +184,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -379,6 +389,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = YYX2P3XVJ7; ENABLE_BITCODE = NO; @@ -508,6 +519,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = YYX2P3XVJ7; ENABLE_BITCODE = NO; @@ -531,6 +543,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = YYX2P3XVJ7; ENABLE_BITCODE = NO; @@ -571,6 +584,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/firebase_ui_auth/example/ios/Runner/AppDelegate.swift b/packages/firebase_ui_auth/example/ios/Runner/AppDelegate.swift index 70693e4a..75bd5cea 100644 --- a/packages/firebase_ui_auth/example/ios/Runner/AppDelegate.swift +++ b/packages/firebase_ui_auth/example/ios/Runner/AppDelegate.swift @@ -1,13 +1,22 @@ import UIKit import Flutter +import app_links -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) + + // Retrieve the link from parameters + if let url = AppLinks.shared.getLink(launchOptions: launchOptions) { + // We have a link, propagate it to your Flutter app or not + AppLinks.shared.handleLink(url: url) + return true // Returning true will stop the propagation to other packages + } + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/packages/firebase_ui_auth/example/ios/Runner/Info.plist b/packages/firebase_ui_auth/example/ios/Runner/Info.plist index accb0a4c..5139a1be 100644 --- a/packages/firebase_ui_auth/example/ios/Runner/Info.plist +++ b/packages/firebase_ui_auth/example/ios/Runner/Info.plist @@ -47,6 +47,8 @@ UIApplicationSupportsIndirectInputEvents + FlutterDeepLinkingEnabled + CFBundleURLTypes diff --git a/packages/firebase_ui_auth/example/ios/Runner/Runner.entitlements b/packages/firebase_ui_auth/example/ios/Runner/Runner.entitlements new file mode 100644 index 00000000..77423e3e --- /dev/null +++ b/packages/firebase_ui_auth/example/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.associated-domains + + applinks:flutterfire-e2e-tests.firebaseapp.com + + + diff --git a/packages/firebase_ui_auth/example/lib/main.dart b/packages/firebase_ui_auth/example/lib/main.dart index f53ceee0..faffd75b 100644 --- a/packages/firebase_ui_auth/example/lib/main.dart +++ b/packages/firebase_ui_auth/example/lib/main.dart @@ -23,7 +23,7 @@ final actionCodeSettings = ActionCodeSettings( url: 'https://flutterfire-e2e-tests.firebaseapp.com', handleCodeInApp: true, androidMinimumVersion: '1', - androidPackageName: 'io.flutter.plugins.firebase_ui.firebase_ui_example', + androidPackageName: 'io.flutter.plugins.firebase_ui_example', iOSBundleId: 'io.flutter.plugins.fireabaseUiExample', ); final emailLinkProviderConfig = EmailLinkAuthProvider( @@ -245,7 +245,7 @@ class FirebaseAuthUIExample extends StatelessWidget { return EmailLinkSignInScreen( actions: [ AuthStateChangeAction((context, state) { - Navigator.pushReplacementNamed(context, '/'); + Navigator.pushReplacementNamed(context, '/profile'); }), ], provider: emailLinkProviderConfig, diff --git a/packages/firebase_ui_auth/example/pubspec.yaml b/packages/firebase_ui_auth/example/pubspec.yaml index de72b0f1..af6b582d 100644 --- a/packages/firebase_ui_auth/example/pubspec.yaml +++ b/packages/firebase_ui_auth/example/pubspec.yaml @@ -35,6 +35,8 @@ dependencies: firebase_ui_oauth_facebook: ^1.3.2 firebase_ui_oauth_google: ^1.4.2 firebase_ui_oauth_twitter: ^1.3.2 + # This and twitter oauth package need to depend on git main directly due to namespace build error on android. + twitter_login: ^4.4.2 dev_dependencies: drive: ^1.0.0-1.0.nullsafety.5 firebase_ui_shared: ^1.4.1 @@ -48,7 +50,7 @@ dev_dependencies: http: ^1.1.2 integration_test: sdk: flutter - twitter_login: ^4.4.2 + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. diff --git a/packages/firebase_ui_auth/example/windows/flutter/generated_plugin_registrant.h b/packages/firebase_ui_auth/example/windows/flutter/generated_plugin_registrant.h deleted file mode 100644 index dc139d85..00000000 --- a/packages/firebase_ui_auth/example/windows/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include - -// Registers Flutter plugins. -void RegisterPlugins(flutter::PluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/firebase_ui_auth/example/windows/flutter/generated_plugins.cmake b/packages/firebase_ui_auth/example/windows/flutter/generated_plugins.cmake deleted file mode 100644 index 73d6fa1c..00000000 --- a/packages/firebase_ui_auth/example/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,27 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST - desktop_webview_auth - firebase_auth - firebase_core - flutter_secure_storage_windows -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/packages/firebase_ui_auth/lib/src/email_verification.dart b/packages/firebase_ui_auth/lib/src/email_verification.dart index f20497ba..62126e44 100644 --- a/packages/firebase_ui_auth/lib/src/email_verification.dart +++ b/packages/firebase_ui_auth/lib/src/email_verification.dart @@ -3,8 +3,9 @@ // BSD-style license that can be found in the LICENSE file. import 'package:firebase_auth/firebase_auth.dart' as fba; -import 'package:firebase_dynamic_links/firebase_dynamic_links.dart'; +import 'package:app_links/app_links.dart'; import 'package:flutter/material.dart'; +import 'dart:async'; /// All possible states of the email verification process. enum EmailVerificationState { @@ -22,7 +23,7 @@ enum EmailVerificationState { sending, /// A state that indicates that user needs to follow the link and the app - /// awaits a valid dynamic link. + /// awaits a valid deep link. pending, /// A state that indicates that the verification email was successfully sent. @@ -42,8 +43,12 @@ class EmailVerificationController extends ValueNotifier /// {@macro ui.auth.auth_controller.auth} final fba.FirebaseAuth auth; - EmailVerificationController(this.auth) - : super(EmailVerificationState.unresolved) { + final AppLinks _appLinks; + StreamSubscription? _linkSubscription; + + EmailVerificationController(this.auth, {AppLinks? appLinks}) + : _appLinks = appLinks ?? AppLinks(), + super(EmailVerificationState.unresolved) { final user = auth.currentUser; if (user != null) { @@ -57,6 +62,13 @@ class EmailVerificationController extends ValueNotifier WidgetsBinding.instance.addObserver(this); } + @override + void dispose() { + _linkSubscription?.cancel(); + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { @@ -93,6 +105,7 @@ class EmailVerificationController extends ValueNotifier /// Indicates that email verification process was cancelled. void dismiss() { value = EmailVerificationState.dismissed; + _linkSubscription?.cancel(); } /// Sends an email with a link to verify the user's email address. @@ -111,19 +124,32 @@ class EmailVerificationController extends ValueNotifier if (_isMobile(platform)) { value = EmailVerificationState.pending; - // ignore: deprecated_member_use - final linkData = await FirebaseDynamicLinks.instance.onLink.first; - - try { - final code = linkData.link.queryParameters['oobCode']!; - await auth.checkActionCode(code); - await auth.applyActionCode(code); - await user.reload(); - value = EmailVerificationState.verified; - } on Exception catch (err) { - error = err; - value = EmailVerificationState.failed; - } + + _linkSubscription?.cancel(); + + _linkSubscription = _appLinks.uriLinkStream.listen( + (Uri uri) async { + try { + final code = uri.queryParameters['oobCode']; + if (code != null) { + await auth.checkActionCode(code); + await auth.applyActionCode(code); + await user.reload(); + value = EmailVerificationState.verified; + _linkSubscription?.cancel(); + } + } on Exception catch (err) { + error = err; + value = EmailVerificationState.failed; + _linkSubscription?.cancel(); + } + }, + onError: (error) { + this.error = error is Exception ? error : Exception(error.toString()); + value = EmailVerificationState.failed; + _linkSubscription?.cancel(); + }, + ); } else { value = EmailVerificationState.sent; } diff --git a/packages/firebase_ui_auth/lib/src/providers/email_link_auth_provider.dart b/packages/firebase_ui_auth/lib/src/providers/email_link_auth_provider.dart index a57f9673..092cfa25 100644 --- a/packages/firebase_ui_auth/lib/src/providers/email_link_auth_provider.dart +++ b/packages/firebase_ui_auth/lib/src/providers/email_link_auth_provider.dart @@ -4,9 +4,10 @@ // ignore_for_file: deprecated_member_use import 'package:firebase_auth/firebase_auth.dart' as fba; -import 'package:firebase_dynamic_links/firebase_dynamic_links.dart'; import 'package:flutter/foundation.dart'; import 'package:firebase_ui_auth/firebase_ui_auth.dart'; +import 'package:app_links/app_links.dart'; +import 'dart:async'; /// A listener of the [EmailLinkFlow] lifecycle. abstract class EmailLinkAuthListener extends AuthListener { @@ -26,7 +27,8 @@ class EmailLinkAuthProvider /// A configuration of the dynamic link. final fba.ActionCodeSettings actionCodeSettings; - final FirebaseDynamicLinks _dynamicLinks; + final AppLinks _appLinks; + StreamSubscription? _linkSubscription; @override late EmailLinkAuthListener authListener; @@ -41,13 +43,13 @@ class EmailLinkAuthProvider } /// {@macro ui.auth.providers.email_link_auth_provider} - EmailLinkAuthProvider( - {required this.actionCodeSettings, + EmailLinkAuthProvider({ + required this.actionCodeSettings, - /// An instance of the [FirebaseDynamicLinks] that should be used to handle - /// the link. By default [FirebaseDynamicLinks.instance] is used. - FirebaseDynamicLinks? dynamicLinks}) - : _dynamicLinks = dynamicLinks ?? FirebaseDynamicLinks.instance; + /// An instance of the [AppLinks] that should be used to handle + /// the link. By default [AppLinks()] is used. + AppLinks? appLinks, + }) : _appLinks = appLinks ?? AppLinks(); /// Sends a link to the [email]. void sendLink(String email) { @@ -63,8 +65,8 @@ class EmailLinkAuthProvider .catchError(authListener.onError); } - void _onLinkReceived(String email, PendingDynamicLinkData linkData) { - final link = linkData.link.toString(); + void _onLinkReceived(String email, Uri uri) { + final link = uri.toString(); if (auth.isSignInWithEmailLink(link)) { authListener.onBeforeSignIn(); @@ -79,12 +81,20 @@ class EmailLinkAuthProvider } } - /// Calls [FirebaseDynamicLinks] to receive the link and perform a sign in. + /// Listens for incoming app links and handles email authentication. /// Should be called after [EmailLinkAuthListener.onLinkSent] was called. void awaitLink(String email) { - _dynamicLinks.onLink.first - .then((linkData) => _onLinkReceived(email, linkData)) - .catchError(authListener.onError); + _linkSubscription?.cancel(); + + _linkSubscription = _appLinks.uriLinkStream.listen( + (Uri uri) => _onLinkReceived(email, uri), + onError: (error) => authListener.onError(error), + ); + } + + void dispose() { + _linkSubscription?.cancel(); + _linkSubscription = null; } void _signInWithEmailLink(String email, String link) { diff --git a/packages/firebase_ui_auth/lib/src/screens/email_link_sign_in_screen.dart b/packages/firebase_ui_auth/lib/src/screens/email_link_sign_in_screen.dart index 92450fd5..1052f575 100644 --- a/packages/firebase_ui_auth/lib/src/screens/email_link_sign_in_screen.dart +++ b/packages/firebase_ui_auth/lib/src/screens/email_link_sign_in_screen.dart @@ -61,7 +61,7 @@ class EmailLinkSignInScreen extends ProviderScreen { @override Widget build(BuildContext context) { - return UniversalScaffold( + final child = UniversalScaffold( body: ResponsivePage( breakpoint: breakpoint, headerBuilder: headerBuilder, @@ -78,5 +78,10 @@ class EmailLinkSignInScreen extends ProviderScreen { ), ), ); + + return FirebaseUIActions( + actions: actions ?? const [], + child: child, + ); } } diff --git a/packages/firebase_ui_auth/pubspec.yaml b/packages/firebase_ui_auth/pubspec.yaml index 4fb4bde0..6c2b73d3 100644 --- a/packages/firebase_ui_auth/pubspec.yaml +++ b/packages/firebase_ui_auth/pubspec.yaml @@ -9,10 +9,10 @@ environment: flutter: ">=3.3.0" dependencies: + app_links: ^6.4.0 email_validator: ^2.1.17 firebase_auth: ^5.6.0 firebase_core: ^3.14.0 - firebase_dynamic_links: ^6.1.7 firebase_ui_localizations: ^1.14.0 firebase_ui_oauth: ^1.7.0 firebase_ui_shared: ^1.4.1 diff --git a/packages/firebase_ui_auth/test/flows/email_link_flow_test.dart b/packages/firebase_ui_auth/test/flows/email_link_flow_test.dart index f0fcc619..517c441e 100644 --- a/packages/firebase_ui_auth/test/flows/email_link_flow_test.dart +++ b/packages/firebase_ui_auth/test/flows/email_link_flow_test.dart @@ -14,7 +14,7 @@ void main() { late EmailLinkAuthProvider provider; late MockListener listener; late MockAuth auth; - late MockDynamicLinks dynamicLinks; + late MockAppLinks appLinks; late EmailLinkFlow flow; late EmailLinkAuthController ctrl; @@ -27,11 +27,11 @@ void main() { setUp(() { auth = MockAuth(); listener = MockListener(); - dynamicLinks = MockDynamicLinks(); + appLinks = MockAppLinks(); provider = EmailLinkAuthProvider( actionCodeSettings: actionCodeSettings, - dynamicLinks: dynamicLinks, + appLinks: appLinks, ); flow = EmailLinkFlow( @@ -114,11 +114,14 @@ void main() { group('#awaitLink', () { test( - 'waits for a link from dynamic links and calls onBeforeSignIn', + 'waits for a link from app links and calls onBeforeSignIn', () async { provider.authListener = listener; provider.awaitLink('test@test.com'); + // Simulate receiving an app link + MockUriStream.addLink(Uri.parse('https://test.com')); + await untilCalled(listener.onBeforeSignIn()); verify(listener.onBeforeSignIn()).called(1); @@ -131,6 +134,9 @@ void main() { when(auth.isSignInWithEmailLink(any)).thenReturn(false); + // Simulate receiving an invalid app link + MockUriStream.addLink(Uri.parse('https://invalid-link.com')); + await untilCalled(listener.onError(any)); final result = verify(listener.onError(captureAny)); @@ -145,6 +151,9 @@ void main() { provider.authListener = listener; provider.awaitLink('test@test.com'); + // Simulate receiving a valid app link + MockUriStream.addLink(Uri.parse('https://test.com')); + await untilCalled(listener.onBeforeSignIn()); final result = verify( @@ -165,6 +174,9 @@ void main() { provider.authListener = listener; provider.awaitLink('test@test.com'); + // Simulate receiving a valid app link + MockUriStream.addLink(Uri.parse('https://test.com')); + await untilCalled(listener.onSignedIn(any)); final result = verify(listener.onSignedIn(captureAny)); @@ -185,6 +197,9 @@ void main() { provider.awaitLink('test@test.com'); + // Simulate receiving a valid app link + MockUriStream.addLink(Uri.parse('https://test.com')); + await untilCalled(listener.onError(any)); final result = verify(listener.onError(captureAny)); diff --git a/packages/firebase_ui_auth/test/test_utils.dart b/packages/firebase_ui_auth/test/test_utils.dart index 4485e56e..bd1bb10e 100644 --- a/packages/firebase_ui_auth/test/test_utils.dart +++ b/packages/firebase_ui_auth/test/test_utils.dart @@ -4,9 +4,10 @@ import 'package:firebase_auth/firebase_auth.dart' as fba; import 'package:firebase_core/firebase_core.dart'; -import 'package:firebase_dynamic_links/firebase_dynamic_links.dart'; +import 'package:app_links/app_links.dart'; import 'package:flutter/material.dart'; import 'package:mockito/mockito.dart'; +import 'dart:async'; class TestMaterialApp extends StatelessWidget { final Widget child; @@ -53,27 +54,52 @@ class MockUser extends Mock implements fba.User { } } -class MockLinksStream extends Mock implements Stream { +class MockUriStream extends Mock implements Stream { + static final StreamController _controller = + StreamController.broadcast(); + @override - Future get first async { - return super.noSuchMethod( - Invocation.getter(#first), - returnValue: PendingDynamicLinkData( - link: Uri.parse('https://test.com'), - ), - returnValueForMissingStub: PendingDynamicLinkData( - link: Uri.parse('https://test.com'), - ), + StreamSubscription listen( + void Function(Uri)? onData, { + Function? onError, + void Function()? onDone, + bool? cancelOnError, + }) { + return _controller.stream.listen( + onData, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError, ); } + + static void addLink(Uri uri) { + _controller.add(uri); + } + + static void addError(Object error) { + _controller.addError(error); + } + + static void close() { + _controller.close(); + } } -// ignore: deprecated_member_use -class MockDynamicLinks extends Mock implements FirebaseDynamicLinks { - static final _linkStream = MockLinksStream(); +class MockAppLinks extends Mock implements AppLinks { + static final _uriStream = MockUriStream(); + + @override + Stream get uriLinkStream => _uriStream; @override - Stream get onLink => _linkStream; + Future getInitialLink() async { + return super.noSuchMethod( + Invocation.method(#getInitialLink, []), + returnValue: null, + returnValueForMissingStub: null, + ); + } } class MockApp extends Mock implements FirebaseApp {} diff --git a/packages/firebase_ui_auth/test/views/email_link_sign_in_view_test.dart b/packages/firebase_ui_auth/test/views/email_link_sign_in_view_test.dart index db8705f0..04c844fd 100644 --- a/packages/firebase_ui_auth/test/views/email_link_sign_in_view_test.dart +++ b/packages/firebase_ui_auth/test/views/email_link_sign_in_view_test.dart @@ -13,18 +13,18 @@ import '../test_utils.dart'; void main() { const labels = DefaultLocalizations(); late MockAuth auth; - late MockDynamicLinks dynamicLinks; + late MockAppLinks appLinks; late EmailLinkAuthProvider emailLinkProvider; setUp(() { auth = MockAuth(); - dynamicLinks = MockDynamicLinks(); + appLinks = MockAppLinks(); final actionCodeSettings = fba.ActionCodeSettings( url: 'https://example.com', ); emailLinkProvider = EmailLinkAuthProvider( actionCodeSettings: actionCodeSettings, - dynamicLinks: dynamicLinks, + appLinks: appLinks, ); }); diff --git a/tests/ios/Podfile b/tests/ios/Podfile index 93badd10..7741e5eb 100644 --- a/tests/ios/Podfile +++ b/tests/ios/Podfile @@ -40,5 +40,12 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + + # Enable Swift 6 access level on imports feature + target.build_configurations.each do |config| + config.build_settings['OTHER_SWIFT_FLAGS'] ||= [] + config.build_settings['OTHER_SWIFT_FLAGS'] << '-enable-experimental-feature' + config.build_settings['OTHER_SWIFT_FLAGS'] << 'AccessLevelOnImport' + end end end diff --git a/tests/linux/flutter/generated_plugin_registrant.h b/tests/linux/flutter/generated_plugin_registrant.h deleted file mode 100644 index e0f0a47b..00000000 --- a/tests/linux/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include - -// Registers Flutter plugins. -void fl_register_plugins(FlPluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/tests/macos/Podfile b/tests/macos/Podfile index 6176a934..9741269d 100644 --- a/tests/macos/Podfile +++ b/tests/macos/Podfile @@ -41,5 +41,12 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) + + # Enable Swift 6 access level on imports feature + target.build_configurations.each do |config| + config.build_settings['OTHER_SWIFT_FLAGS'] ||= [] + config.build_settings['OTHER_SWIFT_FLAGS'] << '-enable-experimental-feature' + config.build_settings['OTHER_SWIFT_FLAGS'] << 'AccessLevelOnImport' + end end end diff --git a/tests/windows/flutter/generated_plugin_registrant.h b/tests/windows/flutter/generated_plugin_registrant.h deleted file mode 100644 index dc139d85..00000000 --- a/tests/windows/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include - -// Registers Flutter plugins. -void RegisterPlugins(flutter::PluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_