Fix errors being swallowed in account_page.dart and login_page.dart fixes #27104 (#27143)

* Refactor login_page with a _showSnackBar function

* Add an onError block to onAuthStateChange

Errors get swallowed without this error handler, I noticed it when I had requested too many magic link emails, there was no feedback in the ui, nor in the logs in the console.

* Fix SnackBars not being displayed and add a _showSnackBar function

* Fix code samples for login_page.dart and account_page.dart to reflect fixes in #27104

* Fix isError not being passed for an Unexpected Error in login_page

* Update flutter user management example to a more modern code

* Update the guide to match the new content

* run formatter

* got rid of excess code and minor format fix

---------

Co-authored-by: dshukertjr <dshukertjr@gmail.com>
This commit is contained in:
Tyler Pfaffanddshukertjr authored and GitHub committed 2024-06-14 09:19:10 +00:00
1 parent fa25402482
commit 33c7e812c4
10 files changed
+226 -388

No files matched your search

@@ -125,9 +125,7 @@ There are no additional configurations.
### Main function
Now that we have deep links ready let's initialize the Supabase client inside our `main` function with the API credentials that you copied [earlier](#get-the-api-keys).
These variables will be exposed on the app, and that's completely fine since we have
[Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
Now that we have deep links ready let's initialize the Supabase client inside our `main` function with the API credentials that you copied [earlier](#get-the-api-keys). These variables will be exposed on the app, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```dart lib/main.dart
import 'package:flutter/material.dart';
@@ -151,60 +149,28 @@ class MyApp extends StatelessWidget {
return const MaterialApp(title: 'Supabase Flutter');
}
}
```
### Set up splash screen
Let's create a splash screen that will be shown to users right after they open the app.
This screen retrieves the current session and redirects the user accordingly.
```dart lib/pages/splash_page.dart
import 'package:flutter/material.dart';
import 'package:supabase_quickstart/main.dart';
class SplashPage extends StatefulWidget {
const SplashPage({super.key});
@override
State<SplashPage> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
@override
void initState() {
super.initState();
_redirect();
}
Future<void> _redirect() async {
await Future.delayed(Duration.zero);
if (!mounted) {
return;
}
final session = supabase.auth.currentSession;
if (session != null) {
Navigator.of(context).pushReplacementNamed('/account');
} else {
Navigator.of(context).pushReplacementNamed('/login');
}
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
extension ContextExtension on BuildContext {
void showSnackBar(String message, {bool isError = false}) {
ScaffoldMessenger.of(this).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: isError
? Theme.of(this).colorScheme.error
: Theme.of(this).snackBarTheme.backgroundColor,
),
);
}
}
```
Notice that we have a `showSnackBar` extension method that we will use to show snack bars in the app. You could define this method in a separate file and import it where needed, but for simplicity, we will define it here.
### Set up a login page
Let's create a Flutter widget to manage logins and sign ups.
We'll use Magic Links, so users can sign in with their email without using passwords.
Notice that this page sets up a listener on the user's auth state using `onAuthStateChange`.
A new event will fire when the user comes back to the app by clicking their magic link, which this page can catch and redirect the user accordingly.
Let's create a Flutter widget to manage logins and sign ups. We will use Magic Links, so users can sign in with their email without using passwords.
Notice that this page sets up a listener on the user's auth state using `onAuthStateChange`. A new event will fire when the user comes back to the app by clicking their magic link, which this page can catch and redirect the user accordingly.
```dart lib/pages/login_page.dart
import 'dart:async';
@@ -213,6 +179,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/main.dart';
import 'package:supabase_quickstart/pages/account_page.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@@ -238,24 +205,15 @@ class _LoginPageState extends State<LoginPage> {
kIsWeb ? null : 'io.supabase.flutterquickstart://login-callback/',
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Check your email for a login link!')),
);
context.showSnackBar('Check your email for a login link!');
_emailController.clear();
}
} on AuthException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (mounted) context.showSnackBar(error.message, isError: true);
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
} finally {
if (mounted) {
@@ -268,14 +226,25 @@ class _LoginPageState extends State<LoginPage> {
@override
void initState() {
_authStateSubscription = supabase.auth.onAuthStateChange.listen((data) {
if (_redirecting) return;
final session = data.session;
if (session != null) {
_redirecting = true;
Navigator.of(context).pushReplacementNamed('/account');
}
});
_authStateSubscription = supabase.auth.onAuthStateChange.listen(
(data) {
if (_redirecting) return;
final session = data.session;
if (session != null) {
_redirecting = true;
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const AccountPage()),
);
}
},
onError: (error) {
if (error is AuthException) {
context.showSnackBar(error.message, isError: true);
} else {
context.showSnackBar('Unexpected error occurred', isError: true);
}
},
);
super.initState();
}
@@ -302,7 +271,7 @@ class _LoginPageState extends State<LoginPage> {
const SizedBox(height: 18),
ElevatedButton(
onPressed: _isLoading ? null : _signIn,
child: Text(_isLoading ? 'Loading' : 'Send Magic Link'),
child: Text(_isLoading ? 'Sending...' : 'Send Magic Link'),
),
],
),
@@ -320,6 +289,7 @@ Let's create a new widget called `account_page.dart` for that.
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/main.dart';
import 'package:supabase_quickstart/pages/login_page.dart';
class AccountPage extends StatefulWidget {
const AccountPage({super.key});
@@ -332,6 +302,7 @@ class _AccountPageState extends State<AccountPage> {
final _usernameController = TextEditingController();
final _websiteController = TextEditingController();
String? _avatarUrl;
var _loading = true;
/// Called once a user id is received within `onAuthenticated()`
@@ -341,24 +312,17 @@ class _AccountPageState extends State<AccountPage> {
});
try {
final userId = supabase.auth.currentUser!.id;
final userId = supabase.auth.currentSession!.user.id;
final data =
await supabase.from('profiles').select().eq('id', userId).single();
_usernameController.text = (data['username'] ?? '') as String;
_websiteController.text = (data['website'] ?? '') as String;
_avatarUrl = (data['avatar_url'] ?? '') as String;
} on PostgrestException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (mounted) context.showSnackBar(error.message, isError: true);
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
} finally {
if (mounted) {
@@ -385,24 +349,12 @@ class _AccountPageState extends State<AccountPage> {
};
try {
await supabase.from('profiles').upsert(updates);
if (mounted) {
const SnackBar(
content: Text('Successfully updated profile!'),
);
}
if (mounted) context.showSnackBar('Successfully updated profile!');
} on PostgrestException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (mounted) context.showSnackBar(error.message, isError: true);
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
} finally {
if (mounted) {
@@ -417,22 +369,16 @@ class _AccountPageState extends State<AccountPage> {
try {
await supabase.auth.signOut();
} on AuthException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (mounted) context.showSnackBar(error.message, isError: true);
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
} finally {
if (mounted) {
Navigator.of(context).pushReplacementNamed('/login');
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const LoginPage()),
);
}
}
}
@@ -454,29 +400,27 @@ class _AccountPageState extends State<AccountPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: _loading
? const Center(child: CircularProgressIndicator())
: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(labelText: 'User Name'),
),
const SizedBox(height: 18),
TextFormField(
controller: _websiteController,
decoration: const InputDecoration(labelText: 'Website'),
),
const SizedBox(height: 18),
ElevatedButton(
onPressed: _loading ? null : _updateProfile,
child: Text(_loading ? 'Saving...' : 'Update'),
),
const SizedBox(height: 18),
TextButton(onPressed: _signOut, child: const Text('Sign Out')),
],
),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(labelText: 'User Name'),
),
const SizedBox(height: 18),
TextFormField(
controller: _websiteController,
decoration: const InputDecoration(labelText: 'Website'),
),
const SizedBox(height: 18),
ElevatedButton(
onPressed: _loading ? null : _updateProfile,
child: Text(_loading ? 'Saving...' : 'Update'),
),
const SizedBox(height: 18),
TextButton(onPressed: _signOut, child: const Text('Sign Out')),
],
),
);
}
}
@@ -484,14 +428,15 @@ class _AccountPageState extends State<AccountPage> {
### Launch!
Now that we have all the components in place, let's update `lib/main.dart`:
Now that we have all the components in place, let's update `lib/main.dart`.
The `home` of the `MaterialApp`, meaning the initial page shown to the user, will be the `LoginPage` if the user is not authenticated, and the `AccountPage` if the user is authenticated.
We also included some theming to make the app look a bit nicer.
```dart lib/main.dart
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/pages/account_page.dart';
import 'package:supabase_quickstart/pages/login_page.dart';
import 'package:supabase_quickstart/pages/splash_page.dart';
Future<void> main() async {
await Supabase.initialize(
@@ -524,12 +469,22 @@ class MyApp extends StatelessWidget {
),
),
),
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (_) => const SplashPage(),
'/login': (_) => const LoginPage(),
'/account': (_) => const AccountPage(),
},
home: supabase.auth.currentSession == null
? const LoginPage()
: const AccountPage(),
);
}
}
extension ContextExtension on BuildContext {
void showSnackBar(String message, {bool isError = false}) {
ScaffoldMessenger.of(this).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: isError
? Theme.of(this).colorScheme.error
: Theme.of(this).snackBarTheme.backgroundColor,
),
);
}
}
@@ -661,21 +616,11 @@ class _AvatarState extends State<Avatar> {
widget.onUpload(imageUrlResponse);
} on StorageException catch (error) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
context.showSnackBar(error.message, isError: true);
}
} catch (error) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
}
@@ -691,8 +636,8 @@ And then we can add the widget to the Account page as well as some logic to upda
```dart lib/pages/account_page.dart
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/components/avatar.dart';
import 'package:supabase_quickstart/main.dart';
import 'package:supabase_quickstart/pages/login_page.dart';
class AccountPage extends StatefulWidget {
const AccountPage({super.key});
@@ -716,27 +661,16 @@ class _AccountPageState extends State<AccountPage> {
try {
final userId = supabase.auth.currentSession!.user.id;
final data = await supabase
.from('profiles')
.select()
.eq('id', userId)
.single();
final data =
await supabase.from('profiles').select().eq('id', userId).single();
_usernameController.text = (data['username'] ?? '') as String;
_websiteController.text = (data['website'] ?? '') as String;
_avatarUrl = (data['avatar_url'] ?? '') as String;
} on PostgrestException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (mounted) context.showSnackBar(error.message, isError: true);
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
} finally {
if (mounted) {
@@ -763,24 +697,12 @@ class _AccountPageState extends State<AccountPage> {
};
try {
await supabase.from('profiles').upsert(updates);
if (mounted) {
const SnackBar(
content: Text('Successfully updated profile!'),
);
}
if (mounted) context.showSnackBar('Successfully updated profile!');
} on PostgrestException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (mounted) context.showSnackBar(error.message, isError: true);
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
} finally {
if (mounted) {
@@ -795,61 +717,18 @@ class _AccountPageState extends State<AccountPage> {
try {
await supabase.auth.signOut();
} on AuthException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (mounted) context.showSnackBar(error.message, isError: true);
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
} finally {
if (mounted) {
Navigator.of(context).pushReplacementNamed('/login');
}
}
}
/// Called when image has been uploaded to Supabase storage from within Avatar widget
Future<void> _onUpload(String imageUrl) async {
try {
final userId = supabase.auth.currentUser!.id;
await supabase.from('profiles').upsert({
'id': userId,
'avatar_url': imageUrl,
});
if (mounted) {
const SnackBar(
content: Text('Updated your profile image!'),
);
}
} on PostgrestException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const LoginPage()),
);
}
}
if (!mounted) {
return;
}
setState(() {
_avatarUrl = imageUrl;
});
}
@override
@@ -869,34 +748,27 @@ class _AccountPageState extends State<AccountPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: _loading
? const Center(child: CircularProgressIndicator())
: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
Avatar(
imageUrl: _avatarUrl,
onUpload: _onUpload,
),
const SizedBox(height: 18),
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(labelText: 'User Name'),
),
const SizedBox(height: 18),
TextFormField(
controller: _websiteController,
decoration: const InputDecoration(labelText: 'Website'),
),
const SizedBox(height: 18),
ElevatedButton(
onPressed: _loading ? null : _updateProfile,
child: Text(_loading ? 'Saving...' : 'Update'),
),
const SizedBox(height: 18),
TextButton(onPressed: _signOut, child: const Text('Sign Out')),
],
),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(labelText: 'User Name'),
),
const SizedBox(height: 18),
TextFormField(
controller: _websiteController,
decoration: const InputDecoration(labelText: 'Website'),
),
const SizedBox(height: 18),
ElevatedButton(
onPressed: _loading ? null : _updateProfile,
child: Text(_loading ? 'Saving...' : 'Update'),
),
const SizedBox(height: 18),
TextButton(onPressed: _signOut, child: const Text('Sign Out')),
],
),
);
}
}
@@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
<string>12.0</string>
</dict>
</plist>
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '11.0'
platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@@ -36,13 +36,13 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS:
app_links: 5ef33d0d295a89d9d16bb81b0e3b0d5f70d6c875
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
app_links: e70ca16b4b0f88253b3b3660200d4a10b4ea9795
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
PODFILE CHECKSUM: 7368163408c647b7eb699d0d788ba6718e18fb8d
PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048
COCOAPODS: 1.11.3
COCOAPODS: 1.14.3
@@ -156,7 +156,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@@ -343,7 +343,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -417,7 +417,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -466,7 +466,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -76,21 +76,11 @@ class _AvatarState extends State<Avatar> {
widget.onUpload(imageUrlResponse);
} on StorageException catch (error) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
context.showSnackBar(error.message, isError: true);
}
} catch (error) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
}
@@ -3,7 +3,6 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/pages/account_page.dart';
import 'package:supabase_quickstart/pages/login_page.dart';
import 'package:supabase_quickstart/pages/splash_page.dart';
Future<void> main() async {
await dotenv.load();
@@ -38,13 +37,22 @@ class MyApp extends StatelessWidget {
),
),
),
initialRoute: '/',
routes: <String, WidgetBuilder>{
// Splash page is needed to ensure that authentication and page loading works correctly
'/': (_) => const SplashPage(),
'/login': (_) => const LoginPage(),
'/account': (_) => const AccountPage(),
},
home: supabase.auth.currentSession == null
? const LoginPage()
: const AccountPage(),
);
}
}
extension ContextExtension on BuildContext {
void showSnackBar(String message, {bool isError = false}) {
ScaffoldMessenger.of(this).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: isError
? Theme.of(this).colorScheme.error
: Theme.of(this).snackBarTheme.backgroundColor,
),
);
}
}
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/components/avatar.dart';
import 'package:supabase_quickstart/main.dart';
import 'package:supabase_quickstart/pages/login_page.dart';
class AccountPage extends StatefulWidget {
const AccountPage({super.key});
@@ -31,18 +32,10 @@ class _AccountPageState extends State<AccountPage> {
_websiteController.text = (data['website'] ?? '') as String;
_avatarUrl = (data['avatar_url'] ?? '') as String;
} on PostgrestException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (mounted) context.showSnackBar(error.message, isError: true);
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
} finally {
if (mounted) {
@@ -69,24 +62,12 @@ class _AccountPageState extends State<AccountPage> {
};
try {
await supabase.from('profiles').upsert(updates);
if (mounted) {
const SnackBar(
content: Text('Successfully updated profile!'),
);
}
if (mounted) context.showSnackBar('Successfully updated profile!');
} on PostgrestException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (mounted) context.showSnackBar(error.message, isError: true);
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
} finally {
if (mounted) {
@@ -101,22 +82,16 @@ class _AccountPageState extends State<AccountPage> {
try {
await supabase.auth.signOut();
} on AuthException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (mounted) context.showSnackBar(error.message, isError: true);
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
} finally {
if (mounted) {
Navigator.of(context).pushReplacementNamed('/login');
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const LoginPage()),
);
}
}
}
@@ -135,18 +110,10 @@ class _AccountPageState extends State<AccountPage> {
);
}
} on PostgrestException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (mounted) context.showSnackBar(error.message, isError: true);
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
}
if (!mounted) {
@@ -175,34 +142,32 @@ class _AccountPageState extends State<AccountPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: _loading
? const Center(child: CircularProgressIndicator())
: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
Avatar(
imageUrl: _avatarUrl,
onUpload: _onUpload,
),
const SizedBox(height: 18),
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(labelText: 'User Name'),
),
const SizedBox(height: 18),
TextFormField(
controller: _websiteController,
decoration: const InputDecoration(labelText: 'Website'),
),
const SizedBox(height: 18),
ElevatedButton(
onPressed: _loading ? null : _updateProfile,
child: Text(_loading ? 'Saving...' : 'Update'),
),
const SizedBox(height: 18),
TextButton(onPressed: _signOut, child: const Text('Sign Out')),
],
),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
Avatar(
imageUrl: _avatarUrl,
onUpload: _onUpload,
),
const SizedBox(height: 18),
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(labelText: 'User Name'),
),
const SizedBox(height: 18),
TextFormField(
controller: _websiteController,
decoration: const InputDecoration(labelText: 'Website'),
),
const SizedBox(height: 18),
ElevatedButton(
onPressed: _loading ? null : _updateProfile,
child: Text(_loading ? 'Saving...' : 'Update'),
),
const SizedBox(height: 18),
TextButton(onPressed: _signOut, child: const Text('Sign Out')),
],
),
);
}
}
@@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/main.dart';
import 'package:supabase_quickstart/pages/account_page.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@@ -29,24 +30,15 @@ class _LoginPageState extends State<LoginPage> {
kIsWeb ? null : 'io.supabase.flutterquickstart://login-callback/',
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Check your email for a login link!')),
);
context.showSnackBar('Check your email for a login link!');
_emailController.clear();
}
} on AuthException catch (error) {
if (mounted) {
SnackBar(
content: Text(error.message),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (mounted) context.showSnackBar(error.message, isError: true);
} catch (error) {
if (mounted) {
SnackBar(
content: const Text('Unexpected error occurred'),
backgroundColor: Theme.of(context).colorScheme.error,
);
context.showSnackBar('Unexpected error occurred', isError: true);
}
} finally {
if (mounted) {
@@ -59,14 +51,25 @@ class _LoginPageState extends State<LoginPage> {
@override
void initState() {
_authStateSubscription = supabase.auth.onAuthStateChange.listen((data) {
if (_redirecting) return;
final session = data.session;
if (session != null) {
_redirecting = true;
Navigator.of(context).pushReplacementNamed('/account');
}
});
_authStateSubscription = supabase.auth.onAuthStateChange.listen(
(data) {
if (_redirecting) return;
final session = data.session;
if (session != null) {
_redirecting = true;
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const AccountPage()),
);
}
},
onError: (error) {
if (error is AuthException) {
context.showSnackBar(error.message, isError: true);
} else {
context.showSnackBar('Unexpected error occurred', isError: true);
}
},
);
super.initState();
}
@@ -93,7 +96,7 @@ class _LoginPageState extends State<LoginPage> {
const SizedBox(height: 18),
ElevatedButton(
onPressed: _isLoading ? null : _signIn,
child: Text(_isLoading ? 'Loading' : 'Send Magic Link'),
child: Text(_isLoading ? 'Sending...' : 'Send Magic Link'),
),
],
),