diff --git a/.github/workflows/supabase_flutter.yml b/.github/workflows/supabase_flutter.yml index b75b7e57d..93ddb0e47 100644 --- a/.github/workflows/supabase_flutter.yml +++ b/.github/workflows/supabase_flutter.yml @@ -68,7 +68,13 @@ jobs: run: | cd ../../ dart pub global activate melos - melos bootstrap + melos bootstrap --ignore="supabase_flutter_example" + + - name: Bootstrap example + if: ${{ matrix.flutter-version == '3.x' }} + run: | + cd ../../ + melos bootstrap --scope="supabase_flutter_example" - name: Run formatter if: ${{ matrix.flutter-version == '3.x'}} @@ -104,7 +110,7 @@ jobs: if: ${{ matrix.os == 'ubuntu-latest' && matrix.flutter-version == '3.x'}} run: | sudo apt-get update - sudo apt-get install -y libgtk-3-dev libblkid-dev liblzma-dev + sudo apt-get install -y libgtk-3-dev libblkid-dev liblzma-dev libsecret-1-dev cd example flutter build linux diff --git a/packages/supabase_flutter/example/README.md b/packages/supabase_flutter/example/README.md index 1f73590d4..4b58c5c9e 100644 --- a/packages/supabase_flutter/example/README.md +++ b/packages/supabase_flutter/example/README.md @@ -60,3 +60,33 @@ create policy "Anyone can update an avatar." on storage.objects - Flutter user management: https://github.com/supabase/supabase/tree/master/examples/user-management/flutter-user-management - Extended flutter user management with web support, github login, recovery password flow: https://github.com/phamhieu/supabase-flutter-demo - Real time chat application: https://github.com/supabase-community/flutter-chat + +## Facebook SDK login setup + +This example uses `flutter_facebook_auth` and `signInWithIdToken`. To make the +SDK login work end-to-end, configure Facebook and Supabase, then update the +platform files below. + +### Supabase + +- Enable Facebook in Auth providers. +- Add your Facebook App ID and App Secret in the Supabase dashboard. + +### Android + +1. Update `android/app/src/main/res/values/strings.xml`: + - `facebook_app_id` + - `facebook_client_token` + - `fb_login_protocol_scheme` (use `fb`) +2. Ensure `android/app/src/main/AndroidManifest.xml` keeps the Facebook + activity and metadata entries. +3. Add your package name and key hashes in the Facebook developer console. + +### iOS + +1. Update `ios/Runner/Info.plist`: + - `FacebookAppID` + - `FacebookClientToken` + - `CFBundleURLTypes` scheme `fb` +2. Ensure `ios/Runner/AppDelegate.swift` initializes `FBSDKCoreKit`. +3. Add your bundle ID in the Facebook developer console. diff --git a/packages/supabase_flutter/example/android/app/src/main/AndroidManifest.xml b/packages/supabase_flutter/example/android/app/src/main/AndroidManifest.xml index 74a78b939..c229f92ad 100644 --- a/packages/supabase_flutter/example/android/app/src/main/AndroidManifest.xml +++ b/packages/supabase_flutter/example/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,28 @@ android:label="example" android:name="${applicationName}" android:icon="@mipmap/ic_launcher"> + + + + + + + + + + + + + + + example + YOUR_FACEBOOK_APP_ID + YOUR_FACEBOOK_CLIENT_TOKEN + fbYOUR_FACEBOOK_APP_ID + diff --git a/packages/supabase_flutter/example/ios/Podfile.lock b/packages/supabase_flutter/example/ios/Podfile.lock index ef2820fff..39f113d0f 100644 --- a/packages/supabase_flutter/example/ios/Podfile.lock +++ b/packages/supabase_flutter/example/ios/Podfile.lock @@ -1,7 +1,20 @@ PODS: - - app_links (0.0.2): + - app_links (7.0.0): - Flutter + - FBAEMKit (18.0.2): + - FBSDKCoreKit_Basics (= 18.0.2) + - FBSDKCoreKit (18.0.2): + - FBAEMKit (= 18.0.2) + - FBSDKCoreKit_Basics (= 18.0.2) + - FBSDKCoreKit_Basics (18.0.2) + - FBSDKLoginKit (18.0.2): + - FBSDKCoreKit (= 18.0.2) - Flutter (1.0.0) + - flutter_facebook_auth (7.1.5): + - FBSDKLoginKit (= 18.0.2) + - Flutter + - flutter_secure_storage (6.0.0): + - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -14,15 +27,28 @@ PODS: DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) - Flutter (from `Flutter`) + - flutter_facebook_auth (from `.symlinks/plugins/flutter_facebook_auth/ios`) + - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) +SPEC REPOS: + trunk: + - FBAEMKit + - FBSDKCoreKit + - FBSDKCoreKit_Basics + - FBSDKLoginKit + EXTERNAL SOURCES: app_links: :path: ".symlinks/plugins/app_links/ios" Flutter: :path: Flutter + flutter_facebook_auth: + :path: ".symlinks/plugins/flutter_facebook_auth/ios" + flutter_secure_storage: + :path: ".symlinks/plugins/flutter_secure_storage/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" shared_preferences_foundation: @@ -31,12 +57,18 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - app_links: f3e17e4ee5e357b39d8b95290a9b2c299fca71c6 + app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8 + FBAEMKit: 56a4f5a9607957b547b6458b17a98c5bb2736367 + FBSDKCoreKit: 54f1240567e16862ab71c53c33dc347db5c524c5 + FBSDKCoreKit_Basics: eb530cd6a79030dbb11df460aafb2767bcad9028 + FBSDKLoginKit: 6a70798793e499335a98fab1a758d7da34af196e Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + flutter_facebook_auth: fa3b69b1df0831d2178fe87a05ee2d7d0156db70 + flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b PODFILE CHECKSUM: 0dbd5a87e0ace00c9610d2037ac22083a01f861d -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/packages/supabase_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/supabase_flutter/example/ios/Runner.xcodeproj/project.pbxproj index f026874e4..f2abf7ec5 100644 --- a/packages/supabase_flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/supabase_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -68,7 +68,6 @@ 9F7A42A0F8397A30C48EF6B6 /* Pods-Runner.release.xcconfig */, FC8416E7A50F84EA287634DD /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -359,13 +358,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ELTTE7K8TT; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = "com.supabase.supabase-flutter"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -487,13 +487,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ELTTE7K8TT; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = "com.supabase.supabase-flutter"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -509,13 +510,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ELTTE7K8TT; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = "com.supabase.supabase-flutter"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/packages/supabase_flutter/example/ios/Runner/AppDelegate.swift b/packages/supabase_flutter/example/ios/Runner/AppDelegate.swift index b63630348..7d621cd1e 100644 --- a/packages/supabase_flutter/example/ios/Runner/AppDelegate.swift +++ b/packages/supabase_flutter/example/ios/Runner/AppDelegate.swift @@ -1,5 +1,6 @@ import UIKit import Flutter +import FBSDKCoreKit @main @objc class AppDelegate: FlutterAppDelegate { @@ -8,6 +9,21 @@ import Flutter didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) + ApplicationDelegate.shared.application( + application, + didFinishLaunchingWithOptions: launchOptions + ) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + override func application( + _ app: UIApplication, + open url: URL, + options: [UIApplication.OpenURLOptionsKey: Any] = [:] + ) -> Bool { + if ApplicationDelegate.shared.application(app, open: url, options: options) { + return true + } + return super.application(app, open: url, options: options) + } } diff --git a/packages/supabase_flutter/example/ios/Runner/Info.plist b/packages/supabase_flutter/example/ios/Runner/Info.plist index 7f553465b..163d6128c 100644 --- a/packages/supabase_flutter/example/ios/Runner/Info.plist +++ b/packages/supabase_flutter/example/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -20,10 +22,34 @@ $(FLUTTER_BUILD_NAME) CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleURLSchemes + + fbYOUR_FACEBOOK_APP_ID + + + CFBundleVersion $(FLUTTER_BUILD_NUMBER) + FacebookAppID + YOUR_FACEBOOK_APP_ID + FacebookClientToken + YOUR_FACEBOOK_CLIENT_TOKEN + FacebookDisplayName + Example + LSApplicationQueriesSchemes + + fbapi + fb-messenger-api + fbauth2 + fbshareextension + LSRequiresIPhoneOS + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -43,9 +69,5 @@ UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/packages/supabase_flutter/example/lib/main.dart b/packages/supabase_flutter/example/lib/main.dart index eb60a4749..48d8d20e1 100644 --- a/packages/supabase_flutter/example/lib/main.dart +++ b/packages/supabase_flutter/example/lib/main.dart @@ -1,8 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter_facebook_auth/flutter_facebook_auth.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; Future main() async { - await Supabase.initialize(url: 'SUPABASE_URL', anonKey: 'SUPABASE_ANON_KEY'); + await Supabase.initialize( + url: 'SUPABASE_URL', + anonKey: 'SUPABASE_ANON_KEY', + ); runApp(const MyApp()); } @@ -74,6 +78,45 @@ class _LoginFormState extends State<_LoginForm> { super.dispose(); } + Future _signInWithFacebook() async { + setState(() { + _loading = true; + }); + final ScaffoldMessengerState scaffoldMessenger = + ScaffoldMessenger.of(context); + try { + final result = await FacebookAuth.instance.login(); + if (result.status != LoginStatus.success) { + scaffoldMessenger.showSnackBar(SnackBar( + content: Text(result.message ?? 'Facebook sign-in cancelled'), + backgroundColor: Colors.red, + )); + setState(() { + _loading = false; + }); + return; + } + final idToken = result.accessToken?.tokenString; + if (idToken == null) { + scaffoldMessenger.showSnackBar(const SnackBar( + content: Text('Facebook ID token is required'), + backgroundColor: Colors.red, + )); + return; + } + await Supabase.instance.client.auth.signInWithIdToken( + provider: OAuthProvider.facebook, idToken: idToken); + } catch (e) { + scaffoldMessenger.showSnackBar(SnackBar( + content: Text('Facebook sign-in failed: ${e.toString()}'), + backgroundColor: Colors.red, + )); + setState(() { + _loading = false; + }); + } + } + @override Widget build(BuildContext context) { return _loading @@ -146,6 +189,11 @@ class _LoginFormState extends State<_LoginForm> { }, child: const Text('Signup'), ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: _signInWithFacebook, + child: const Text('Sign in with Facebook (SDK)'), + ), ], ); } @@ -177,29 +225,31 @@ class _ProfileFormState extends State<_ProfileForm> { } Future _loadProfile() async { - final ScaffoldMessengerState scaffoldMessenger = - ScaffoldMessenger.of(context); try { final userId = Supabase.instance.client.auth.currentUser!.id; final data = (await Supabase.instance.client .from('profiles') .select() .match({'id': userId}).maybeSingle()); - if (data != null) { + if (data != null && mounted) { setState(() { _usernameController.text = data['username']; _websiteController.text = data['website']; }); } } catch (e) { - scaffoldMessenger.showSnackBar(const SnackBar( - content: Text('Error occurred while getting profile'), - backgroundColor: Colors.red, - )); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Error occurred while getting profile'), + backgroundColor: Colors.red, + )); + } + } + if (mounted) { + setState(() { + _loading = false; + }); } - setState(() { - _loading = false; - }); } @override diff --git a/packages/supabase_flutter/example/linux/flutter/generated_plugin_registrant.cc b/packages/supabase_flutter/example/linux/flutter/generated_plugin_registrant.cc index 3792af4b6..5a27a5d60 100644 --- a/packages/supabase_flutter/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/supabase_flutter/example/linux/flutter/generated_plugin_registrant.cc @@ -6,10 +6,14 @@ #include "generated_plugin_registrant.h" +#include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); g_autoptr(FlPluginRegistrar) gtk_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); gtk_plugin_register_with_registrar(gtk_registrar); diff --git a/packages/supabase_flutter/example/linux/flutter/generated_plugins.cmake b/packages/supabase_flutter/example/linux/flutter/generated_plugins.cmake index 21d8f8bbb..015388dcb 100644 --- a/packages/supabase_flutter/example/linux/flutter/generated_plugins.cmake +++ b/packages/supabase_flutter/example/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_linux gtk url_launcher_linux ) diff --git a/packages/supabase_flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/supabase_flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift index a620c94b7..bfffc5d14 100644 --- a/packages/supabase_flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/supabase_flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,11 +6,15 @@ import FlutterMacOS import Foundation import app_links +import facebook_auth_desktop +import flutter_secure_storage_darwin import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) + FacebookAuthDesktopPlugin.register(with: registry.registrar(forPlugin: "FacebookAuthDesktopPlugin")) + FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/packages/supabase_flutter/example/pubspec.yaml b/packages/supabase_flutter/example/pubspec.yaml index a7b6dd1e5..adc97972f 100644 --- a/packages/supabase_flutter/example/pubspec.yaml +++ b/packages/supabase_flutter/example/pubspec.yaml @@ -11,13 +11,22 @@ environment: dependencies: flutter: sdk: flutter + flutter_facebook_auth: ^7.0.0 supabase_flutter: ^2.12.3 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^3.0.1 + flutter_lints: ^6.0.0 + +dependency_overrides: + flutter_secure_storage: ^10.0.0 + flutter_secure_storage_platform_interface: ^2.0.1 + flutter_secure_storage_web: ^2.0.0 + flutter_secure_storage_linux: ^2.0.0 + flutter_secure_storage_macos: ^4.0.0 + flutter_secure_storage_windows: ^4.0.0 flutter: uses-material-design: true diff --git a/packages/supabase_flutter/example/windows/flutter/generated_plugin_registrant.cc b/packages/supabase_flutter/example/windows/flutter/generated_plugin_registrant.cc index 785a046f9..563050d09 100644 --- a/packages/supabase_flutter/example/windows/flutter/generated_plugin_registrant.cc +++ b/packages/supabase_flutter/example/windows/flutter/generated_plugin_registrant.cc @@ -7,11 +7,14 @@ #include "generated_plugin_registrant.h" #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { AppLinksPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("AppLinksPluginCApi")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/packages/supabase_flutter/example/windows/flutter/generated_plugins.cmake b/packages/supabase_flutter/example/windows/flutter/generated_plugins.cmake index 642b1bed3..889e4c3c9 100644 --- a/packages/supabase_flutter/example/windows/flutter/generated_plugins.cmake +++ b/packages/supabase_flutter/example/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST app_links + flutter_secure_storage_windows url_launcher_windows )