Skip to main content

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​

  • 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

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:

  1. Review the Exercise & Muscle Group API documentation
  2. Test queries in GraphQL Playground
  3. Check exercise data structure and muscle group hierarchy
  4. Contact the content team for exercise metadata updates