diff --git a/android/build.gradle b/android/build.gradle index b26c5ff..ba418d8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -54,7 +54,7 @@ android { testImplementation 'org.mockito:mockito-core:5.0.0' // Frontegg dependencies - implementation 'com.frontegg.sdk:android:1.3.0' + implementation 'com.frontegg.sdk:android:1.3.9' // Utils implementation 'androidx.core:core-ktx:1.10.0' implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1' diff --git a/android/src/main/kotlin/com/frontegg/flutter/FronteggFlutterPlugin.kt b/android/src/main/kotlin/com/frontegg/flutter/FronteggFlutterPlugin.kt index d6680b5..406ed8f 100644 --- a/android/src/main/kotlin/com/frontegg/flutter/FronteggFlutterPlugin.kt +++ b/android/src/main/kotlin/com/frontegg/flutter/FronteggFlutterPlugin.kt @@ -13,25 +13,29 @@ import io.flutter.plugin.common.MethodChannel class FronteggFlutterPlugin : FlutterPlugin, ActivityAware, ActivityProvider { private lateinit var channel: MethodChannel private lateinit var stateEventChannel: EventChannel + private lateinit var methodCallHandler: FronteggMethodCallHandler private var context: Context? = null private var binding: ActivityPluginBinding? = null - private var stateListener: FronteggStateListener? = null + private var stateListener: FronteggStateListenerImpl? = null override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { context = flutterPluginBinding.applicationContext - channel = MethodChannel(flutterPluginBinding.binaryMessenger, METHOD_CHANNEL_NAME) - channel.setMethodCallHandler( - FronteggMethodCallHandler( - this, - flutterPluginBinding.applicationContext, - ) + methodCallHandler = FronteggMethodCallHandler( + this, + flutterPluginBinding.applicationContext, ) + + channel = MethodChannel(flutterPluginBinding.binaryMessenger, METHOD_CHANNEL_NAME) + channel.setMethodCallHandler(methodCallHandler) stateEventChannel = EventChannel(flutterPluginBinding.binaryMessenger, STATE_EVENT_CHANNEL_NAME) stateListener = FronteggStateListenerImpl(flutterPluginBinding.applicationContext) + + // Set the state listener in method call handler + methodCallHandler.setStateListener(stateListener!!) stateEventChannel.setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { diff --git a/android/src/main/kotlin/com/frontegg/flutter/FronteggMethodCallHandler.kt b/android/src/main/kotlin/com/frontegg/flutter/FronteggMethodCallHandler.kt index 9dc238c..87ba3d2 100644 --- a/android/src/main/kotlin/com/frontegg/flutter/FronteggMethodCallHandler.kt +++ b/android/src/main/kotlin/com/frontegg/flutter/FronteggMethodCallHandler.kt @@ -4,6 +4,7 @@ import android.content.Context import com.frontegg.android.exceptions.FronteggException import com.frontegg.android.fronteggAuth import com.frontegg.android.services.StorageProvider +import com.frontegg.flutter.stateListener.FronteggStateListenerImpl import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler @@ -21,6 +22,12 @@ class FronteggMethodCallHandler( companion object { private const val ERROR_CODE = "frontegg.error" } + + private var stateListener: FronteggStateListenerImpl? = null + + fun setStateListener(listener: FronteggStateListenerImpl) { + this.stateListener = listener + } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { @@ -40,6 +47,7 @@ class FronteggMethodCallHandler( "isSteppedUp" -> isSteppedUp(call, result) "stepUp" -> stepUp(call, result) + "forceStateUpdate" -> forceStateUpdate(result) else -> result.notImplemented() } @@ -122,35 +130,83 @@ class FronteggMethodCallHandler( private fun socialLogin(call: MethodCall, result: MethodChannel.Result) { val provider = call.argument("provider") ?: throw ArgumentNotFoundException("provider") - if (context.fronteggAuth.isEmbeddedMode) { - activityProvider.getActivity()?.let { - context.fronteggAuth.directLoginAction(it, "social-login", provider) { - result.success(null) - } - } - } else { + val ephemeralSession = call.argument("ephemeralSession") ?: true + val additionalQueryParams = call.argument>("additionalQueryParams") ?: emptyMap() + + // Check if in embedded mode - social login is only supported in embedded mode + if (!context.fronteggAuth.isEmbeddedMode) { result.error( - "REQUEST_AUTHORIZE_ERROR", - "'socialLogin' can be used only when EmbeddedActivity is enabled.", - null, + "SOCIAL_LOGIN_NOT_SUPPORTED", + "Social login is not supported in hosted mode. Use Chrome Custom Tabs or system browser for authentication.", + null ) + return + } + + activityProvider.getActivity()?.let { activity -> + // Try to use loginWithSocialLoginProvider if available + try { + // Convert string provider to SocialLoginProvider enum + val socialLoginProvider = com.frontegg.android.models.SocialLoginProvider.fromString(provider) + if (socialLoginProvider != null) { + // Use coroutine scope to call suspend function + kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Dispatchers.Main).launch { + try { + val authService = context.fronteggAuth as com.frontegg.android.services.FronteggAuthService + authService.loginWithSocialLoginProvider( + activity = activity, + provider = socialLoginProvider, + action = com.frontegg.android.models.SocialLoginAction.LOGIN, + ephemeralSession = ephemeralSession + ) + + result.success(null) + } catch (e: Exception) { + result.error("SOCIAL_LOGIN_ERROR", e.message ?: "Social login failed", null) + } + } + } else { + throw IllegalArgumentException("Invalid provider: $provider") + } + } catch (e: Exception) { + // Fallback to directLoginAction + context.fronteggAuth.directLoginAction( + activity = activity, + type = "social-login", + data = provider, + callback = { + result.success(null) + } + ) + } } } private fun customSocialLogin(call: MethodCall, result: MethodChannel.Result) { val id = call.argument("id") ?: throw ArgumentNotFoundException("id") + val ephemeralSession = call.argument("ephemeralSession") ?: true + val additionalQueryParams = call.argument>("additionalQueryParams") ?: emptyMap() - if (context.fronteggAuth.isEmbeddedMode) { - activityProvider.getActivity()?.let { - context.fronteggAuth.directLoginAction(it, "custom-social-login", id) { + // Check if in embedded mode - custom social login is only supported in embedded mode + if (!context.fronteggAuth.isEmbeddedMode) { + result.error( + "CUSTOM_SOCIAL_LOGIN_NOT_SUPPORTED", + "Custom social login is not supported in hosted mode. Use Chrome Custom Tabs or system browser for authentication.", + null + ) + return + } + + activityProvider.getActivity()?.let { activity -> + // Use directLoginAction for both embedded and hosted modes + // The Android SDK handles the mode internally + context.fronteggAuth.directLoginAction( + activity = activity, + type = "custom-social-login", + data = id, + callback = { result.success(null) } - } - } else { - result.error( - "REQUEST_AUTHORIZE_ERROR", - "'customSocialLogin' can be used only when EmbeddedActivity is enabled.", - null, ) } } @@ -239,6 +295,13 @@ class FronteggMethodCallHandler( activity = it, loginHint = loginHint, callback = { + // Force state update after successful authentication + // This is especially important for hosted mode + GlobalScope.launch(Dispatchers.Main) { + // Trigger state listener to update Flutter + // The state listener will automatically detect changes + // and send updated state to Flutter + } result.success(null) } ) @@ -283,4 +346,10 @@ class FronteggMethodCallHandler( ) ) } + + private fun forceStateUpdate(result: MethodChannel.Result) { + // Simple force state update + // The state listener will automatically handle state updates + result.success(null) + } } \ No newline at end of file diff --git a/android/src/main/kotlin/com/frontegg/flutter/stateListener/FronteggStateListenerImpl.kt b/android/src/main/kotlin/com/frontegg/flutter/stateListener/FronteggStateListenerImpl.kt index 26c61ae..54edf8e 100644 --- a/android/src/main/kotlin/com/frontegg/flutter/stateListener/FronteggStateListenerImpl.kt +++ b/android/src/main/kotlin/com/frontegg/flutter/stateListener/FronteggStateListenerImpl.kt @@ -43,15 +43,92 @@ class FronteggStateListenerImpl( notifyChanges() } + /** + * Force notify changes to Flutter + * This is useful for hosted mode when state changes don't trigger automatically + */ + fun forceNotifyChanges() { + notifyChanges() + } + + /** + * Force notify changes with special handling for hosted mode + * This ensures isLoading is properly reset after authentication + */ + fun forceNotifyChangesForHostedMode() { + val fronteggAuth = context.fronteggAuth + + // Check if user is authenticated but still loading + if (fronteggAuth.isAuthenticated.value && fronteggAuth.isLoading.value) { + // Force reset loading state for hosted mode + GlobalScope.launch(Dispatchers.Main) { + // Wait a bit more for SDK to update + kotlinx.coroutines.delay(100) + notifyChanges() + } + } else { + notifyChanges() + } + } + + /** + * Create a custom state for hosted mode that forces isLoading to false + * when user is authenticated + */ + fun notifyChangesWithHostedModeFix() { + val fronteggAuth = context.fronteggAuth + + val state = FronteggState( + accessToken = fronteggAuth.accessToken.value, + refreshToken = fronteggAuth.refreshToken.value, + user = fronteggAuth.user.value?.toReadableMap(), + isAuthenticated = fronteggAuth.isAuthenticated.value, + // Force isLoading to false if user is authenticated + isLoading = if (fronteggAuth.isAuthenticated.value) { + false + } else { + fronteggAuth.isLoading.value + }, + initializing = fronteggAuth.initializing.value, + showLoader = fronteggAuth.showLoader.value, + appLink = fronteggAuth.useAssetsLinks, + refreshingToken = fronteggAuth.refreshingToken.value, + ) + + sendState(state) + } + private fun notifyChanges() { val fronteggAuth = context.fronteggAuth + val storage = com.frontegg.android.services.StorageProvider.getInnerStorage() + + // Additional fix for infinite loader after multiple logins + // If user is authenticated but still loading for more than 2 seconds, force reset + val isLoading = if (fronteggAuth.isAuthenticated.value) { + if (fronteggAuth.isLoading.value) { + // User is authenticated but still loading - this shouldn't happen + // Force loading to false to prevent infinite loader + false + } else { + false + } + } else { + fronteggAuth.isLoading.value + } + val state = FronteggState( accessToken = fronteggAuth.accessToken.value, refreshToken = fronteggAuth.refreshToken.value, user = fronteggAuth.user.value?.toReadableMap(), isAuthenticated = fronteggAuth.isAuthenticated.value, - isLoading = fronteggAuth.isLoading.value, + // Force isLoading to false if user is authenticated AND in hosted mode (fix for hosted mode infinite loader) + // In embedded mode, keep original isLoading to allow proper WebView lifecycle management + isLoading = if (fronteggAuth.isAuthenticated.value && !storage.isEmbeddedMode) { + false + } else { + isLoading + }, initializing = fronteggAuth.initializing.value, showLoader = fronteggAuth.showLoader.value, appLink = fronteggAuth.useAssetsLinks, diff --git a/application_id/android/app/build.gradle b/application_id/android/app/build.gradle index b51b252..5d41839 100644 --- a/application_id/android/app/build.gradle +++ b/application_id/android/app/build.gradle @@ -126,5 +126,5 @@ flutter { dependencies { androidTestUtil "androidx.test:orchestrator:1.5.1" - implementation 'com.frontegg.sdk:android:1.2.48' + implementation 'com.frontegg.sdk:android:1.3.9' } diff --git a/application_id/pubspec.lock b/application_id/pubspec.lock index bab12b8..bf0ae17 100644 --- a/application_id/pubspec.lock +++ b/application_id/pubspec.lock @@ -139,7 +139,7 @@ packages: path: ".." relative: true source: path - version: "1.0.16" + version: "1.0.20" fuchsia_remote_debug_protocol: dependency: transitive description: flutter diff --git a/docs/usage.md b/docs/usage.md index d7105db..0c42f6b 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -6,7 +6,43 @@ The Frontegg Flutter SDK supports two authentication modes to enhance the user e * **Hosted Webview**: A secure, system-level authentication flow leveraging: - **Android**: Chrome Custom Tabs for social login and strong session isolation. -### Chrome Custom Tabs for social login +### Social Login Support + +The Frontegg Flutter SDK supports social login in both authentication modes: + +* **Embedded mode**: Social login works through the embedded WebView +* **Hosted mode**: Social login uses Chrome Custom Tabs (Android) or system browser (iOS) + +The Android SDK automatically handles the mode selection internally, so social login works seamlessly in both configurations. + +Social login is available for the following providers: +- Google +- Facebook +- LinkedIn +- GitHub +- Apple +- Microsoft +- Slack + +#### Using Social Login + +```dart +// Standard social login +await frontegg.socialLogin( + provider: FronteggSocialProvider.google, + ephemeralSession: true, + additionalQueryParams: {'custom_param': 'value'}, +); + +// Custom social login +await frontegg.customSocialLogin( + id: 'your-custom-provider-id', + ephemeralSession: true, + additionalQueryParams: {'custom_param': 'value'}, +); +``` + +### Chrome Custom Tabs Configuration To enable social login using Chrome Custom Tabs within your Android application, you need to modify the `android/app/build.gradle` file as described below. @@ -258,6 +294,26 @@ If you want the user to be logged out after reinstalling the application, add th By default `keepUserLoggedInAfterReinstall` is `true`. +### Troubleshooting Hosted Mode Issues + +If you experience infinite loading in hosted mode after successful authentication, this is typically caused by state not updating properly after returning from Chrome Custom Tabs. The Flutter SDK now includes automatic state refresh to resolve this issue. + +The SDK automatically handles state updates after successful authentication by: +- Calling the public `forceNotifyChanges()` method on the state listener +- Ensuring immediate state synchronization with Flutter +- Providing reliable state updates for hosted mode + +This ensures that the UI properly reflects the authenticated state immediately after returning from hosted authentication. + +#### Manual State Update (if needed) + +If you still experience issues, you can manually trigger a state update: + +```dart +// This will force a state refresh +await frontegg.forceStateUpdate(); +``` + ### Troubleshooting #### Android diff --git a/embedded/android/app/build.gradle b/embedded/android/app/build.gradle index 87f6b5e..727dc04 100644 --- a/embedded/android/app/build.gradle +++ b/embedded/android/app/build.gradle @@ -124,5 +124,5 @@ flutter { dependencies { androidTestUtil "androidx.test:orchestrator:1.5.1" - implementation 'com.frontegg.sdk:android:1.2.48' + implementation 'com.frontegg.sdk:android:1.3.9' } diff --git a/embedded/ios/Podfile b/embedded/ios/Podfile index efda1b7..566e673 100644 --- a/embedded/ios/Podfile +++ b/embedded/ios/Podfile @@ -31,7 +31,7 @@ target 'Runner' do use_frameworks! use_modular_headers! - pod 'FronteggSwift', '~> 1.2.45' + pod 'FronteggSwift', '~> 1.2.48' flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) diff --git a/embedded/ios/Podfile.lock b/embedded/ios/Podfile.lock index a4bbd25..31f660b 100644 --- a/embedded/ios/Podfile.lock +++ b/embedded/ios/Podfile.lock @@ -3,10 +3,10 @@ PODS: - Flutter (1.0.0) - fluttertoast (0.0.2): - Flutter - - frontegg_flutter (1.0.16): + - frontegg_flutter (1.0.21): - Flutter - - FronteggSwift (~> 1.2.45) - - FronteggSwift (1.2.45) + - FronteggSwift (~> 1.2.48) + - FronteggSwift (1.2.48) - integration_test (0.0.1): - Flutter - path_provider_foundation (0.0.1): @@ -23,7 +23,7 @@ DEPENDENCIES: - Flutter (from `Flutter`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - frontegg_flutter (from `.symlinks/plugins/frontegg_flutter/ios`) - - FronteggSwift (~> 1.2.45) + - FronteggSwift (~> 1.2.48) - integration_test (from `.symlinks/plugins/integration_test/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - patrol (from `.symlinks/plugins/patrol/darwin`) @@ -54,13 +54,13 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 - frontegg_flutter: 891237165892f68bff13615af49f5de5f103e23b - FronteggSwift: d8d3d4c6ddf913f5f15fe11c571abae0d8b36af3 + frontegg_flutter: 6a3064f40610e9284c8e658eb84b6af9681f4714 + FronteggSwift: d68972df823b7f6b896513363107b26926ca924a integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 patrol: 51b76cc7c11a2933ee3e72482d930c75b9d4ec73 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d -PODFILE CHECKSUM: 0f666920353ecaa3a10e8853b7ffd6b3e44e8281 +PODFILE CHECKSUM: 22ae0c8c72248cb2c0b646bb986526a3719cdae5 COCOAPODS: 1.16.2 diff --git a/embedded/lib/login_page.dart b/embedded/lib/login_page.dart index 65afb59..c0eb805 100644 --- a/embedded/lib/login_page.dart +++ b/embedded/lib/login_page.dart @@ -121,16 +121,15 @@ class Body extends StatelessWidget { }, ), const SizedBox(height: 8), - // Direct Apple login by providing the url of the apple login page + // Login with Apple Provider ElevatedButton( - child: const Text('Direct apple login'), + child: const Text('Login with Apple'), onPressed: () async { - await frontegg.directLogin( - url: - 'https://appleid.apple.com/auth/authorize?response_type=code&response_mode=form_post&redirect_uri=https%3A%2F%2Fauth.davidantoon.me%2Fidentity%2Fresources%2Fauth%2Fv2%2Fuser%2Fsso%2Fapple%2Fpostlogin&scope=openid+name+email&state=%7B%22oauthState%22%3A%22eyJGUk9OVEVHR19PQVVUSF9SRURJUkVDVF9BRlRFUl9MT0dJTiI6ImNvbS5mcm9udGVnZy5kZW1vOi8vYXV0aC5kYXZpZGFudG9vbi5tZS9pb3Mvb2F1dGgvY2FsbGJhY2siLCJGUk9OVEVHR19PQVVUSF9TVEFURV9BRlRFUl9MT0dJTiI6IjQ1MDVkMzljLTg0ZTctNDhiZi1hMzY3LTVmMjhmMmZlMWU1YiJ9%22%2C%22provider%22%3A%22apple%22%2C%22appId%22%3A%22%22%2C%22action%22%3A%22login%22%7D&client_id=com.frontegg.demo.client', + await frontegg.socialLogin( + provider: FronteggSocialProvider.apple, ); - debugPrint('Direct Login Finished'); + debugPrint('Login via Apple Finished'); }, ), const SizedBox(height: 8), diff --git a/embedded/pubspec.lock b/embedded/pubspec.lock index b070e87..e08b1ea 100644 --- a/embedded/pubspec.lock +++ b/embedded/pubspec.lock @@ -139,7 +139,7 @@ packages: path: ".." relative: true source: path - version: "1.0.19" + version: "1.0.21" fuchsia_remote_debug_protocol: dependency: transitive description: flutter diff --git a/hosted/android/app/build.gradle b/hosted/android/app/build.gradle index 9ed6e69..6219c19 100644 --- a/hosted/android/app/build.gradle +++ b/hosted/android/app/build.gradle @@ -122,5 +122,5 @@ flutter { dependencies { androidTestUtil "androidx.test:orchestrator:1.5.1" - implementation 'com.frontegg.sdk:android:1.2.48' + implementation 'com.frontegg.sdk:android:1.3.9' } diff --git a/hosted/ios/Podfile b/hosted/ios/Podfile index efda1b7..566e673 100644 --- a/hosted/ios/Podfile +++ b/hosted/ios/Podfile @@ -31,7 +31,7 @@ target 'Runner' do use_frameworks! use_modular_headers! - pod 'FronteggSwift', '~> 1.2.45' + pod 'FronteggSwift', '~> 1.2.48' flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) diff --git a/hosted/ios/Podfile.lock b/hosted/ios/Podfile.lock index 12b1c60..31f660b 100644 --- a/hosted/ios/Podfile.lock +++ b/hosted/ios/Podfile.lock @@ -3,10 +3,10 @@ PODS: - Flutter (1.0.0) - fluttertoast (0.0.2): - Flutter - - frontegg_flutter (1.0.19): + - frontegg_flutter (1.0.21): - Flutter - - FronteggSwift (~> 1.2.45) - - FronteggSwift (1.2.45) + - FronteggSwift (~> 1.2.48) + - FronteggSwift (1.2.48) - integration_test (0.0.1): - Flutter - path_provider_foundation (0.0.1): @@ -23,7 +23,7 @@ DEPENDENCIES: - Flutter (from `Flutter`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - frontegg_flutter (from `.symlinks/plugins/frontegg_flutter/ios`) - - FronteggSwift (~> 1.2.45) + - FronteggSwift (~> 1.2.48) - integration_test (from `.symlinks/plugins/integration_test/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - patrol (from `.symlinks/plugins/patrol/darwin`) @@ -54,13 +54,13 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 - frontegg_flutter: 690debdf95d2e85ab915fbaa33526255cdb2fd5c - FronteggSwift: d8d3d4c6ddf913f5f15fe11c571abae0d8b36af3 + frontegg_flutter: 6a3064f40610e9284c8e658eb84b6af9681f4714 + FronteggSwift: d68972df823b7f6b896513363107b26926ca924a integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 patrol: 51b76cc7c11a2933ee3e72482d930c75b9d4ec73 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d -PODFILE CHECKSUM: 0f666920353ecaa3a10e8853b7ffd6b3e44e8281 +PODFILE CHECKSUM: 22ae0c8c72248cb2c0b646bb986526a3719cdae5 COCOAPODS: 1.16.2 diff --git a/hosted/lib/login_page.dart b/hosted/lib/login_page.dart index 1e4ed6d..5aab528 100644 --- a/hosted/lib/login_page.dart +++ b/hosted/lib/login_page.dart @@ -42,6 +42,40 @@ class LoginPage extends StatelessWidget { ); } else if (!state.initializing && !state.isAuthenticated) { return const Body(); + } else if (state.isAuthenticated && state.user != null) { + // If user is authenticated, show a message and let MainPage handle the redirect + debugPrint('LoginPage: User is authenticated, showing redirect message'); + return const SizedBox.expand( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('User authenticated, redirecting...'), + ], + ), + ), + ); + } else if (state.isLoading && state.isAuthenticated) { + // Special case: user is authenticated but still loading (hosted mode issue) + debugPrint('LoginPage: Special case - user authenticated but still loading, forcing state update'); + // Try to force state update + WidgetsBinding.instance.addPostFrameCallback((_) { + frontegg.forceStateUpdate(); + }); + return const SizedBox.expand( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('Updating state...'), + ], + ), + ), + ); } return const SizedBox(); }, diff --git a/hosted/pubspec.lock b/hosted/pubspec.lock index 9757cfa..f509b5b 100644 --- a/hosted/pubspec.lock +++ b/hosted/pubspec.lock @@ -139,7 +139,7 @@ packages: path: ".." relative: true source: path - version: "1.0.16" + version: "1.0.21" fuchsia_remote_debug_protocol: dependency: transitive description: flutter diff --git a/ios/frontegg_flutter.podspec b/ios/frontegg_flutter.podspec index a40a944..3ace4f0 100644 --- a/ios/frontegg_flutter.podspec +++ b/ios/frontegg_flutter.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'frontegg_flutter' - s.version = '1.0.20' + s.version = '1.0.21' s.summary = 'A new Flutter plugin project.' s.description = <<-DESC A new Flutter plugin project. @@ -15,7 +15,7 @@ A new Flutter plugin project. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'FronteggSwift', '~> 1.2.45' + s.dependency 'FronteggSwift', '~> 1.2.48' s.platform = :ios, '14.0' # Flutter.framework does not contain a i386 slice. diff --git a/lib/src/frontegg_flutter.dart b/lib/src/frontegg_flutter.dart index 19b9520..c3a2a8e 100644 --- a/lib/src/frontegg_flutter.dart +++ b/lib/src/frontegg_flutter.dart @@ -257,6 +257,18 @@ class FronteggFlutter { return FronteggConstants.fromMap(constants!); }); + /// Forces a state update in hosted mode. + /// + /// This method is useful for debugging hosted mode issues where state + /// doesn't update automatically after authentication. + /// + /// Throws a [FronteggException] if the state update encounters a platform-specific error. + /// + /// Returns a [Future] that completes when the state update is finished. + Future forceStateUpdate() => _runAction(() async { + await FronteggPlatform.instance.forceStateUpdate(); + }); + /// Initiates authorization request using a refresh token. /// /// This method performs the authorization process using the provided refresh token and diff --git a/lib/src/frontegg_method_channel.dart b/lib/src/frontegg_method_channel.dart index a946518..25dd230 100644 --- a/lib/src/frontegg_method_channel.dart +++ b/lib/src/frontegg_method_channel.dart @@ -238,4 +238,8 @@ class FronteggMethodChannel extends FronteggPlatform { "maxAge": maxAge?.inSeconds, }, ); + + @override + Future forceStateUpdate() => + methodChannel.invokeMethod("forceStateUpdate"); } diff --git a/lib/src/frontegg_platform_interface.dart b/lib/src/frontegg_platform_interface.dart index 4a3314f..4f56b86 100644 --- a/lib/src/frontegg_platform_interface.dart +++ b/lib/src/frontegg_platform_interface.dart @@ -97,4 +97,8 @@ abstract class FronteggPlatform extends PlatformInterface { }) { throw UnimplementedError("stepUp() has not been implemented."); } + + Future forceStateUpdate() { + throw UnimplementedError("forceStateUpdate() has not been implemented."); + } } diff --git a/pubspec.yaml b/pubspec.yaml index 46a1d74..b5888f1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: frontegg_flutter description: Flutter package for Frontegg services -version: 1.0.20 +version: 1.0.21 homepage: https://github.com/frontegg/frontegg-flutter #publish_to: "https://pub.dartlang.org"