Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.10.0'
flutter-version: '3.27.1'
channel: 'stable'
- run: flutter pub get
- run: flutter pub run build_runner build --delete-conflicting-outputs
Expand Down
43 changes: 43 additions & 0 deletions lib/src/services/api_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,49 @@ class ApiClient {
}
}

Future<List<Team>> getGlobalTrueSkillRankings(
{String gradeLevel = 'Middle School'}) async {
// RoboStem API uses a different base URL and key
final token = _settings.roboStemApiKey ?? AppConstants.roboStemApiKey;
final dio = Dio(BaseOptions(
baseUrl: AppConstants.roboStemBaseUrl, // https://api.robostem-api.org
headers: {
'x-api-key': token,
'accept': 'application/json',
},
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
));
Comment on lines +312 to +320
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Creating a new Dio instance within the getGlobalTrueSkillRankings method for every call is inefficient. This can lead to performance issues, such as socket exhaustion under heavy use, and prevents connection reuse. It's better to create a dedicated Dio instance for the RoboStem API at the class level, similar to how _dio is handled for RobotEvents, and reuse it across calls.

Comment on lines +311 to +320
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block for creating a Dio instance is nearly identical to the one in the existing getGlobalSkills method. To improve maintainability and reduce code duplication, consider extracting this logic into a private helper method, like _getRoboStemClient(), that returns a configured Dio instance for the RoboStem API. You could then call this helper in both getGlobalTrueSkillRankings and getGlobalSkills.


try {
final response = await dio.get('/api/rankings/statiq', queryParameters: {
'program': 'VIQRC',
'limit': 100, // Reduced from 2500 for performance
'grade_level': gradeLevel,
});

// RoboStem response structure might differ. Assuming standard list or {data: []}
List<Map<String, dynamic>> rawList = [];
if (response.data is List) {
rawList = (response.data as List).cast<Map<String, dynamic>>();
} else if (response.data is Map && response.data['data'] is List) {
rawList = (response.data['data'] as List).cast<Map<String, dynamic>>();
}

// Client-side filter fallback (just in case API returns mixed results)
// The sample response shows 'grade_level': 'Middle School' or 'Elementary School'
final filteredList = rawList.where((item) {
// Try both 'grade' (standard Team model) and 'grade_level' (API param/possible return)
final g = item['grade'] ?? item['grade_level'];
return g == gradeLevel;
}).toList();

return filteredList.map((json) => Team.fromJson(json)).toList();
} catch (e) {
return [];
}
Comment on lines +338 to +340
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The catch block currently swallows the error and returns an empty list without any logging. This can make it very difficult to debug API failures. It's a good practice to log the error, similar to how it's done in the getGlobalSkills method, to aid in diagnostics.

    } catch (e) {
      if (e is DioException) {
        print('RoboStem API Error (TrueSkill): ${e.message} ${e.response?.statusCode}');
      } else {
        print('Error fetching TrueSkill rankings: $e');
      }
      return [];
    }

Comment on lines +338 to +340
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This catch block silently swallows exceptions and returns an empty list. This prevents the UI from displaying a meaningful error message to the user in case of a network failure or other API error. The UI layer is prepared to handle exceptions. To improve error feedback, you should log the error and then rethrow it to be handled by the caller.

    } catch (e) {
      print('Error fetching global TrueSkill rankings: $e');
      rethrow;
    }

}

Future<List<Team>> searchTeams(
{String? number, int? program, int? limit}) async {
// Strategy: Use RobotEvents API for exact team lookup first,
Expand Down
2 changes: 1 addition & 1 deletion lib/src/ui/screens/events_list_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ class _EventsListViewState extends ConsumerState<EventsListView> {
const SizedBox(width: 8),
CupertinoButton(
padding: EdgeInsets.zero,
minimumSize: Size.zero,
minSize: 0,
child: Icon(CupertinoIcons.clock, color: primaryColor),
onPressed: () => _showHistory(context),
),
Expand Down
Loading
Loading