SonarFit Flutter Plugin
Official Flutter plugin for the SonarFit SDK - AI-powered strength training with real-time rep detection using Apple Watch or AirPods Pro motion sensors.
Features
- Real-time Rep Detection: AI-powered motion analysis counts reps automatically
- Multiple Exercise Support: Squats, deadlifts, bench press, and more
- Device Flexibility: Works with Apple Watch or AirPods Pro
- Built-in UI: Beautiful, ready-to-use workout interface
- Progress Tracking: Automatic set/rep tracking with rest timers
- Permission Handling: Automatic motion permission requests
Platform Support
| Platform | Support |
|---|---|
| iOS | ✅ 17.0+ |
| Android | ❌ Not supported |
Installation
Add this to your pubspec.yaml:
dependencies:
sonarfit_flutter: ^1.0.0
Then run:
flutter pub get
iOS Configuration
- Update iOS deployment target in
ios/Podfile:
platform :ios, '17.0'
- Add required permissions to
ios/Runner/Info.plist:
<key>NSMotionUsageDescription</key>
<string>SonarFit needs motion sensor access to count your reps during workouts</string>
<key>NSHealthUpdateUsageDescription</key>
<string>SonarFit needs HealthKit access to save your workout data</string>
<key>NSHealthShareUsageDescription</key>
<string>SonarFit needs HealthKit access to read your workout history</string>
- Install CocoaPods dependencies:
cd ios && pod install && cd ..
Quick Start
1. Initialize the SDK
Get your API key from https://sonarfit.com/get-started
import 'package:sonarfit_flutter/sonarfit_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize SonarFit SDK
await SonarFit.initialize('your-api-key-here');
runApp(MyApp());
}
2. Present a Workout
import 'package:flutter/material.dart';
import 'package:sonarfit_flutter/sonarfit_flutter.dart';
class WorkoutScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SonarFit Workout')),
body: Center(
child: ElevatedButton(
onPressed: () async {
try {
final result = await SonarFit.presentWorkout(
WorkoutConfig(
workoutType: WorkoutType.squat,
sets: 3,
reps: 10,
restTime: 60,
),
);
if (result.completed) {
print('Workout completed!');
print('Reps: ${result.totalRepsCompleted}/${result.totalTargetReps}');
print('Completion: ${(result.completionPercentage * 100).toStringAsFixed(0)}%');
} else if (result.cancelled) {
print('Workout cancelled');
}
} on SonarFitException catch (e) {
print('Error: ${e.message}');
}
},
child: Text('Start Workout'),
),
),
);
}
}
API Reference
SonarFit
Main entry point for the SDK.
Methods
initialize(String apiKey)
Initialize the SDK with your API key. Must be called before using any other methods.
await SonarFit.initialize('your-api-key');
Throws: SonarFitException if initialization fails.
presentWorkout(WorkoutConfig config)
Present SonarFit's built-in workout UI.
final result = await SonarFit.presentWorkout(
WorkoutConfig(
workoutType: WorkoutType.squat,
sets: 3,
reps: 10,
restTime: 60,
countdownDuration: 3,
autoReLift: true,
deviceType: DeviceType.none, // Auto-detect
),
);
Returns: WorkoutResult when workout completes or user dismisses.
Throws: SonarFitException if workout fails to start.
WorkoutConfig
Configuration for a workout session.
Properties
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
workoutType |
WorkoutType |
Yes | - | Type of exercise |
sets |
int |
Yes | - | Number of sets |
reps |
int |
Yes | - | Target reps per set |
restTime |
int |
No | 60 |
Rest duration in seconds |
countdownDuration |
int |
No | 3 |
Countdown before each set |
autoReLift |
bool |
No | true |
Auto-detect reps |
deviceType |
DeviceType |
No | none |
Motion tracking device |
Example
final config = WorkoutConfig(
workoutType: WorkoutType.deadlift,
sets: 5,
reps: 5,
restTime: 90,
countdownDuration: 5,
autoReLift: true,
deviceType: DeviceType.watch,
);
WorkoutType
Supported exercise types.
enum WorkoutType {
squat,
deadlift,
benchpress,
}
DeviceType
Motion tracking devices.
enum DeviceType {
none, // Auto-detect available device
watch, // Apple Watch
airpods, // AirPods Pro/Max with motion sensors
}
WorkoutResult
Result of a completed workout.
Properties
| Property | Type | Description |
|---|---|---|
completed |
bool |
Was the workout completed? |
cancelled |
bool |
Was the workout cancelled by user? |
workoutType |
WorkoutType? |
Exercise type |
deviceType |
DeviceType? |
Device used for tracking |
startTime |
DateTime? |
Workout start time |
endTime |
DateTime? |
Workout end time |
totalDuration |
double? |
Total duration in seconds |
completionPercentage |
double |
Completion percentage (0.0-1.0) |
targetSets |
int? |
Target number of sets |
targetRepsPerSet |
int? |
Target reps per set |
totalRepsCompleted |
int |
Total reps completed |
totalTargetReps |
int? |
Total target reps |
sets |
List<WorkoutSet> |
Per-set details |
Example
if (result.completed) {
print('Completed ${result.totalRepsCompleted}/${result.totalTargetReps} reps');
print('${(result.completionPercentage * 100).toStringAsFixed(0)}% complete');
for (var set in result.sets) {
print('Set ${set.setNumber}: ${set.repsCompleted} reps');
}
}
SonarFitException
Exception thrown by SDK operations.
Properties
| Property | Type | Description |
|---|---|---|
code |
String |
Error code |
message |
String |
Human-readable error message |
details |
dynamic |
Additional error details |
Common Error Codes
| Code | Description |
|---|---|
E_INIT_FAILED |
SDK initialization failed |
E_PERMISSION |
Motion permission denied |
E_INVALID_CONFIG |
Invalid workout configuration |
E_NO_ROOT_VC |
Cannot find root view controller |
Examples
Complete Workout Flow
import 'package:flutter/material.dart';
import 'package:sonarfit_flutter/sonarfit_flutter.dart';
class WorkoutExampleScreen extends StatefulWidget {
@override
_WorkoutExampleScreenState createState() => _WorkoutExampleScreenState();
}
class _WorkoutExampleScreenState extends State<WorkoutExampleScreen> {
WorkoutResult? _lastResult;
bool _isLoading = false;
Future<void> _startWorkout() async {
setState(() => _isLoading = true);
try {
final result = await SonarFit.presentWorkout(
WorkoutConfig(
workoutType: WorkoutType.squat,
sets: 3,
reps: 10,
restTime: 60,
),
);
setState(() {
_lastResult = result;
_isLoading = false;
});
if (result.completed) {
_showCompletionDialog(result);
}
} on SonarFitException catch (e) {
setState(() => _isLoading = false);
_showErrorDialog(e);
}
}
void _showCompletionDialog(WorkoutResult result) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Workout Complete! 🎉'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Reps: ${result.totalRepsCompleted}/${result.totalTargetReps}'),
Text('Completion: ${(result.completionPercentage * 100).toStringAsFixed(0)}%'),
SizedBox(height: 16),
Text('Sets:', style: TextStyle(fontWeight: FontWeight.bold)),
...result.sets.map((set) =>
Text('Set ${set.setNumber}: ${set.repsCompleted} reps')),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('OK'),
),
],
),
);
}
void _showErrorDialog(SonarFitException error) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Error'),
content: Text(error.message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('OK'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SonarFit Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_isLoading)
CircularProgressIndicator()
else
ElevatedButton(
onPressed: _startWorkout,
child: Text('Start Workout'),
),
if (_lastResult != null) ...[
SizedBox(height: 32),
Text(
'Last Workout',
style: Theme.of(context).textTheme.headlineSmall,
),
SizedBox(height: 8),
Text('${_lastResult!.totalRepsCompleted} reps completed'),
Text('${(_lastResult!.completionPercentage * 100).toStringAsFixed(0)}% complete'),
],
],
),
),
);
}
}
Multiple Workout Types
class WorkoutTypeSelector extends StatelessWidget {
final List<WorkoutType> workouts = [
WorkoutType.squat,
WorkoutType.deadlift,
WorkoutType.benchpress,
];
Future<void> _startWorkout(BuildContext context, WorkoutType type) async {
try {
final result = await SonarFit.presentWorkout(
WorkoutConfig(
workoutType: type,
sets: 3,
reps: 10,
),
);
if (result.completed) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Workout completed! ${result.totalRepsCompleted} reps')),
);
}
} on SonarFitException catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: ${e.message}')),
);
}
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: workouts.length,
itemBuilder: (context, index) {
final workout = workouts[index];
return ListTile(
title: Text(_getWorkoutName(workout)),
trailing: Icon(Icons.arrow_forward),
onTap: () => _startWorkout(context, workout),
);
},
);
}
String _getWorkoutName(WorkoutType type) {
switch (type) {
case WorkoutType.squat:
return 'Squat';
case WorkoutType.deadlift:
return 'Deadlift';
case WorkoutType.benchpress:
return 'Bench Press';
}
}
}
Pricing
SonarFit uses MAU-based pricing (Monthly Active Users). A user counts as active when they complete at least one workout in a calendar month.
| Tier | Price | Included MAUs | Overage |
|---|---|---|---|
| Developer | Free | 500 (hard cap) | N/A |
| Startup | £129/mo | 2,000 | £0.08/MAU |
| Growth | £599/mo | 10,000 | £0.07/MAU |
| Enterprise | Custom | Negotiated | Custom |
Get started at https://sonarfit.com/pricing
Requirements
- Flutter: 3.3.0 or higher
- Dart: 3.0.0 or higher
- iOS: 17.0 or higher
- Xcode: 16.0 or higher
- CocoaPods: 1.11.0 or higher
Troubleshooting
Build Errors
Error: "Platform version is lower than 17.0"
Solution: Update ios/Podfile:
platform :ios, '17.0'
Error: "No such module 'SonarFitKit'"
Solution: Run:
cd ios && pod install && cd ..
flutter clean
flutter pub get
Runtime Errors
Error: "Motion permission denied"
Solution: Add NSMotionUsageDescription to Info.plist and ensure user grants permission when prompted.
Error: "SDK not initialized"
Solution: Call SonarFit.initialize() before using any other methods.
Support
- Documentation: https://sonarfit.com/docs
- Email: [email protected]
- GitHub Issues: https://github.com/sonarfit/sonarfit-flutter/issues
License
MIT License - see LICENSE file for details
About SonarFit
SonarFit provides AI-powered workout execution intelligence for fitness apps. Our SDK handles real-time rep counting, form analysis, and workout progression so you can focus on building great user experiences.
Learn more at https://sonarfit.com