fix: leaderboard empty on first open — decouple from loadAll()
- Add isLeaderboardLoading flag separate from isLoading - Add loadLeaderboard() method that fires independently of loadAll TTL - Remove leaderboard from loadAll() Future.wait (failures in dashboard/shop no longer silently zero-out leaderboard data) - setDistrict / setTimePeriod now use isLeaderboardLoading - contribute_screen calls loadLeaderboard() alongside loadAll() on mount Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ class GamificationProvider extends ChangeNotifier {
|
||||
String leaderboardTimePeriod = 'all_time'; // 'all_time' | 'this_month'
|
||||
|
||||
bool isLoading = false;
|
||||
bool isLeaderboardLoading = false;
|
||||
String? error;
|
||||
|
||||
// TTL guard — prevents redundant API calls from multiple screens
|
||||
@@ -44,7 +45,6 @@ class GamificationProvider extends ChangeNotifier {
|
||||
try {
|
||||
final results = await Future.wait([
|
||||
_service.getDashboard(),
|
||||
_service.getLeaderboard(district: leaderboardDistrict, timePeriod: leaderboardTimePeriod),
|
||||
_service.getShopItems(),
|
||||
_service.getAchievements(),
|
||||
]);
|
||||
@@ -53,17 +53,12 @@ class GamificationProvider extends ChangeNotifier {
|
||||
profile = dashboard.profile;
|
||||
submissions = dashboard.submissions;
|
||||
|
||||
final lbResponse = results[1] as LeaderboardResponse;
|
||||
leaderboard = lbResponse.entries;
|
||||
currentUserStats = lbResponse.currentUser;
|
||||
totalParticipants = lbResponse.totalParticipants;
|
||||
|
||||
shopItems = results[2] as List<ShopItem>;
|
||||
shopItems = results[1] as List<ShopItem>;
|
||||
|
||||
// Prefer achievements from dashboard API; fall back to fetched or existing defaults
|
||||
final dashAchievements = dashboard.achievements;
|
||||
final fetchedAchievements = results[3] as List<AchievementBadge>;
|
||||
|
||||
final fetchedAchievements = results[2] as List<AchievementBadge>;
|
||||
|
||||
if (dashAchievements.isNotEmpty) {
|
||||
achievements = dashAchievements;
|
||||
} else if (fetchedAchievements.isNotEmpty) {
|
||||
@@ -80,12 +75,35 @@ class GamificationProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Load leaderboard independently (decoupled from loadAll)
|
||||
// ---------------------------------------------------------------------------
|
||||
Future<void> loadLeaderboard() async {
|
||||
isLeaderboardLoading = true;
|
||||
notifyListeners();
|
||||
try {
|
||||
final response = await _service.getLeaderboard(
|
||||
district: leaderboardDistrict,
|
||||
timePeriod: leaderboardTimePeriod,
|
||||
);
|
||||
leaderboard = response.entries;
|
||||
currentUserStats = response.currentUser;
|
||||
totalParticipants = response.totalParticipants;
|
||||
} catch (e) {
|
||||
error = userFriendlyError(e);
|
||||
} finally {
|
||||
isLeaderboardLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Change district filter
|
||||
// ---------------------------------------------------------------------------
|
||||
Future<void> setDistrict(String district) async {
|
||||
if (leaderboardDistrict == district) return;
|
||||
leaderboardDistrict = district;
|
||||
isLeaderboardLoading = true;
|
||||
notifyListeners();
|
||||
try {
|
||||
final response = await _service.getLeaderboard(district: district, timePeriod: leaderboardTimePeriod);
|
||||
@@ -94,6 +112,8 @@ class GamificationProvider extends ChangeNotifier {
|
||||
totalParticipants = response.totalParticipants;
|
||||
} catch (e) {
|
||||
error = userFriendlyError(e);
|
||||
} finally {
|
||||
isLeaderboardLoading = false;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -104,6 +124,7 @@ class GamificationProvider extends ChangeNotifier {
|
||||
Future<void> setTimePeriod(String period) async {
|
||||
if (leaderboardTimePeriod == period) return;
|
||||
leaderboardTimePeriod = period;
|
||||
isLeaderboardLoading = true;
|
||||
notifyListeners();
|
||||
try {
|
||||
final response = await _service.getLeaderboard(district: leaderboardDistrict, timePeriod: period);
|
||||
@@ -112,6 +133,8 @@ class GamificationProvider extends ChangeNotifier {
|
||||
totalParticipants = response.totalParticipants;
|
||||
} catch (e) {
|
||||
error = userFriendlyError(e);
|
||||
} finally {
|
||||
isLeaderboardLoading = false;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -104,7 +104,9 @@ class _ContributeScreenState extends State<ContributeScreen>
|
||||
super.initState();
|
||||
PostHogService.instance.screen('Contribute');
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
context.read<GamificationProvider>().loadAll();
|
||||
final p = context.read<GamificationProvider>();
|
||||
p.loadAll();
|
||||
p.loadLeaderboard(); // independent — always fires regardless of loadAll TTL
|
||||
});
|
||||
}
|
||||
|
||||
@@ -274,7 +276,7 @@ class _ContributeScreenState extends State<ContributeScreen>
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Leaderboard List
|
||||
if (provider.isLoading && leaderboard.isEmpty)
|
||||
if (provider.isLeaderboardLoading && leaderboard.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 60),
|
||||
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
|
||||
|
||||
Reference in New Issue
Block a user