Upgrade guide
Although supabase_flutter
v2 brings a few breaking changes, for the most part the public API should be the same with a few minor exceptions.
We have brought numerous updates behind the scenes to make the SDK work more intuitively for Flutter and Dart developers.
Upgrade the client library#
Make sure you are using v2 of the client library in your pubspec.yaml
file.
_10supabase_flutter: ^2.0.0
Optionally passing custom configuration to Supabase.initialize()
is now organized into separate objects:
Auth updates#
Renaming Provider to OAuthProvider#
Provider
enum is renamed to OAuthProvider
.
Previously the Provider
symbol often collided with classes in the provider package and developers needed to add import prefixes to avoid collisions.
With the new update, developers can use Supabase and Provider in the same codebase without any import prefixes.
_10await supabase.auth.signInWithOAuth(_10 Provider.google,_10);
Sign in with Apple method deprecated#
We have removed the sign_in_with_apple dependency in v2. This is because not every developer needs to sign in with Apple, and we want to reduce the number of dependencies in the library.
With v2, you can import sign_in_with_apple as a separate dependency if you need to sign in with Apple.
We have also added auth.generateRawNonce()
method to easily generate a secure nonce.
_10await supabase.auth.signInWithApple();
Initialization does not await for session refresh#
In v1, Supabase.initialize()
would await for the session to be refreshed before returning.
This caused delays in the app's launch time, especially when the app is opened in a poor network environment.
In v2, Supabase.initialize()
returns immediately after obtaining the session from the local storage, which makes the app launch faster.
Because of this, there is no guarantee that the session is valid when the app starts.
If you need to make sure the session is valid, you can access the isExpired
getter to check if the session is valid.
If the session is expired, you can listen to the onAuthStateChange
event and wait for a new tokenRefreshed
event to be fired.
_10// Session is valid, no check required_10final session = supabase.auth.currentSession;
Removing Flutter Webview dependency for OAuth sign in#
In v1, on iOS you could pass a BuildContext
to the signInWithOAuth()
method to launch the OAuth flow in a Flutter Webview.
In v2, we have dropped the webview_flutter dependency in v2 to allow you to have full control over the UI of the OAuth flow. We now have native support for Google and Apple sign in, so opening an external browser is no longer needed on iOS.
Because of this update, we no longer need the context
parameter, so we have removed the context
parameter from the signInWithOAuth()
method.
_10// Opens a webview on iOS._10await supabase.auth.signInWithOAuth(_10 Provider.github,_10 authScreenLaunchMode: LaunchMode.inAppWebView,_10 context: context,_10);
PKCE is the default auth flow type#
PKCE flow, which is a more secure method for obtaining sessions from deep links, is now the default auth flow for any authentication involving deep links.
_10await Supabase.initialize(_10 url: 'SUPABASE_URL',_10 anonKey: 'SUPABASE_ANON_KEY',_10 authFlowType: AuthFlowType.implicit, // set to implicit by default_10);
Auth callback host name parameter removed#
Supabase.initialize()
no longer has the authCallbackUrlHostname
parameter.
The supabase_flutter
SDK will automatically detect auth callback URLs and handle them internally.
_10await Supabase.initialize(_10 url: 'SUPABASE_URL',_10 anonKey: 'SUPABASE_ANON_KEY',_10 authCallbackUrlHostname: 'auth-callback',_10);
SupabaseAuth class removed#
The SupabaseAuth
had an initialSession
member, which was used to obtain the initial session upon app start.
This is now removed, and currentSession
should be used to access the session at any time.
_10// Use `initialSession` to obtain the initial session when the app starts._10final initialSession = await SupabaseAuth.initialSession;
Data methods#
Insert and return data#
We made the query builder immutable, which means you can reuse the same query object to chain multiple filters and get the expected outcome.
_10// If you declare a query and chain filters on it_10final myQuery = supabase.from('my_table').select();_10_10final foo = await myQuery.eq('some_col', 'foo');_10_10// The `eq` filter above is applied in addition to the following filter_10final bar = await myQuery.eq('another_col', 'bar');
Renaming is and in filter#
Because is
and in
are reserved keywords in Dart, v1 used is_
and in_
as query filter names.
Users found the underscore confusing, so the query filters are now renamed to isFilter
and inFilter
.
_10final data = await supabase_10 .from('users')_10 .select()_10 .is_('status', null);_10_10final data = await supabase_10 .from('users')_10 .select()_10 .in_('status', ['ONLINE', 'OFFLINE']);
Deprecate FetchOption in favor of count()
and head()
methods#
FetchOption()
on .select()
is now deprecated, and new .count()
and head()
methods are added to the query builder.
count()
on .select()
performs the select while also getting the count value, and .count()
directly on .from()
performs a head request resulting in only fetching the count value.
_22// Request with count option_22final res = await supabase.from('cities').select(_22 'name',_22 const FetchOptions(_22 count: CountOption.exact,_22 ),_22 );_22_22final data = res.data;_22final count = res.count;_22_22// Request with count and head option_22// obtains the count value without fetching the data._22final res = await supabase.from('cities').select(_22 'name',_22 const FetchOptions(_22 count: CountOption.exact,_22 head: true,_22 ),_22 );_22_22final count = res.count;
PostgREST error codes#
The PostgrestException
instance thrown by the API methods has a code
property. In v1, the code
property contained the http status code.
In v2, the code
property contains the PostgREST error code, which is more useful for debugging.
_10try {_10 await supabase.from('countries').select();_10} on PostgrestException catch (error) {_10 error.code; // Contains http status code_10}
Realtime methods#
Realtime methods contains the biggest breaking changes. Most of these changes are to make the interface more type safe.
We have removed the .on()
method and replaced it with .onPostgresChanges()
, .onBroadcast()
, and three different presence methods.
Postgres Changes#
Use the new .onPostgresChanges()
method to listen to realtime changes in the database.
In v1, filters were not strongly typed because they took a String
type. In v2, filter
takes an object. Its properties are strictly typed to catch type errors.
The payload of the callback is now typed as well. In v1
, the payload was returned as dynamic
. It is now returned as a PostgresChangePayload
object. The object contains the oldRecord
and newRecord
properties for accessing the data before and after the change.
_13supabase.channel('my_channel').on(_13 RealtimeListenTypes.postgresChanges,_13 ChannelFilter(_13 event: '*',_13 schema: 'public',_13 table: 'messages',_13 filter: 'room_id=eq.200',_13 ),_13 (dynamic payload, [ref]) {_13 final Map<String, dynamic> newRecord = payload['new'];_13 final Map<String, dynamic> oldRecord = payload['old'];_13 },_13).subscribe();
Broadcast#
Broadcast now uses the dedicated .onBroadcast()
method, rather than the generic .on()
method.
Because the method is specific to broadcast, it takes fewer properties.
_10supabase.channel('my_channel').on(_10 RealtimeListenTypes.broadcast,_10 ChannelFilter(_10 event: 'position',_10 ),_10 (dynamic payload, [ref]) {_10 print(payload);_10 },_10).subscribe();
Presence#
Realtime Presence gets three different methods for listening to three different presence events: sync
, join
, and leave
.
This allows the callback to be strictly typed.
_27final channel = supabase.channel('room1');_27_27channel.on(_27 RealtimeListenTypes.presence,_27 ChannelFilter(event: 'sync'),_27 (payload, [ref]) {_27 print('Synced presence state: ${channel.presenceState()}');_27 },_27).on(_27 RealtimeListenTypes.presence,_27 ChannelFilter(event: 'join'),_27 (payload, [ref]) {_27 print('Newly joined presences $payload');_27 },_27).on(_27 RealtimeListenTypes.presence,_27 ChannelFilter(event: 'leave'),_27 (payload, [ref]) {_27 print('Newly left presences: $payload');_27 },_27).subscribe(_27 (status, [error]) async {_27 if (status == 'SUBSCRIBED') {_27 await channel.track({'online_at': DateTime.now().toIso8601String()});_27 }_27 },_27);