strava_client 2.3.1
strava_client: ^2.3.1 copied to clipboard
An unofficial Flutter client for the Strava V3 API: OAuth2 authentication with automatic token refresh, typed models, and repository-based endpoints.
strava_client #
An unofficial Flutter client for the Strava V3 API.
It handles the OAuth2 flow (including the native Strava app on iOS), automatic token storage and refresh, and gives you typed models and repositories for the Strava endpoints.
- 🔐 OAuth2 authentication with automatic token refresh
- 🧩 Typed request/response models (generated with
json_serializable) - 🗂️ Repository-per-domain API (athletes, activities, segments, …)
- ⚠️ Structured error handling via
Fault - 📖 Bundled, validated OpenAPI specs for the Strava API
Looking for a hands-on tour? The example app is an interactive API explorer that lets you log in and run every call against your account.
Table of contents #
- Installation
- Quick start
- Platform setup
- Authentication
- Making API calls
- Error handling
- Supported endpoints
- Example app
- Contributing
- Acknowledgements
- License
Installation #
Add the package:
flutter pub add strava_client
or in pubspec.yaml:
dependencies:
strava_client: ^2.3.0
Requires Dart >=3.8.0.
Quick start #
import 'package:strava_client/strava_client.dart';
final stravaClient = StravaClient(
clientId: "YOUR_CLIENT_ID",
secret: "YOUR_CLIENT_SECRET",
);
Future<void> main() async {
// 1. Authenticate (opens the Strava login / consent screen).
final token = await stravaClient.authentication.authenticate(
scopes: [
AuthenticationScope.profile_read_all,
AuthenticationScope.read_all,
AuthenticationScope.activity_read_all,
],
redirectUrl: "stravaflutter://redirect",
callbackUrlScheme: "stravaflutter",
);
print("Access token: ${token.accessToken}");
// 2. Call the API — the token is attached (and refreshed) automatically.
final athlete = await stravaClient.athletes.getAuthenticatedAthlete();
print("Hello ${athlete.firstname}!");
}
Create the client once and reuse it (it wires up shared session state).
Platform setup #
The redirect URL scheme used in the snippets below is stravaflutter://redirect.
In your Strava API settings set the
Authorization Callback Domain to redirect.
Android #
Add the callback activity to android/app/src/main/AndroidManifest.xml:
<activity
android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
android:exported="true">
<intent-filter android:label="flutter_web_auth_2">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="redirect" android:scheme="stravaflutter" />
</intent-filter>
</activity>
Set android:launchMode="singleTop" on your main activity and remove any
android:taskAffinity entries.
On Android 9 (API 28) the auth webview can throw
net::ERR_CLEARTEXT_NOT_PERMITTED. If so, addandroid:usesCleartextTraffic="true"to your<application>tag.
iOS #
Add the URL scheme to ios/Runner/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>stravaflutter</string>
<key>CFBundleURLSchemes</key>
<array>
<string>stravaflutter</string>
</array>
</dict>
</array>
To let users authenticate through the native Strava app when installed, also add:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>strava</string>
</array>
The web platform is not supported (the OAuth redirect does not return to the app). Use Android or iOS.
Authentication #
// Authenticate. If a valid token is already stored it is reused; an expired
// token is refreshed automatically.
final TokenResponse token = await stravaClient.authentication.authenticate(
scopes: [AuthenticationScope.activity_read_all],
redirectUrl: "stravaflutter://redirect",
callbackUrlScheme: "stravaflutter",
forceShowingApproval: false, // force the consent screen even if already granted
preferEphemeral: true, // iOS: don't share cookies with Safari
);
// Revoke access and clear the stored token.
await stravaClient.authentication.deAuthorize();
Available scopes (AuthenticationScope): read, read_all,
profile_read_all, profile_write, activity_read, activity_read_all,
activity_write.
Token storage & refresh #
Tokens are persisted with shared_preferences and reused across launches. On
every authenticated request the client checks expiry and transparently
refreshes the access token using the stored refresh token, so you don't have
to manage refresh yourself.
Making API calls #
Calls are grouped into repositories on the client:
| Getter | Repository |
|---|---|
stravaClient.authentication |
OAuth |
stravaClient.athletes |
Athlete |
stravaClient.activities |
Activities |
stravaClient.clubs |
Clubs |
stravaClient.gears |
Gear |
stravaClient.routes |
Routes |
stravaClient.runningRaces |
Running races |
stravaClient.segments |
Segments |
stravaClient.segmentEfforts |
Segment efforts |
stravaClient.streams |
Streams |
stravaClient.uploads |
Uploads |
// Recent activities
final activities = await stravaClient.activities.listLoggedInAthleteActivities(
DateTime.now(), // before
DateTime.now().subtract(Duration(days: 30)), // after
1, // page
30, // per page
);
// A detailed segment
final segment = await stravaClient.segments.getSegment(229781);
// Activity streams keyed by type
final streams = await stravaClient.streams.getActivityStreamsByType(
12345678,
["distance", "heartrate", "watts"],
);
Error handling #
Failed requests throw a typed Fault:
try {
await stravaClient.athletes.getAuthenticatedAthlete();
} on Fault catch (fault) {
print("message: ${fault.message}");
for (final e in fault.errors ?? []) {
print(" ${e.resource}.${e.field}: ${e.code}");
}
}
Supported endpoints #
Athlete
getAuthenticatedAthletegetAthleteZones(heart-rate & power zones)getAthleteStatsupdateAthlete
Activities
getActivitylistLoggedInAthleteActivitieslistActivityCommentslistActivityKudoersgetLapsByActivityIdgetActivityZonescreateActivityupdateActivity
Clubs
getClubgetLoggedInAthleteClubslistClubActivitieslistClubAdministratorslistClubMembers
Gear / Routes / Running races
gears.getGearByIdroutes.getRoute,routes.listAthleteRoutes,routes.exportRouteGPX,routes.exportRouteTCXrunningRaces.listRunningRaces,runningRaces.getRage
Segments & efforts
segments.getSegmentsegments.listStarredSegmentssegments.exploreSegmentssegments.getLeaderBoardsegments.starSegmentsegmentEfforts.getSegmentEffort,segmentEfforts.listSegmentEfforts
Streams & uploads
streams.getActivityStreams/getActivityStreamsByTypestreams.getRouteStreams/getRouteStreamsByTypestreams.getSegmentStreams/getSegmentStreamsByTypestreams.getSegmentEffortStreams/getSegmentEffortStreamsByTypeuploads.uploadActivity,uploads.getUpload
To upload activities you can generate TCX files with the
rw_tcx package.
Missing an endpoint? Open an issue or send a PR — the repository pattern makes new calls easy to add.
Example app #
The example/ directory is a full API Explorer: log in with
OAuth, pick any call from a grouped list, fill in its parameters, and see the
pretty-printed JSON response (or Fault).
cd example
cp lib/secret.dart.example lib/secret.dart # add your clientId + secret
flutter run
Contributing #
Contributions are welcome! Please:
- Open an issue for bugs or feature requests.
- For PRs: keep changes backwards compatible where possible, run
dart analyzeandflutter test, and add tests where it makes sense.
CI runs analysis and tests on every PR.
Acknowledgements #
- @Birdyf for the original package.
- Patrick Finkelstein, the package's original maintainer.
- Joe Birch — OAuth reference.
- Strava's published Swagger spec,
bundled and validated under
openapi/.
License #
MIT — Copyright (c) 2019-present Serdar Coşkun and contributors.