Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/providers/beer_provider.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../models/models.dart';
import '../services/services.dart';
Expand Down Expand Up @@ -361,7 +361,7 @@ class BeerProvider extends ChangeNotifier {
} else {
return 'Could not load festivals. Please check your connection.';
}
} else if (error is SocketException) {
} else if (error is http.ClientException) {
return 'No internet connection. Please check your network.';
} else if (error is TimeoutException) {
return 'Request timed out. Please check your connection and try again.';
Expand Down
26 changes: 17 additions & 9 deletions lib/services/beer_api_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,30 @@ class BeerApiService {
}

/// Fetches all available drinks from a festival (all beverage types)
///
///
/// Fetches all beverage types in parallel for faster loading.
/// Throws [BeerApiException] if ALL beverage types fail to load or return
/// no drinks. Individual failures are tracked and reported in the exception
/// message to help diagnose issues like CORS or network problems.
Future<List<Drink>> fetchAllDrinks(Festival festival) async {
final allDrinks = <Drink>[];
final errors = <String, String>{};

for (final beverageType in festival.availableBeverageTypes) {
try {
final drinks = await fetchDrinks(festival, beverageType);
allDrinks.addAll(drinks);
} catch (e) {
// Track the error for this beverage type
errors[beverageType] = e.toString();
}
// Fetch all beverage types in parallel for faster loading
final results = await Future.wait(
festival.availableBeverageTypes.map((beverageType) async {
try {
return await fetchDrinks(festival, beverageType);
} catch (e) {
// Track the error for this beverage type
errors[beverageType] = e.toString();
return <Drink>[];
}
}),
);

for (final drinks in results) {
allDrinks.addAll(drinks);
}

// If we got no drinks at all and there were errors, throw with details
Expand Down
2 changes: 2 additions & 0 deletions lib/widgets/festival_menu_sheets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,10 @@ class FestivalSelectorSheet extends StatelessWidget {
sortedFestivals: festivals,
isSelected: isSelected,
onTap: () {
final router = GoRouter.maybeOf(context);
provider.setFestival(festival);
Navigator.pop(context);
router?.go('/${festival.id}');
},
Comment thread
richardthe3rd marked this conversation as resolved.
onInfoTap: () {
Navigator.pop(context);
Expand Down
14 changes: 7 additions & 7 deletions test/provider_test.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:cambridge_beer_festival/providers/beer_provider.dart';
import 'package:cambridge_beer_festival/services/services.dart';
import 'package:http/http.dart' as http;
import 'package:cambridge_beer_festival/models/models.dart';
import 'package:cambridge_beer_festival/domain/models/models.dart';
import 'package:cambridge_beer_festival/domain/repositories/repositories.dart';
Expand Down Expand Up @@ -153,16 +153,16 @@ void main() {
);
await provider.initialize();

// Mock SocketException (no internet)
// http.ClientException is thrown on network failures across all platforms
when(mockDrinkRepository.getDrinks(any))
.thenThrow(const SocketException('Failed host lookup'));
.thenThrow(http.ClientException('Failed host lookup'));

await provider.loadDrinks();

expect(provider.error, isNotNull);
expect(provider.error, contains('No internet connection'));
expect(provider.error, contains('check your network'));
expect(provider.error, isNot(contains('SocketException')));
expect(provider.error, isNot(contains('ClientException')));
expect(provider.error, isNot(contains('Failed host lookup')));
});

Expand Down Expand Up @@ -315,15 +315,15 @@ void main() {
analyticsService: mockAnalyticsService,
);

// Mock SocketException
// http.ClientException is thrown on network failures across all platforms
when(mockFestivalRepository.getFestivals())
.thenThrow(const SocketException('Network unreachable'));
.thenThrow(http.ClientException('Network unreachable'));

await provider.loadFestivals();

expect(provider.festivalsError, isNotNull);
expect(provider.festivalsError, contains('No internet connection'));
expect(provider.festivalsError, isNot(contains('SocketException')));
expect(provider.festivalsError, isNot(contains('ClientException')));
});

test('shows connection message for FestivalServiceException without status',
Expand Down
Loading
Loading