From 1cfb855a0e0a3bf764ad59de7e766088616d5570 Mon Sep 17 00:00:00 2001 From: Hans Date: Fri, 3 Jul 2026 06:18:16 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20improve=20error=20handling=20=E2=80=94?= =?UTF-8?q?=20stop=20silently=20swallowing=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add debug logging to all catch blocks that previously used catch(_){} or catch(_) with no logging, across 14 files - vocpass_auth_service: handleTokenLogin now rethrows on failure so callers can react; restoreSession returns bool success status - app.dart: wrap deep-link token login in try/catch since it now throws - w2m_result_screen: filter out unparseable dates instead of silently substituting DateTime.now() - restaurant_screen: surface menu load errors in the UI (_menuError) - following_screen: log profile fetch failures instead of empty catch - forum_verification_sheet: log refreshMe() failures - forum_service/forum_screen: log fetchAdminInfo and fetchTags failures - api_service: log curriculum fetch failure in fetchAttendanceWithCurriculum - All screen-level catch blocks (score, exam_score, curriculum, attendance, home) now log the original error for debugging while still showing user-friendly messages Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- lib/app.dart | 8 ++++++-- lib/screens/attendance_screen.dart | 4 +++- lib/screens/curriculum_screen.dart | 4 +++- lib/screens/exam_score_screen.dart | 7 +++++-- lib/screens/following_screen.dart | 5 ++++- lib/screens/forum/forum_screen.dart | 6 +++++- lib/screens/forum/forum_verification_sheet.dart | 5 ++++- lib/screens/home_screen.dart | 2 ++ lib/screens/restaurant_screen.dart | 13 +++++++++++-- lib/screens/score_screen.dart | 4 +++- lib/screens/w2m/w2m_result_screen.dart | 8 +++++--- lib/services/api_service.dart | 3 ++- lib/services/forum_service.dart | 4 +++- lib/services/vocpass_auth_service.dart | 10 +++++++--- 14 files changed, 63 insertions(+), 20 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index dcc3524..7f2f193 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -79,8 +79,12 @@ class _RootRouterState extends State { } Future _handleTokenLoginAndSyncNotify(String token) async { - await VocPassAuthService.instance.handleTokenLogin(token); - await NotificationTokenService.instance.uploadNow(); + try { + await VocPassAuthService.instance.handleTokenLogin(token); + await NotificationTokenService.instance.uploadNow(); + } catch (e) { + if (kDebugMode) print('[DeepLink] token login failed: $e'); + } } @override diff --git a/lib/screens/attendance_screen.dart b/lib/screens/attendance_screen.dart index 2ba56e1..a3d42eb 100644 --- a/lib/screens/attendance_screen.dart +++ b/lib/screens/attendance_screen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -139,7 +140,8 @@ class _AttendanceScreenState extends State { } else { setState(() { _error = e.message; _isLoading = false; }); } - } catch (_) { + } catch (e) { + if (kDebugMode) print('[Attendance] 缺曠資料載入失敗: $e'); setState(() { _error = '缺曠資料載入失敗'; _isLoading = false; }); } } diff --git a/lib/screens/curriculum_screen.dart b/lib/screens/curriculum_screen.dart index 0f06ebc..e4804f0 100644 --- a/lib/screens/curriculum_screen.dart +++ b/lib/screens/curriculum_screen.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -126,7 +127,8 @@ class _CurriculumScreenState extends State { _isLoading = false; }); } - } catch (_) { + } catch (e) { + if (kDebugMode) print('[Curriculum] 課表載入失敗: $e'); if (!mounted) return; setState(() { _error = '課表載入失敗'; diff --git a/lib/screens/exam_score_screen.dart b/lib/screens/exam_score_screen.dart index 1cdbe46..6159dce 100644 --- a/lib/screens/exam_score_screen.dart +++ b/lib/screens/exam_score_screen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -49,7 +50,8 @@ class _ExamScoreScreenState extends State { _isLoading = false; }); } - } catch (_) { + } catch (e) { + if (kDebugMode) print('[ExamScore] 考試選單載入失敗: $e'); setState(() { _error = '考試成績載入失敗'; _isLoading = false; @@ -148,7 +150,8 @@ class _ExamScoreDetailScreenState extends State { _error = e.message; _isLoading = false; }); - } catch (_) { + } catch (e) { + if (kDebugMode) print('[ExamScore] 考試成績載入失敗: $e'); setState(() { _error = '考試成績載入失敗'; _isLoading = false; diff --git a/lib/screens/following_screen.dart b/lib/screens/following_screen.dart index 0194707..2d338d4 100644 --- a/lib/screens/following_screen.dart +++ b/lib/screens/following_screen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -34,7 +35,9 @@ class _FollowingListScreenState extends State { final profile = await VocPassAuthService.instance.fetchUser(username); if (!mounted) return; setState(() => _profiles[username] = profile); - } catch (_) {} + } catch (e) { + if (kDebugMode) print('[FollowingList] 取得用戶 $username 資料失敗: $e'); + } } void _showAddDialog() { diff --git a/lib/screens/forum/forum_screen.dart b/lib/screens/forum/forum_screen.dart index e8bb5a5..e7a3644 100644 --- a/lib/screens/forum/forum_screen.dart +++ b/lib/screens/forum/forum_screen.dart @@ -2,6 +2,7 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -114,7 +115,10 @@ class _ForumScreenState extends State { else Future.value(null), ForumService.instance.fetchVocPassAdminInfo(), - ForumService.instance.fetchTags().catchError((_) => []), + ForumService.instance.fetchTags().catchError((e) { + if (kDebugMode) print('[Forum] fetchTags 失敗: $e'); + return []; + }), ]); if (!mounted) return; setState(() { diff --git a/lib/screens/forum/forum_verification_sheet.dart b/lib/screens/forum/forum_verification_sheet.dart index 84b398b..f842dba 100644 --- a/lib/screens/forum/forum_verification_sheet.dart +++ b/lib/screens/forum/forum_verification_sheet.dart @@ -1,5 +1,6 @@ // 論壇學校驗證狀態說明 +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -14,7 +15,9 @@ Future showForumVerificationSheet( if (VocPassAuthService.instance.isLoggedIn) { try { await VocPassAuthService.instance.refreshMe(); - } catch (_) {} + } catch (e) { + if (kDebugMode) print('[ForumVerification] 刷新用戶資料失敗: $e'); + } } if (!context.mounted) return; await showModalBottomSheet( diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 1021375..267f3b4 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -52,6 +53,7 @@ class _HomeScreenState extends State { }); } } catch (e) { + if (kDebugMode) print('[Home] 獎懲資料載入失敗: $e'); setState(() { _error = '資料載入失敗'; _isLoading = false; diff --git a/lib/screens/restaurant_screen.dart b/lib/screens/restaurant_screen.dart index b326a0a..e806798 100644 --- a/lib/screens/restaurant_screen.dart +++ b/lib/screens/restaurant_screen.dart @@ -1,5 +1,7 @@ import 'dart:math'; +import 'package:flutter/foundation.dart'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -351,6 +353,7 @@ class _RestaurantDetailScreenState extends State { bool _isLoading = false; bool _isLoadingMenu = false; String? _evalError; + String? _menuError; double? get _averageScore { if (_evaluations.isEmpty) return null; @@ -385,9 +388,10 @@ class _RestaurantDetailScreenState extends State { final list = await api.fetchRestaurantMenu(widget.restaurant.id); if (!mounted) return; setState(() { _menuItems = list; _isLoadingMenu = false; }); - } catch (_) { + } catch (e) { + if (kDebugMode) print('[Restaurant] 菜單載入失敗: $e'); if (!mounted) return; - setState(() => _isLoadingMenu = false); + setState(() { _menuError = e.toString(); _isLoadingMenu = false; }); } } @@ -565,6 +569,11 @@ class _RestaurantDetailScreenState extends State { padding: EdgeInsets.all(16), child: CircularProgressIndicator(), )) + else if (_menuError != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Text('菜單載入失敗', style: TextStyle(color: Colors.red[400])), + ) else if (_menuItems.isEmpty) const Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), diff --git a/lib/screens/score_screen.dart b/lib/screens/score_screen.dart index 2c0e504..de5a469 100644 --- a/lib/screens/score_screen.dart +++ b/lib/screens/score_screen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -51,7 +52,8 @@ class _ScoreScreenState extends State { _isLoading = false; }); } - } catch (_) { + } catch (e) { + if (kDebugMode) print('[Score] 成績資料載入失敗: $e'); setState(() { _error = '成績資料載入失敗'; _isLoading = false; diff --git a/lib/screens/w2m/w2m_result_screen.dart b/lib/screens/w2m/w2m_result_screen.dart index db1eb1a..9950d33 100644 --- a/lib/screens/w2m/w2m_result_screen.dart +++ b/lib/screens/w2m/w2m_result_screen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; @@ -667,10 +668,11 @@ class _W2MEditSheetState extends State<_W2MEditSheet> { _selectedDates = widget.event.dates.map((s) { try { return DateTime.parse(s); - } catch (_) { - return DateTime.now(); + } catch (e) { + if (kDebugMode) print('[W2M] 無法解析日期 "$s": $e'); + return null; } - }).toSet(); + }).whereType().toSet(); } @override diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 9102ca6..8ed5d2e 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -514,7 +514,8 @@ class ApiService extends ChangeNotifier { Map? curriculum; try { curriculum = await curriculumFuture; - } catch (_) { + } catch (e) { + if (kDebugMode) print('[ApiService] 課表載入失敗(缺曠頁面仍可運作): $e'); curriculum = null; } diff --git a/lib/services/forum_service.dart b/lib/services/forum_service.dart index 092320c..4d028ee 100644 --- a/lib/services/forum_service.dart +++ b/lib/services/forum_service.dart @@ -131,7 +131,9 @@ class ForumService { if (data is Map) { return ForumAdminInfo.fromJson(data.cast()); } - } catch (_) {} + } catch (e) { + if (kDebugMode) print('[Forum] fetchAdminInfo 失敗: $e'); + } return null; } diff --git a/lib/services/vocpass_auth_service.dart b/lib/services/vocpass_auth_service.dart index ba4c618..50c1e16 100644 --- a/lib/services/vocpass_auth_service.dart +++ b/lib/services/vocpass_auth_service.dart @@ -56,12 +56,14 @@ class VocPassAuthService extends ChangeNotifier { } catch (e) { await _saveToken(null); if (kDebugMode) print('[VocPassAuth] 取得使用者資料失敗: $e'); + rethrow; } } - // Restore session on app start - Future restoreSession() async { - if (_authToken == null || _authToken!.isEmpty) return; + // Restore session on app start. + // Returns true if session was restored, false if token was invalid/expired. + Future restoreSession() async { + if (_authToken == null || _authToken!.isEmpty) return false; try { final user = await fetchMe(); currentUser = user; @@ -71,9 +73,11 @@ class VocPassAuthService extends ChangeNotifier { } notifyListeners(); if (kDebugMode) print('[VocPassAuth] 已恢復 session: ${user.displayName}'); + return true; } catch (e) { await _saveToken(null); if (kDebugMode) print('[VocPassAuth] Session 已失效: $e'); + return false; } }