From 92b7aead7ec30ae04f485b87500f63bd0b7d6337 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka <45696119+bdlukaa@users.noreply.github.com> Date: Sun, 18 Jul 2021 18:55:18 -0300 Subject: [PATCH 1/7] Update implementation --- lib/src/supabase.dart | 66 +++++++++++------------ lib/src/supabase_auth_required_state.dart | 21 ++++---- lib/src/supabase_auth_state.dart | 27 +++++----- lib/src/supabase_deep_linking_mixin.dart | 8 +-- lib/src/supabase_state.dart | 2 +- test/supabase_flutter_test.dart | 12 +++-- 6 files changed, 70 insertions(+), 66 deletions(-) diff --git a/lib/src/supabase.dart b/lib/src/supabase.dart index d2d1ff16..3c0ea01b 100644 --- a/lib/src/supabase.dart +++ b/lib/src/supabase.dart @@ -27,17 +27,16 @@ Future defPersistSession(String persistSessionString) async { } class LocalStorage { - LocalStorage( - {Future Function()? hasAccessToken, - Future Function()? accessToken, - Future Function()? removePersistedSession, - Future Function(String)? persistSession}) { - this.hasAccessToken = hasAccessToken ?? defHasAccessToken; - this.accessToken = accessToken ?? defAccessToken; - this.removePersistedSession = - removePersistedSession ?? defRemovePersistedSession; - this.persistSession = persistSession ?? defPersistSession; - } + LocalStorage({ + Future Function()? hasAccessToken, + Future Function()? accessToken, + Future Function()? removePersistedSession, + Future Function(String)? persistSession, + }) : hasAccessToken = hasAccessToken ?? defHasAccessToken, + accessToken = accessToken ?? defAccessToken, + removePersistedSession = + removePersistedSession ?? defRemovePersistedSession, + persistSession = persistSession ?? defPersistSession; Future Function() hasAccessToken = defHasAccessToken; Future Function() accessToken = defAccessToken; @@ -46,7 +45,19 @@ class LocalStorage { } class Supabase { - factory Supabase({ + /// Gets the current supabase instance. + /// + /// An error is thrown if supabase isn't initialized yet + static Supabase get instance { + assert( + _instance._initialized, + 'You must initialize the supabase instance before calling Supabase.instance', + ); + return _instance; + } + + /// Initialize the current supabase instance + factory Supabase.initialize({ String? url, String? anonKey, String? authCallbackUrlHostname, @@ -67,7 +78,9 @@ class Supabase { Supabase._privateConstructor(); static final Supabase _instance = Supabase._privateConstructor(); - SupabaseClient? _client; + bool _initialized = false; + + late SupabaseClient _client; GotrueSubscription? _initialClientSubscription; bool _initialDeeplinkIsHandled = false; bool _debugEnable = false; @@ -79,36 +92,23 @@ class Supabase { if (_initialClientSubscription != null) { _initialClientSubscription!.data!.unsubscribe(); } + _initialized = false; } void _init(String supabaseUrl, String supabaseAnonKey) { - if (_client != null) { - throw 'Supabase client is initialized more than once $_client'; - } - _client = SupabaseClient(supabaseUrl, supabaseAnonKey); _initialClientSubscription = - _client!.auth.onAuthStateChange(_onAuthStateChange); + _client.auth.onAuthStateChange(_onAuthStateChange); + _initialized = true; } - SupabaseClient get client { - if (_client == null) { - throw 'Supabase client is not initialized'; - } - return _client!; - } + SupabaseClient get client => _client; - LocalStorage get localStorage { - return _localStorage; - } + LocalStorage get localStorage => _localStorage; - Future get hasAccessToken async { - return _localStorage.hasAccessToken(); - } + Future get hasAccessToken async => _localStorage.hasAccessToken(); - Future get accessToken async { - return _localStorage.accessToken(); - } + Future get accessToken async => _localStorage.accessToken(); void _onAuthStateChange(AuthChangeEvent event, Session? session) { log('**** onAuthStateChange: $event'); diff --git a/lib/src/supabase_auth_required_state.dart b/lib/src/supabase_auth_required_state.dart index a8d20eff..7f9930e0 100644 --- a/lib/src/supabase_auth_required_state.dart +++ b/lib/src/supabase_auth_required_state.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:supabase/supabase.dart'; import 'package:supabase_flutter/src/supabase.dart'; import 'package:supabase_flutter/src/supabase_state.dart'; @@ -10,22 +10,22 @@ abstract class SupabaseAuthRequiredState void initState() { super.initState(); - if (Supabase().client.auth.currentSession == null) { + if (Supabase.instance.client.auth.currentSession == null) { _recoverSupabaseSession(); } else { - onAuthenticated(Supabase().client.auth.currentSession!); + onAuthenticated(Supabase.instance.client.auth.currentSession!); } } @override void startAuthObserver() { - Supabase().log('***** SupabaseAuthRequiredState startAuthObserver'); + Supabase.instance.log('***** SupabaseAuthRequiredState startAuthObserver'); WidgetsBinding.instance?.addObserver(this); } @override void stopAuthObserver() { - Supabase().log('***** SupabaseAuthRequiredState stopAuthObserver'); + Supabase.instance.log('***** SupabaseAuthRequiredState stopAuthObserver'); WidgetsBinding.instance?.removeObserver(this); } @@ -45,26 +45,27 @@ abstract class SupabaseAuthRequiredState } Future onResumed() async { - Supabase().log('***** SupabaseAuthRequiredState onResumed'); + Supabase.instance.log('***** SupabaseAuthRequiredState onResumed'); return _recoverSupabaseSession(); } Future _recoverSupabaseSession() async { - final bool exist = await Supabase().localStorage.hasAccessToken(); + final bool exist = await Supabase.instance.localStorage.hasAccessToken(); if (!exist) { onUnauthenticated(); return false; } - final String? jsonStr = await Supabase().localStorage.accessToken(); + final String? jsonStr = await Supabase.instance.localStorage.accessToken(); if (jsonStr == null) { onUnauthenticated(); return false; } - final response = await Supabase().client.auth.recoverSession(jsonStr); + final response = + await Supabase.instance.client.auth.recoverSession(jsonStr); if (response.error != null) { - Supabase().localStorage.removePersistedSession(); + Supabase.instance.localStorage.removePersistedSession(); onUnauthenticated(); return false; } else { diff --git a/lib/src/supabase_auth_state.dart b/lib/src/supabase_auth_state.dart index 9871ff27..dd0ec3aa 100644 --- a/lib/src/supabase_auth_state.dart +++ b/lib/src/supabase_auth_state.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:supabase/supabase.dart'; import 'package:supabase_flutter/src/supabase.dart'; import 'package:supabase_flutter/src/supabase_state.dart'; @@ -10,21 +10,21 @@ abstract class SupabaseAuthState extends SupabaseState with SupabaseDeepLinkingMixin { @override void startAuthObserver() { - Supabase().log('***** SupabaseAuthState startAuthObserver'); + Supabase.instance.log('***** SupabaseAuthState startAuthObserver'); startDeeplinkObserver(); } @override void stopAuthObserver() { - Supabase().log('***** SupabaseAuthState stopAuthObserver'); + Supabase.instance.log('***** SupabaseAuthState stopAuthObserver'); stopDeeplinkObserver(); } @override Future handleDeeplink(Uri uri) async { - if (!Supabase().isAuthCallbackDeeplink(uri)) return false; + if (!Supabase.instance.isAuthCallbackDeeplink(uri)) return false; - Supabase().log('***** SupabaseAuthState handleDeeplink $uri'); + Supabase.instance.log('***** SupabaseAuthState handleDeeplink $uri'); // notify auth deeplink received onReceivedAuthDeeplink(uri); @@ -34,15 +34,15 @@ abstract class SupabaseAuthState @override void onErrorReceivingDeeplink(String message) { - Supabase().log('onErrorReceivingDeppLink message: $message'); + Supabase.instance.log('onErrorReceivingDeppLink message: $message'); } Future recoverSessionFromUrl(Uri uri) async { - final uriParameters = Supabase().parseUriParameters(uri); + final uriParameters = Supabase.instance.parseUriParameters(uri); final type = uriParameters['type'] ?? ''; // recover session from deeplink - final response = await Supabase().client.auth.getSessionFromUrl(uri); + final response = await Supabase.instance.client.auth.getSessionFromUrl(uri); if (response.error != null) { onErrorAuthenticating(response.error!.message); } else { @@ -58,21 +58,22 @@ abstract class SupabaseAuthState /// Recover/refresh session if it's available /// e.g. called on a Splash screen when app starts. Future recoverSupabaseSession() async { - final bool exist = await Supabase().localStorage.hasAccessToken(); + final bool exist = await Supabase.instance.localStorage.hasAccessToken(); if (!exist) { onUnauthenticated(); return false; } - final String? jsonStr = await Supabase().localStorage.accessToken(); + final String? jsonStr = await Supabase.instance.localStorage.accessToken(); if (jsonStr == null) { onUnauthenticated(); return false; } - final response = await Supabase().client.auth.recoverSession(jsonStr); + final response = + await Supabase.instance.client.auth.recoverSession(jsonStr); if (response.error != null) { - Supabase().localStorage.removePersistedSession(); + Supabase.instance.localStorage.removePersistedSession(); onUnauthenticated(); return false; } else { @@ -83,7 +84,7 @@ abstract class SupabaseAuthState /// Callback when deeplink received and is processing. Optional void onReceivedAuthDeeplink(Uri uri) { - Supabase().log('onReceivedAuthDeeplink uri: $uri'); + Supabase.instance.log('onReceivedAuthDeeplink uri: $uri'); } /// Callback when user is unauthenticated diff --git a/lib/src/supabase_deep_linking_mixin.dart b/lib/src/supabase_deep_linking_mixin.dart index d6b6df13..dbd41876 100644 --- a/lib/src/supabase_deep_linking_mixin.dart +++ b/lib/src/supabase_deep_linking_mixin.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter/services.dart'; import 'package:supabase_flutter/src/supabase.dart'; import 'package:uni_links/uni_links.dart'; @@ -10,13 +10,13 @@ mixin SupabaseDeepLinkingMixin on State { StreamSubscription? _sub; void startDeeplinkObserver() { - Supabase().log('***** SupabaseDeepLinkingMixin startAuthObserver'); + Supabase.instance.log('***** SupabaseDeepLinkingMixin startAuthObserver'); _handleIncomingLinks(); _handleInitialUri(); } void stopDeeplinkObserver() { - Supabase().log('***** SupabaseDeepLinkingMixin stopAuthObserver'); + Supabase.instance.log('***** SupabaseDeepLinkingMixin stopAuthObserver'); if (_sub != null) _sub?.cancel(); } @@ -45,7 +45,7 @@ mixin SupabaseDeepLinkingMixin on State { /// /// We handle all exceptions, since it is called from initState. Future _handleInitialUri() async { - if (!Supabase().shouldHandleInitialDeeplink()) return; + if (!Supabase.instance.shouldHandleInitialDeeplink()) return; try { final uri = await getInitialUri(); diff --git a/lib/src/supabase_state.dart b/lib/src/supabase_state.dart index 2436a97b..55af5544 100644 --- a/lib/src/supabase_state.dart +++ b/lib/src/supabase_state.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; /// Interface for screen that requires an authenticated user abstract class SupabaseState extends State { diff --git a/test/supabase_flutter_test.dart b/test/supabase_flutter_test.dart index 687494bf..aa466d69 100644 --- a/test/supabase_flutter_test.dart +++ b/test/supabase_flutter_test.dart @@ -8,18 +8,20 @@ void main() { setUpAll(() { // initial Supabase singleton - Supabase(url: supabaseUrl, anonKey: supabaseKey); + Supabase.initialize(url: supabaseUrl, anonKey: supabaseKey); }); + final instance = Supabase.instance; + test('can access Supabase singleton', () async { - final client = Supabase().client; + final client = instance.client; expect(client, isNotNull); }); test('can parse deeplink', () async { final uri = Uri.parse( "io.supabase.flutterdemo://login-callback#access_token=aaa&expires_in=3600&refresh_token=bbb&token_type=bearer&type=recovery"); - final uriParams = Supabase().parseUriParameters(uri); + final uriParams = instance.parseUriParameters(uri); expect(uriParams.length, equals(5)); expect(uriParams['access_token'], equals('aaa')); expect(uriParams['refresh_token'], equals('bbb')); @@ -28,7 +30,7 @@ void main() { test('can parse flutter web redirect link', () async { final uri = Uri.parse( "http://localhost:55510/#access_token=aaa&expires_in=3600&refresh_token=bbb&token_type=bearer&type=magiclink"); - final uriParams = Supabase().parseUriParameters(uri); + final uriParams = instance.parseUriParameters(uri); expect(uriParams.length, equals(5)); expect(uriParams['access_token'], equals('aaa')); expect(uriParams['refresh_token'], equals('bbb')); @@ -37,7 +39,7 @@ void main() { test('can parse flutter web custom page redirect link', () async { final uri = Uri.parse( "http://localhost:55510/#/webAuth%23access_token=aaa&expires_in=3600&refresh_token=bbb&token_type=bearer&type=magiclink"); - final uriParams = Supabase().parseUriParameters(uri); + final uriParams = instance.parseUriParameters(uri); expect(uriParams.length, equals(5)); expect(uriParams['access_token'], equals('aaa')); expect(uriParams['refresh_token'], equals('bbb')); From 1a63409c18617e48b2436e8c4c3f514ce679a32b Mon Sep 17 00:00:00 2001 From: Bruno D'Luka <45696119+bdlukaa@users.noreply.github.com> Date: Sun, 18 Jul 2021 19:12:54 -0300 Subject: [PATCH 2/7] Update documentation --- lib/src/supabase.dart | 59 +++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/lib/src/supabase.dart b/lib/src/supabase.dart index 3c0ea01b..72cab732 100644 --- a/lib/src/supabase.dart +++ b/lib/src/supabase.dart @@ -4,44 +4,41 @@ import 'package:url_launcher/url_launcher.dart'; const supabasePersistSessionKey = 'SUPABASE_PERSIST_SESSION_KEY'; -Future defHasAccessToken() async { +Future _defHasAccessToken() async { final prefs = await SharedPreferences.getInstance(); final exist = prefs.containsKey(supabasePersistSessionKey); return exist; } -Future defAccessToken() async { +Future _defAccessToken() async { final prefs = await SharedPreferences.getInstance(); final jsonStr = prefs.getString(supabasePersistSessionKey); return jsonStr; } -Future defRemovePersistedSession() async { +Future _defRemovePersistedSession() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); return prefs.remove(supabasePersistSessionKey); } -Future defPersistSession(String persistSessionString) async { +Future _defPersistSession(String persistSessionString) async { final prefs = await SharedPreferences.getInstance(); return prefs.setString(supabasePersistSessionKey, persistSessionString); } class LocalStorage { - LocalStorage({ - Future Function()? hasAccessToken, - Future Function()? accessToken, - Future Function()? removePersistedSession, - Future Function(String)? persistSession, - }) : hasAccessToken = hasAccessToken ?? defHasAccessToken, - accessToken = accessToken ?? defAccessToken, - removePersistedSession = - removePersistedSession ?? defRemovePersistedSession, - persistSession = persistSession ?? defPersistSession; - - Future Function() hasAccessToken = defHasAccessToken; - Future Function() accessToken = defAccessToken; - Future Function() removePersistedSession = defRemovePersistedSession; - Future Function(String) persistSession = defPersistSession; + /// Creates a `LocalStorage` instance + const LocalStorage({ + this.hasAccessToken = _defHasAccessToken, + this.accessToken = _defAccessToken, + this.removePersistedSession = _defRemovePersistedSession, + this.persistSession = _defPersistSession, + }); + + final Future Function() hasAccessToken; + final Future Function() accessToken; + final Future Function() removePersistedSession; + final Future Function(String) persistSession; } class Supabase { @@ -57,6 +54,9 @@ class Supabase { } /// Initialize the current supabase instance + /// + /// This must be called only once. If called more than once, an + /// [AssertionError] is thrown factory Supabase.initialize({ String? url, String? anonKey, @@ -64,6 +64,10 @@ class Supabase { bool? debug, LocalStorage? localStorage, }) { + assert( + !_instance._initialized, + 'This instance is already initialized', + ); if (url != null && anonKey != null) { _instance._init(url, anonKey); _instance._authCallbackUrlHostname = authCallbackUrlHostname; @@ -80,7 +84,8 @@ class Supabase { bool _initialized = false; - late SupabaseClient _client; + /// The supabase client for this instance + late final SupabaseClient client; GotrueSubscription? _initialClientSubscription; bool _initialDeeplinkIsHandled = false; bool _debugEnable = false; @@ -88,6 +93,7 @@ class Supabase { String? _authCallbackUrlHostname; LocalStorage _localStorage = LocalStorage(); + /// Dispose the instance void dispose() { if (_initialClientSubscription != null) { _initialClientSubscription!.data!.unsubscribe(); @@ -96,19 +102,17 @@ class Supabase { } void _init(String supabaseUrl, String supabaseAnonKey) { - _client = SupabaseClient(supabaseUrl, supabaseAnonKey); + client = SupabaseClient(supabaseUrl, supabaseAnonKey); _initialClientSubscription = - _client.auth.onAuthStateChange(_onAuthStateChange); + client.auth.onAuthStateChange(_onAuthStateChange); _initialized = true; } - SupabaseClient get client => _client; - LocalStorage get localStorage => _localStorage; - Future get hasAccessToken async => _localStorage.hasAccessToken(); + Future get hasAccessToken => _localStorage.hasAccessToken(); - Future get accessToken async => _localStorage.accessToken(); + Future get accessToken => _localStorage.accessToken(); void _onAuthStateChange(AuthChangeEvent event, Session? session) { log('**** onAuthStateChange: $event'); @@ -172,7 +176,8 @@ class Supabase { } extension GoTrueClientSignInProvider on GoTrueClient { - Future signInWithProvider(Provider? provider, + /// Signs the user in using a thrid parties providers. + Future signInWithProvider(Provider provider, {AuthOptions? options}) async { final res = await signIn( provider: provider, From a3782cdb048f7ca902e24ec7a3275f9cb0d1284c Mon Sep 17 00:00:00 2001 From: Bruno D'Luka <45696119+bdlukaa@users.noreply.github.com> Date: Sun, 18 Jul 2021 19:32:43 -0300 Subject: [PATCH 3/7] Rework the local storage implementation --- lib/src/supabase.dart | 89 +++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/lib/src/supabase.dart b/lib/src/supabase.dart index 72cab732..59d7faf3 100644 --- a/lib/src/supabase.dart +++ b/lib/src/supabase.dart @@ -4,41 +4,63 @@ import 'package:url_launcher/url_launcher.dart'; const supabasePersistSessionKey = 'SUPABASE_PERSIST_SESSION_KEY'; -Future _defHasAccessToken() async { - final prefs = await SharedPreferences.getInstance(); - final exist = prefs.containsKey(supabasePersistSessionKey); - return exist; -} +class LocalStorage { + /// Creates a `LocalStorage` instance + LocalStorage({ + required this.hasAccessToken, + required this.accessToken, + required this.removePersistedSession, + required this.persistSession, + }); -Future _defAccessToken() async { - final prefs = await SharedPreferences.getInstance(); - final jsonStr = prefs.getString(supabasePersistSessionKey); - return jsonStr; -} + bool Function() hasAccessToken; + String? Function() accessToken; + void Function() removePersistedSession; + void Function(String) persistSession; -Future _defRemovePersistedSession() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - return prefs.remove(supabasePersistSessionKey); + Future init() async {} } -Future _defPersistSession(String persistSessionString) async { - final prefs = await SharedPreferences.getInstance(); - return prefs.setString(supabasePersistSessionKey, persistSessionString); -} +class _DefaultLocalStorage extends LocalStorage { + late SharedPreferences sharedPreferencesInstance; + + _DefaultLocalStorage() + : super( + accessToken: () => null, + hasAccessToken: () => false, + persistSession: (_) {}, + removePersistedSession: () {}, + ); + + @override + Future init() async { + sharedPreferencesInstance = await SharedPreferences.getInstance(); + hasAccessToken = _defHasAccessToken; + accessToken = _defAccessToken; + persistSession = _defPersistSession; + removePersistedSession = _defRemovePersistedSession; + } -class LocalStorage { - /// Creates a `LocalStorage` instance - const LocalStorage({ - this.hasAccessToken = _defHasAccessToken, - this.accessToken = _defAccessToken, - this.removePersistedSession = _defRemovePersistedSession, - this.persistSession = _defPersistSession, - }); + bool _defHasAccessToken() { + final exist = + sharedPreferencesInstance.containsKey(supabasePersistSessionKey); + return exist; + } + + String? _defAccessToken() { + final jsonStr = + sharedPreferencesInstance.getString(supabasePersistSessionKey); + return jsonStr; + } + + void _defRemovePersistedSession() { + sharedPreferencesInstance.remove(supabasePersistSessionKey); + } - final Future Function() hasAccessToken; - final Future Function() accessToken; - final Future Function() removePersistedSession; - final Future Function(String) persistSession; + void _defPersistSession(String persistSessionString) { + sharedPreferencesInstance.setString( + supabasePersistSessionKey, persistSessionString); + } } class Supabase { @@ -72,7 +94,8 @@ class Supabase { _instance._init(url, anonKey); _instance._authCallbackUrlHostname = authCallbackUrlHostname; _instance._debugEnable = debug ?? false; - _instance._localStorage = localStorage ?? LocalStorage(); + _instance._localStorage = (localStorage ?? _DefaultLocalStorage()) + ..init(); _instance.log('***** Supabase init completed $_instance'); } @@ -91,7 +114,7 @@ class Supabase { bool _debugEnable = false; String? _authCallbackUrlHostname; - LocalStorage _localStorage = LocalStorage(); + LocalStorage _localStorage = _DefaultLocalStorage()..init(); /// Dispose the instance void dispose() { @@ -110,9 +133,9 @@ class Supabase { LocalStorage get localStorage => _localStorage; - Future get hasAccessToken => _localStorage.hasAccessToken(); + bool get hasAccessToken => _localStorage.hasAccessToken(); - Future get accessToken => _localStorage.accessToken(); + String? get accessToken => _localStorage.accessToken(); void _onAuthStateChange(AuthChangeEvent event, Session? session) { log('**** onAuthStateChange: $event'); From b715e67e23d5d8cf7a33f8b63dd83ea9c8f65959 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka <45696119+bdlukaa@users.noreply.github.com> Date: Sun, 18 Jul 2021 19:35:27 -0300 Subject: [PATCH 4/7] Revert "Rework the local storage implementation" This reverts commit a3782cdb048f7ca902e24ec7a3275f9cb0d1284c. --- lib/src/supabase.dart | 89 ++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 56 deletions(-) diff --git a/lib/src/supabase.dart b/lib/src/supabase.dart index 59d7faf3..72cab732 100644 --- a/lib/src/supabase.dart +++ b/lib/src/supabase.dart @@ -4,63 +4,41 @@ import 'package:url_launcher/url_launcher.dart'; const supabasePersistSessionKey = 'SUPABASE_PERSIST_SESSION_KEY'; -class LocalStorage { - /// Creates a `LocalStorage` instance - LocalStorage({ - required this.hasAccessToken, - required this.accessToken, - required this.removePersistedSession, - required this.persistSession, - }); - - bool Function() hasAccessToken; - String? Function() accessToken; - void Function() removePersistedSession; - void Function(String) persistSession; - - Future init() async {} +Future _defHasAccessToken() async { + final prefs = await SharedPreferences.getInstance(); + final exist = prefs.containsKey(supabasePersistSessionKey); + return exist; } -class _DefaultLocalStorage extends LocalStorage { - late SharedPreferences sharedPreferencesInstance; - - _DefaultLocalStorage() - : super( - accessToken: () => null, - hasAccessToken: () => false, - persistSession: (_) {}, - removePersistedSession: () {}, - ); - - @override - Future init() async { - sharedPreferencesInstance = await SharedPreferences.getInstance(); - hasAccessToken = _defHasAccessToken; - accessToken = _defAccessToken; - persistSession = _defPersistSession; - removePersistedSession = _defRemovePersistedSession; - } +Future _defAccessToken() async { + final prefs = await SharedPreferences.getInstance(); + final jsonStr = prefs.getString(supabasePersistSessionKey); + return jsonStr; +} - bool _defHasAccessToken() { - final exist = - sharedPreferencesInstance.containsKey(supabasePersistSessionKey); - return exist; - } +Future _defRemovePersistedSession() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + return prefs.remove(supabasePersistSessionKey); +} - String? _defAccessToken() { - final jsonStr = - sharedPreferencesInstance.getString(supabasePersistSessionKey); - return jsonStr; - } +Future _defPersistSession(String persistSessionString) async { + final prefs = await SharedPreferences.getInstance(); + return prefs.setString(supabasePersistSessionKey, persistSessionString); +} - void _defRemovePersistedSession() { - sharedPreferencesInstance.remove(supabasePersistSessionKey); - } +class LocalStorage { + /// Creates a `LocalStorage` instance + const LocalStorage({ + this.hasAccessToken = _defHasAccessToken, + this.accessToken = _defAccessToken, + this.removePersistedSession = _defRemovePersistedSession, + this.persistSession = _defPersistSession, + }); - void _defPersistSession(String persistSessionString) { - sharedPreferencesInstance.setString( - supabasePersistSessionKey, persistSessionString); - } + final Future Function() hasAccessToken; + final Future Function() accessToken; + final Future Function() removePersistedSession; + final Future Function(String) persistSession; } class Supabase { @@ -94,8 +72,7 @@ class Supabase { _instance._init(url, anonKey); _instance._authCallbackUrlHostname = authCallbackUrlHostname; _instance._debugEnable = debug ?? false; - _instance._localStorage = (localStorage ?? _DefaultLocalStorage()) - ..init(); + _instance._localStorage = localStorage ?? LocalStorage(); _instance.log('***** Supabase init completed $_instance'); } @@ -114,7 +91,7 @@ class Supabase { bool _debugEnable = false; String? _authCallbackUrlHostname; - LocalStorage _localStorage = _DefaultLocalStorage()..init(); + LocalStorage _localStorage = LocalStorage(); /// Dispose the instance void dispose() { @@ -133,9 +110,9 @@ class Supabase { LocalStorage get localStorage => _localStorage; - bool get hasAccessToken => _localStorage.hasAccessToken(); + Future get hasAccessToken => _localStorage.hasAccessToken(); - String? get accessToken => _localStorage.accessToken(); + Future get accessToken => _localStorage.accessToken(); void _onAuthStateChange(AuthChangeEvent event, Session? session) { log('**** onAuthStateChange: $event'); From 05fdc8fa95090d107f6c07c19c3b59e13c0eb714 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka <45696119+bdlukaa@users.noreply.github.com> Date: Sun, 18 Jul 2021 20:32:19 -0300 Subject: [PATCH 5/7] Fix lint issues --- lib/src/supabase.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/supabase.dart b/lib/src/supabase.dart index 72cab732..580b8234 100644 --- a/lib/src/supabase.dart +++ b/lib/src/supabase.dart @@ -72,7 +72,7 @@ class Supabase { _instance._init(url, anonKey); _instance._authCallbackUrlHostname = authCallbackUrlHostname; _instance._debugEnable = debug ?? false; - _instance._localStorage = localStorage ?? LocalStorage(); + _instance._localStorage = localStorage ?? const LocalStorage(); _instance.log('***** Supabase init completed $_instance'); } @@ -91,7 +91,7 @@ class Supabase { bool _debugEnable = false; String? _authCallbackUrlHostname; - LocalStorage _localStorage = LocalStorage(); + LocalStorage _localStorage = const LocalStorage(); /// Dispose the instance void dispose() { From ca51df2c27f2793e967c34e56bc51b36dc4289d1 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka <45696119+bdlukaa@users.noreply.github.com> Date: Sun, 18 Jul 2021 21:18:22 -0300 Subject: [PATCH 6/7] Update tests --- test/supabase_flutter_test.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/supabase_flutter_test.dart b/test/supabase_flutter_test.dart index aa466d69..2bc197db 100644 --- a/test/supabase_flutter_test.dart +++ b/test/supabase_flutter_test.dart @@ -11,17 +11,15 @@ void main() { Supabase.initialize(url: supabaseUrl, anonKey: supabaseKey); }); - final instance = Supabase.instance; - test('can access Supabase singleton', () async { - final client = instance.client; + final client = Supabase.instance.client; expect(client, isNotNull); }); test('can parse deeplink', () async { final uri = Uri.parse( "io.supabase.flutterdemo://login-callback#access_token=aaa&expires_in=3600&refresh_token=bbb&token_type=bearer&type=recovery"); - final uriParams = instance.parseUriParameters(uri); + final uriParams = Supabase.instance.parseUriParameters(uri); expect(uriParams.length, equals(5)); expect(uriParams['access_token'], equals('aaa')); expect(uriParams['refresh_token'], equals('bbb')); @@ -30,7 +28,7 @@ void main() { test('can parse flutter web redirect link', () async { final uri = Uri.parse( "http://localhost:55510/#access_token=aaa&expires_in=3600&refresh_token=bbb&token_type=bearer&type=magiclink"); - final uriParams = instance.parseUriParameters(uri); + final uriParams = Supabase.instance.parseUriParameters(uri); expect(uriParams.length, equals(5)); expect(uriParams['access_token'], equals('aaa')); expect(uriParams['refresh_token'], equals('bbb')); @@ -39,7 +37,7 @@ void main() { test('can parse flutter web custom page redirect link', () async { final uri = Uri.parse( "http://localhost:55510/#/webAuth%23access_token=aaa&expires_in=3600&refresh_token=bbb&token_type=bearer&type=magiclink"); - final uriParams = instance.parseUriParameters(uri); + final uriParams = Supabase.instance.parseUriParameters(uri); expect(uriParams.length, equals(5)); expect(uriParams['access_token'], equals('aaa')); expect(uriParams['refresh_token'], equals('bbb')); From fc43818e513c79e0bebefb562bdd4314c4c3a663 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka <45696119+bdlukaa@users.noreply.github.com> Date: Sun, 18 Jul 2021 21:23:41 -0300 Subject: [PATCH 7/7] Update README.md --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fd50c109..480f0881 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ import 'package:supabase_flutter/supabase_flutter.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); - Supabase( + Supabase.initialize( url: SUPABASE_URL, anonKey: SUPABASE_ANNON_KEY, authCallbackUrlHostname: 'login-callback', // optional @@ -45,7 +45,7 @@ Now you can access Supabase client anywhere in your app. ```dart import 'package:supabase_flutter/supabase_flutter.dart'; -final response = await Supabase().client.auth.signIn(email: _email, password: _password); +final response = await Supabase.instance.client.auth.signIn(email: _email, password: _password); ``` #### SupabaseAuthState @@ -67,8 +67,8 @@ For more details, take a look at the example [here](https://github.com/phamhieu/ This method will automatically launch the auth url and open a browser for user to sign in with 3rd party login. ```dart -Supabase().client.auth.signInWithProvider( - supabase.Provider.github, +Supabase.instance.client.auth.signInWithProvider( + Provider.github, options: supabase.AuthOptions(redirectTo: ''), ); ``` @@ -93,7 +93,8 @@ final localStorage = LocalStorage( const storage = FlutterSecureStorage(); return storage.write(key: supabasePersistSessionKey, value: value); }); -Supabase( + +Supabase.initialize( ... localStorage: localStorage, );