Exercises & Muscle Groups Service
The Exercises & Muscle Groups Service provides comprehensive access to OpenLift's exercise library with rich metadata, instructional content, and hierarchical muscle group associations.
Overviewβ
This service manages OpenLift's extensive exercise database, providing:
- Complete exercise library with rich metadata
- Hierarchical muscle group organization (General β Specific)
- Exercise search and filtering capabilities
- Instructional content including videos, images, and tips
- Equipment requirements and exercise difficulty levels
- Flutter-compatible data structure for mobile integration
Key Featuresβ
π Comprehensive Exercise Libraryβ
- Rich Metadata: Instructions, tips, difficulty levels, equipment requirements
- Visual Content: Exercise images, animation URLs, and YouTube demo videos
- Muscle Targeting: Primary, secondary, and tertiary muscle groups
- Equipment Information: Required equipment and quantity specifications
ποΈ Muscle Group Hierarchyβ
- Two-Tier Structure: General categories (Arms, Legs, Back) β Specific muscles (Biceps Brachii, Quadriceps)
- Flexible Organization: Supports complex muscle group relationships
- Image Assets: Visual representations for each muscle group
π Advanced Search & Filteringβ
- Text Search: Case-insensitive exercise name matching
- Muscle Group Filtering: Filter exercises by specific muscle groups
- Relay Pagination: Efficient cursor-based pagination for large result sets
- Total Count: Accurate result counts for UI pagination
Architectureβ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Client App β β Exercise Serviceβ β Database β
β β β β β β
β β’ Exercise List βββββΆβ β’ Search Logic βββββΆβ β’ Exercise Data β
β β’ Muscle Browse ββββββ β’ Filtering ββββββ β’ Muscle Groups β
β β’ Detail Views β β β’ Pagination β β β’ Relationships β
β β’ Offline Cache β β β’ Data Mapping β β β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
Service Responsibilitiesβ
β Exercise Service Handlesβ
- Exercise library management and retrieval
- Exercise search and filtering operations
- Muscle group hierarchy organization
- Exercise metadata and instructional content
- Equipment and difficulty information
- Flutter-compatible data transformation
β Exercise Service Does NOT Handleβ
- Exercise creation or modification (read-only for clients)
- User-specific exercise preferences
- Workout planning or program creation
- Exercise performance tracking
- Custom exercise definitions
Core Data Modelsβ
Exercise Modelβ
interface Exercise {
// Core identifiers
id: string;
name: string;
slug: string;
// Basic information
description?: string;
level?: 'beginner' | 'intermediate' | 'advanced';
// Instructional content
instructions?: any; // JSONValue with structured instructions
tips: string[];
shortYouTubeDemo?: string;
inDepthYouTubeExplanation?: string;
// Visual assets
animationUrl?: string;
imageUrl?: string; // Flutter-compatible alias
videoUrl?: string; // Flutter-compatible alias
// Muscle targeting
targetMuscleGroup?: string;
primeMoverMuscle?: string;
secondaryMuscle?: string;
tertiaryMuscle?: string;
muscleGroups: MuscleGroup[];
primaryMuscleGroup?: MuscleGroup;
// Equipment and setup
primaryEquipment?: string;
equipmentType?: string; // Flutter-compatible alias
numPrimaryItems: number;
// Default units
defaultWeightUnit?: string;
defaultRepUnit?: string;
}
Muscle Group Hierarchyβ
// Specific muscle groups (e.g., "Biceps Brachii", "Rectus Femoris")
interface MuscleGroup {
id: string;
name: string;
slug: string;
imageName?: string;
generalGroup?: GeneralMuscleGroup;
}
// General muscle categories (e.g., "Arms", "Legs", "Back")
interface GeneralMuscleGroup {
id: string;
name: string;
specificMuscleGroups: MuscleGroup[];
}
Key Operationsβ
Exercise Search & Retrievalβ
# Get paginated exercises with search and filtering
query GetExercises($first: Int, $search: String, $muscleGroupId: String) {
exercises(first: $first, search: $search, muscleGroupId: $muscleGroupId) {
totalCount
pageInfo {
hasNextPage
endCursor
}
edges {
node {
id
name
level
imageUrl
equipmentType
muscleGroups {
id
name
slug
}
}
}
}
}
# Get detailed exercise information
query GetExercise($id: ID!) {
exercise(id: $id) {
id
name
description
level
instructions
tips
imageUrl
videoUrl
equipmentType
muscleGroups {
id
name
generalGroup {
name
}
}
}
}
Muscle Group Navigationβ
# Get muscle group hierarchy
query GetMuscleGroupHierarchy {
generalMuscleGroups {
id
name
specificMuscleGroups {
id
name
slug
imageName
}
}
}
Integration Patternsβ
Flutter Exercise Browserβ
class ExerciseService {
final GraphQLClient _client;
Future<ExerciseConnection> searchExercises({
String? search,
String? muscleGroupId,
int limit = 20,
String? cursor,
}) async {
const query = '''
query SearchExercises(
\$first: Int,
\$search: String,
\$muscleGroupId: String,
\$after: String
) {
exercises(
first: \$first,
search: \$search,
muscleGroupId: \$muscleGroupId,
after: \$after
) {
totalCount
pageInfo {
hasNextPage
endCursor
}
edges {
node {
id
name
level
imageUrl
equipmentType
muscleGroups {
name
slug
}
}
}
}
}
''';
final result = await _client.query(QueryOptions(
document: gql(query),
variables: {
'first': limit,
'search': search,
'muscleGroupId': muscleGroupId,
'after': cursor,
}..removeWhere((key, value) => value == null),
cachePolicy: CachePolicy.cacheFirst, // Cache for offline
));
return ExerciseConnection.fromJson(result.data!['exercises']);
}
}
Exercise Detail Viewβ
class ExerciseDetailWidget extends StatelessWidget {
final String exerciseId;
Widget build(BuildContext context) {
return FutureBuilder<Exercise?>(
future: exerciseService.getExercise(exerciseId),
builder: (context, snapshot) {
final exercise = snapshot.data;
if (exercise == null) return const CircularProgressIndicator();
return Column(
children: [
// Exercise image
if (exercise.imageUrl != null)
CachedNetworkImage(imageUrl: exercise.imageUrl!),
// Exercise details
Text(exercise.name, style: Theme.of(context).textTheme.headline4),
if (exercise.level != null)
Chip(label: Text('Level: ${exercise.level}')),
// Muscle groups
Wrap(
children: exercise.muscleGroups.map((muscle) =>
Chip(
label: Text(muscle.name),
avatar: muscle.imageName != null
? Image.asset('assets/muscles/${muscle.imageName}')
: null,
)
).toList(),
),
// Exercise tips
if (exercise.tips.isNotEmpty) ...[
const Text('Tips:', style: TextStyle(fontWeight: FontWeight.bold)),
...exercise.tips.map((tip) => Text('β’ $tip')),
],
// Video demo
if (exercise.videoUrl != null)
ElevatedButton(
onPressed: () => launchUrl(Uri.parse(exercise.videoUrl!)),
child: const Text('Watch Demo'),
),
],
);
},
);
}
}
Search & Filtering Capabilitiesβ
Text Searchβ
- Case-insensitive: Searches across exercise names
- Partial matching: Supports substring searches
- Indexed search: Optimized database queries for performance
Muscle Group Filteringβ
class MuscleGroupFilter {
// Filter exercises by specific muscle group
Future<List<Exercise>> getExercisesForMuscleGroup(String muscleGroupId) {
return exerciseService.getExercises(muscleGroupId: muscleGroupId);
}
// Get exercises targeting multiple muscle groups
Future<List<Exercise>> getExercisesForMuscleGroups(List<String> muscleGroupIds) {
// Implementation handles multiple group filtering
return exerciseService.getExercisesForMultipleMuscleGroups(muscleGroupIds);
}
}
Advanced Filteringβ
interface ExerciseFilters {
search?: string;
muscleGroupIds?: string[];
equipmentTypes?: string[];
difficultyLevels?: ('beginner' | 'intermediate' | 'advanced')[];
hasVideo?: boolean;
hasInstructions?: boolean;
}
Flutter-Compatible Featuresβ
Alias Fieldsβ
The service provides Flutter-friendly aliases for common fields:
// Original field β Flutter alias
animationUrl β imageUrl
shortYouTubeDemo β videoUrl
primaryEquipment β equipmentType
muscleGroups β mainMuscleGroups (alias)
Optimized Data Structureβ
class Exercise {
// Flutter-optimized constructor
const Exercise({
required this.id,
required this.name,
required this.slug,
this.description,
this.level,
this.instructions,
required this.tips,
this.imageUrl, // Direct Flutter usage
this.videoUrl, // Direct Flutter usage
this.equipmentType, // Direct Flutter usage
required this.muscleGroups,
});
// Offline caching support
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'slug': slug,
'imageUrl': imageUrl,
'tips': tips,
// ... complete serialization
};
factory Exercise.fromJson(Map<String, dynamic> json) {
return Exercise(
id: json['id'],
name: json['name'],
imageUrl: json['imageUrl'],
tips: List<String>.from(json['tips'] ?? []),
// ... complete deserialization
);
}
}
Offline Supportβ
Caching Strategyβ
class OfflineExerciseProvider {
// Cache essential exercise data for offline use
Future<void> cacheEssentialExercises() async {
final popularExercises = await exerciseService.getExercises(first: 100);
final muscleGroups = await muscleGroupService.getGeneralMuscleGroups();
await _cacheExercises(popularExercises.exercises);
await _cacheMuscleGroups(muscleGroups);
}
// Provide offline exercise search
Future<List<Exercise>> searchCachedExercises(String query) async {
final cachedExercises = await _getCachedExercises();
return cachedExercises
.where((ex) => ex.name.toLowerCase().contains(query.toLowerCase()))
.toList();
}
}
Performance Optimizationβ
Efficient Paginationβ
class ExerciseListWidget extends StatefulWidget {
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
// Trigger pagination near end of list
if (index == exercises.length - 5 && hasNextPage) {
_loadMoreExercises();
}
return ExerciseListTile(exercise: exercises[index]);
},
);
}
Future<void> _loadMoreExercises() async {
if (isLoading) return;
final nextPage = await exerciseService.getExercises(
after: lastCursor,
search: currentSearch,
muscleGroupId: selectedMuscleGroup,
);
setState(() {
exercises.addAll(nextPage.exercises);
lastCursor = nextPage.pageInfo.endCursor;
hasNextPage = nextPage.pageInfo.hasNextPage;
});
}
}
Error Handlingβ
Common Exercise Service Errorsβ
try {
final exercises = await exerciseService.searchExercises(search: query);
} on GraphQLError catch (e) {
switch (e.extensions?['code']) {
case 'INVALID_SEARCH_QUERY':
showError('Search query contains invalid characters');
break;
case 'INVALID_MUSCLE_GROUP':
showError('Selected muscle group not found');
break;
case 'PAGINATION_ERROR':
showError('Unable to load more exercises');
break;
default:
showError('Failed to search exercises');
}
}
Testing Strategyβ
Unit Testsβ
- Exercise search logic
- Muscle group hierarchy navigation
- Data transformation and mapping
- Flutter alias field generation
Integration Testsβ
- GraphQL query execution
- Pagination functionality
- Search and filtering combinations
- Offline caching behavior
Related Servicesβ
Dependenciesβ
- Database: Exercise and muscle group data storage
- Content Management: Exercise images, videos, and instructional content
Consumersβ
- Workout History Service: Exercise selection for workout logging
- Program Plans Service: Exercise selection for program templates
- Progression Playbook Service: Exercise progression rules
- Coaching Service: Exercise recommendations
API Documentationβ
For detailed GraphQL schema and usage examples, see:
- Exercise & Muscle Group API documentation: coming soon
Supportβ
For exercise library questions:
- Review the Exercise & Muscle Group API documentation
- Test queries in GraphQL Playground
- Check exercise data structure and muscle group hierarchy
- Contact the content team for exercise metadata updates