fix(stability): remove auth/context force unwraps and add guarded null-safe paths

This commit is contained in:
Lukas Müllner 2026-03-05 14:39:00 +01:00
parent f41708c662
commit 947992e8b9
5 changed files with 60 additions and 15 deletions

View file

@ -68,8 +68,12 @@ void showGlobalSnackBar(String message, {bool isError = false}) {
/// Show a dialog safely through the global navigator key.
Future<T?> showGlobalDialog<T>({required WidgetBuilder builder}) {
final context = navigatorKey.currentContext;
if (context == null) {
return Future<T?>.value(null);
}
return showDialog<T>(
context: navigatorKey.currentContext!,
context: context,
builder: builder,
);
}
@ -201,8 +205,14 @@ class _AuthGateState extends State<AuthGate> {
}
Future<void> _showResetPasswordDialog() async {
final context = navigatorKey.currentContext;
if (context == null) {
_isInPasswordRecoveryFlow = false;
return;
}
await showDialog<void>(
context: navigatorKey.currentContext!,
context: context,
barrierDismissible: false,
builder: (_) => const _ResetPasswordDialog(),
);

View file

@ -483,6 +483,11 @@ class GarageScreenState extends State<GarageScreen> {
.update({'collection_id': targetId})
.inFilter('id', _selectedIds.toList());
} else {
final userId = supabase.auth.currentUser?.id;
if (userId == null) {
throw Exception('You must be signed in to copy cars.');
}
final sourceCars = _cars
.where((car) => _selectedIds.contains(car['id'] as int))
.toList(growable: false);
@ -492,7 +497,7 @@ class GarageScreenState extends State<GarageScreen> {
final imagePath = car['user_image_url'] as String?;
return <String, dynamic>{
'hw_id': car['hw_id'] as String,
'user_id': supabase.auth.currentUser!.id,
'user_id': userId,
'collection_id': targetId,
if (notes != null && notes.trim().isNotEmpty) 'notes': notes,
if (imagePath != null && imagePath.isNotEmpty)
@ -1028,11 +1033,16 @@ class GarageScreenState extends State<GarageScreen> {
.update({'collection_id': targetId})
.eq('id', car['id']);
} else {
final userId = supabase.auth.currentUser?.id;
if (userId == null) {
throw Exception('You must be signed in to copy cars.');
}
final notes = car['notes'] as String?;
final imagePath = car['user_image_url'] as String?;
await supabase.from('hotwheels').insert({
'hw_id': car['hw_id'] as String,
'user_id': supabase.auth.currentUser!.id,
'user_id': userId,
'collection_id': targetId,
if (notes != null && notes.trim().isNotEmpty) 'notes': notes,
if (imagePath != null && imagePath.isNotEmpty)

View file

@ -419,9 +419,14 @@ class ScanTabState extends State<ScanTab> {
String hwId, {
String? notes,
}) async {
final userId = supabase.auth.currentUser?.id;
if (userId == null) {
throw Exception('You must be signed in to add cars.');
}
await supabase.from('hotwheels').insert({
'hw_id': hwId,
'user_id': supabase.auth.currentUser!.id,
'user_id': userId,
'collection_id': collectionId,
if (notes != null && notes.trim().isNotEmpty) 'notes': notes.trim(),
});
@ -433,6 +438,11 @@ class ScanTabState extends State<ScanTab> {
String? series,
int? year,
}) async {
final userId = supabase.auth.currentUser?.id;
if (userId == null) {
throw Exception('You must be signed in to create catalog entries.');
}
final cleanedSeries = series?.trim();
final payload = <String, dynamic>{
'hw_id': hwId,
@ -445,12 +455,15 @@ class ScanTabState extends State<ScanTab> {
await supabase.from('car_votes').insert({
'hw_id': hwId,
'user_id': supabase.auth.currentUser!.id,
'user_id': userId,
});
}
Future<void> _ensureValidationVote(String hwId) async {
final userId = supabase.auth.currentUser!.id;
final userId = supabase.auth.currentUser?.id;
if (userId == null) {
throw Exception('You must be signed in to validate entries.');
}
final existingVote = await supabase
.from('car_votes')
.select('id')

View file

@ -53,6 +53,14 @@ class CollectionMember {
class CollectionService {
CollectionService._();
static String _requireUserId() {
final userId = supabase.auth.currentUser?.id;
if (userId == null) {
throw Exception('You must be signed in to perform this action.');
}
return userId;
}
/// Ensures the current user has at least one collection membership.
/// Creates a default collection on first login.
static Future<String?> ensureDefaultCollection() async {
@ -80,7 +88,7 @@ class CollectionService {
/// Fetch all collections the current user is a member of,
/// including item count and member count.
static Future<List<Collection>> getMyCollections() async {
final userId = supabase.auth.currentUser!.id;
final userId = _requireUserId();
// Get memberships with collection data.
final memberships = await supabase
@ -249,7 +257,7 @@ class CollectionService {
required String name,
String? description,
}) async {
final userId = supabase.auth.currentUser!.id;
final userId = _requireUserId();
final row = await supabase
.from('collections')
@ -287,7 +295,7 @@ class CollectionService {
required String name,
String? description,
}) async {
final userId = supabase.auth.currentUser!.id;
final userId = _requireUserId();
final collection = await supabase
.from('collections')
@ -311,7 +319,7 @@ class CollectionService {
/// Delete a collection. Owner only. Cascade deletes members & items.
static Future<void> delete(String collectionId) async {
final userId = supabase.auth.currentUser!.id;
final userId = _requireUserId();
final collection = await supabase
.from('collections')
@ -362,7 +370,7 @@ class CollectionService {
throw Exception('Unsupported role "$role".');
}
final currentUserId = supabase.auth.currentUser!.id;
final currentUserId = _requireUserId();
final collection = await supabase
.from('collections')
@ -428,7 +436,7 @@ class CollectionService {
required String collectionId,
required String memberUserId,
}) async {
final currentUserId = supabase.auth.currentUser!.id;
final currentUserId = _requireUserId();
final collection = await supabase
.from('collections')
@ -501,7 +509,7 @@ class CollectionService {
/// Leave a collection (for non-owners).
static Future<void> leave(String collectionId) async {
final userId = supabase.auth.currentUser!.id;
final userId = _requireUserId();
final membership = await supabase
.from('collection_members')

View file

@ -31,7 +31,11 @@ class StorageService {
await deleteCarImage(oldPath);
}
final userId = supabase.auth.currentUser!.id;
final user = supabase.auth.currentUser;
if (user == null) {
throw Exception('You must be signed in to upload images.');
}
final userId = user.id;
final path = '$userId/$entryId.jpg';
final compressed = await _compressImage(file);