strava_flutter 1.2.2+61

  • Readme
  • Changelog
  • Example
  • Installing
  • 85

strava_flutter #

Dart/flutter package to use Strava API v3

Follow the "new" Authentication process

https://developers.strava.com/docs/authentication/

API currently supported: #

Authentication #

  • authorize
  • deauthorize
  • getLoggedInAthlete
  • updateLoggedInAthlete (scope profile:write)
  • getLoggedInAthleteActivities (not limited)
  • getLoggedInAthleteZones
  • getGearById
  • getStats
  • getClubById
  • getClubActivitiesById
  • getClubMembersById
  • getRunningRaces
  • getRunningRaceById
  • createActivity
  • uploadActivity (includes getUploadById) Tested on TCX and GPX

To generate TCX you can use the following package https://pub.dev/packages/rw_tcx

  • getSegmentById
  • getLoggedInAthleteStarredSegments
  • getLeaderboardBySegmentId (not limited)
  • starSegment

How to install #

Check on pub.dev/packages to see how to install this package

https://pub.dev/packages/strava_flutter#-installing-tab-

Additional steps when running on Android Pie 9.0 (API level 28) #

The webview returned by the auth process may throw net::ERR_CLEARTEXT_NOT_PERMITTED. In this case add android:usersCleartextTraffic="true" to the AndroidManifest.xml like below:

<application
    ....
    android:usesCleartextTraffic="true"
    ....>

How to use it #

1 -Get the client secret in your Strava settings related to your app https://www.strava.com/settings/api with "Authorization Callback Domain" set to "redirect"

2 - Settings of the url scheme for Strava Authentication redirect url a) For Android Add the following lines in your AndroidManifest.xml (in android/app/src/)

 <!-- To get redirect url when using url_launcher   -->
 <intent-filter>
    <action android:name="android.intent.action.VIEW" />  
    <category android:name="android.intent.category.DEFAULT" /> 
    <category android:name="android.intent.category.BROWSABLE" /> 
    <data android:scheme="stravaflutter" android:host="redirect" />    
    <data android:pathPattern="/.*" />   
</intent-filter>

Change android:launchMode="singleTop" to android:launchMode="singleInstance" It is needed to have authentication working with firefox

b) for iOS Add the following lines in your info.plist (in ios/Flutter/Runner/)

<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>

3 - Create a file secret.dart and put in this file: final String secret = "[Your client secret]"; final String clientId = "[Your appID]";

4 - import 'secret.dart' when you need secret and clientId in Strava API

5 - To see debug info in Strava API, set isInDebug to true in Strava() init

6 - Please check examples.dart for the moment

https://github.com/BirdyF/strava_flutter/blob/master/example/lib/main.dart

https://github.com/BirdyF/strava_flutter/blob/master/example/lib/examples.dart

If you have any problem or need an API not yet implemented please post a new issue

Tested on: #

  • Android 7, 8.0.0, 9.0, 10
  • iOS 12.1, 13.2, 13.3.1
  • NOT working yet on web (Auth problem, not coming back to initial page)

Contributors welcome! #

If you spot a problem/bug or if you consider that the code could be better please post an issue. I am not planning to implement all the Strava APIs, because I dont need all of them in my dev. But let me know if you need some APIs that are not in the current list and I will add it. Alternatively, you can easily implement additional API and I will add it to strava_futter.

Thanks #

Thanks to Joe Birch, I used his code to better understand Oauth process https://github.com/hitherejoe/FlutterOAuth

And Javier for https://javiercbk.github.io/json_to_dart/

License: #

strava_flutter is provided under a MIT License. Copyright (c) 2019-2020 Patrick FINKELSTEIN

[1.2.2+61] #

Added getSegmentEffortById Modification in AdroidManifest.xml to have authentication working with Firefox (not only Chrome)

[1.2.1+51] #

Changed urlScheme from strava to stravaflutter used in url redirect to avoid conflict with strava app when installed in the mobile

[1.2.0+45] #

Fix bug in _saveToken

[1.1.0+37] #

Fix user agent disallowed when selecting Google in Strava Auth page

[1.0.12] #

Store the potentially new refresh token when getting new access token

[1.0.11] #

removed lines in test

[1.0.10] #

added strava_prefix to preferences key

example/lib/main.dart

import 'package:flutter/material.dart';
// To remove # at the end of redirect url when in web mode (not mobile)
// This is a web only package
// import 'dart:html' as html;

import 'package:example/examples.dart';

import 'package:example/secret.dart';

import 'package:example/permissions.dart';

import 'package:strava_flutter/strava.dart';

// Used by example

import 'package:strava_flutter/Models/activity.dart';
import 'package:strava_flutter/API/activities.dart';
import 'package:strava_flutter/Models/club.dart';
import 'package:strava_flutter/Models/detailedAthlete.dart';
import 'package:strava_flutter/Models/gear.dart';
import 'package:strava_flutter/Models/runningRace.dart';
import 'package:strava_flutter/Models/stats.dart';
import 'package:strava_flutter/Models/summaryAthlete.dart';
import 'package:strava_flutter/Models/zone.dart';
import 'package:strava_flutter/Models/fault.dart';

Strava strava;

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Strava Flutter',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: StravaFlutterPage(title: 'Strava Flutter Demo'),
    );
  }
}

class StravaFlutterPage extends StatefulWidget {
  StravaFlutterPage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _StravaFlutterPageState createState() => _StravaFlutterPageState();
}

class _StravaFlutterPageState extends State<StravaFlutterPage> {
  @override
  void initState() {
    setState(() {
      // html.window.history.pushState(null, "home", '/');
    });
    super.initState();
  }

  void exampleStrava() {
    example(secret);
  }

  void exampleSeg() {
    exampleSegment(secret);
  }

  void permissions() {
    testPermissions(secret);
  }

  ///
  /// Example of dart code to use Strava API
  ///
  /// set isInDebug to true in strava init to see the debug info
  void example(String secret) async {
    bool isAuthOk = false;

    final strava = Strava(true, secret);
    final prompt = 'auto';

    isAuthOk = await strava.oauth(
        clientId,
        'activity:write,activity:read_all,profile:read_all,profile:write',
        secret,
        prompt);

    if (isAuthOk) {
      // Get the zones related to the logged athlete
      Zone _zone = await strava.getLoggedInAthleteZones();
      if (_zone.fault.statusCode != 200) {
        print(
            'Error in getLoggedInAthleteZones  ${_zone.fault.statusCode}  ${_zone.fault.message}');
      } else {
        _zone.infoZones.zones.forEach(
            (zone) => print('getLoggedInAthleteZones ${zone.min} ${zone.max}'));
      }

      // Activity 3226262796 with totalElevationGain to 0
      DetailedActivity _activityPhoto =
          await strava.getActivityById('3288393232');

      // Get the photo of an activity
      // PhotoActivity _photo = await strava.getPhotosFromActivityById('3288393232');

      // Create an new activity
      String _startDate = '2020-02-18 10:02:13';
      DetailedActivity _newActivity = await strava.createActivity(
          'Test_Strava_Flutter', 'ride', _startDate, 3600,
          distance: 1555, description: 'This is a strava_flutter test');
      if (_newActivity.fault.statusCode != 201) {
        print(
            'Error in createActivity ${_newActivity.fault.statusCode}  ${_newActivity.fault.message}');
      } else {
        print('createActivity  ${_newActivity.name}');
      }

      // Type of expected answer:
      // {"id":25707617,"username":"patrick_ff","resource_state":3,"firstname":"Patrick","lastname":"FF",
      // "city":"Le Beausset","state":"Provence-Alpes-Côte d'Azur","country":"France","sex":null,"premium"
      DetailedAthlete _athlete = await strava.getLoggedInAthlete();
      if (_athlete.fault.statusCode != 200) {
        print(
            'Error in getloggedInAthlete ${_athlete.fault.statusCode}  ${_athlete.fault.message}');
      } else {
        print('getLoggedInAthlete ${_athlete.firstname}  ${_athlete.lastname}');
      }

      // Type of expected answer
      //  {"biggest_ride_distance":156733.0,"biggest_climb_elevation_gain":null,"recent_ride_totals":{"count":2,"distance":111427.7001953125,
      // "moving_time":17726,"elapsed_time":23181,"elevation_gain":1354.5838375091553,"achievement_count":0},"recent_run_to
      Stats _stats = await strava.getStats(_athlete.id);
      if (_stats.fault.statusCode != 200) {
        print(
            'Error in getStats ${_stats.fault.statusCode}    ${_stats.fault.message}');
      } else {
        print(
            'getStats ${_stats.ytdRideTotals.distance} ${_stats.ytdRideTotals.elevationGain}   ${_stats.allSwimTotals.distance}');
      }

      // A long list of races per city
      // Starting by Walt Disney World Marathon
      List<RunningRace> _listRunningRaces =
          await strava.getRunningRaces('2019');
      if ((_listRunningRaces == null) ||
          (_listRunningRaces[0].fault.statusCode != 200)) {
        print(
            'Error in getRunningRaces: ${_listRunningRaces[0].fault.statusCode}    ${_listRunningRaces[0].fault.message}');
      } else {
        print('getRunningRaces ${_listRunningRaces[0].name}');
      }

      // id corresponding to BMW Berlin Marathon 29th Sept 2019
      RunningRace _race = await strava.getRunningRaceById('2724');
      if (_race.fault.statusCode != 200) {
        print(
            'Error in getRunningRaceById  ${_race.fault.statusCode}    ${_race.fault.message}');
      } else {
        print('getRunningRaceById $_race');
      }

      // Change weight of the loggedAthlete in profile (in kg)
      DetailedAthlete _athlete2 = await strava.updateLoggedInAthlete(80);
      if (_athlete2.fault.statusCode != 200) {
        print(
            'Error in updateLoggedInAthlete ${_athlete2.fault.statusCode}  ${_athlete2.fault.message}');
      } else {
        print('getRunningRaceById $_athlete2');
      }

      /// Gear should be owned by the loggedIn Athleted
      /// Type of expected answer:
      /// {"id":"b4366285","primary":true,"name":"Roubaix Specialized","resource_state":3,"distance":461692.0,
      /// "brand_name":"Specialized","model_name":"Roubaix Expert","frame_type":3,"description":"So comfortable!"}
      Gear _gear = await strava.getGearById("b4366285");
      if (_gear.fault.statusCode != 200) {
        print(
            'error code getGearById  ${_gear.fault.statusCode}  ${_gear.fault.message}');
      } else {
        print('getGearById $_gear');
      }

      // IMPORTANT ------
      //  You have to join this club to do the test
      final clubStravaMarseille = '226910';

      /// Answer expected:
      /// {"id":226910,"resource_state":3,"name":"STRAVA Marseille ",
      /// "profile_medium":"https://dgalywyr863hv.cloudfront.net/pictures/clubs/226910/5003423/3/medium.jpg","profile":"https://dgalywyr863hv.cloudfront.net/pictures/clubs/226910/5003423/3/larg
      Club _club = await strava.getClubById(clubStravaMarseille);
      if (_club.fault.statusCode != 200) {
        print(
            'error code getClubById  ${_club.fault.statusCode}  ${_club.fault.message}');
      } else {
        print('getClubById $_club');
      }

      /// List the member of Strava club
      /// Expected answer (should start like this):
      ///  [{"resource_state":2,"firstname":"Adam","lastname":"Š.","membership":"member",
      /// "admin":false,"owner":false},{"resource_state":2,"firstname":"Alex","lastname":"M.","membership"

      List<SummaryAthlete> _listMembers = await strava.getClubMembersById('1');
      // List<SummaryAthlete> _listMembers = await strava.getClubMembersById(_club.id.toString());

      if (_listMembers[0].fault.statusCode != 200) {
        print(
            'error code getClubById  ${_club.fault.statusCode}  ${_club.fault.message}');
      } else {
        print('getClubMembersById ');
        _listMembers.forEach((member) => print(
            '${member.firstname}   ${member.lastname}  ${member.id} ${member.membership}'));
      }

      List<SummaryActivity> _listSumm =
          await strava.getClubActivitiesById(clubStravaMarseille);
      if (_listSumm[0].fault.statusCode != 200) {
        print(
            'error code getClubById  ${_club.fault.statusCode}  ${_club.fault.message}');
      } else {
        print('getClubActivitiesById ');
        _listSumm.forEach((activity) =>
            print('${activity.name}   ${activity.totalElevationGain}'));
      }

      /// You have to put an id of one activity of the logged Athlete
      /// You can find the id of one activity looking at your web
      ///  like https://www.strava.com/activities/2130215349

      // Activity 3226262796 with totalElevationGain to 0
      DetailedActivity _activity = await strava.getActivityById('3234043107');

      // Activity 2704301316 totalElevationGain 1360.1
      // DetailedActivity _activity = await strava.getActivityById('3234026164');
      if (_activity.fault.statusCode != 200) {
        print(
            'Error in getActivityById: ${_activity.fault.statusCode} - ${_activity.fault.message}');
      } else {
        print(
            'getActivityById ${_activity.name}  Total Elevation Gain ${_activity.totalElevationGain}');
      }
    }
  }

  Future<Fault> upload() async {
    print('Trying to upload');

    showDialog<String>(
        context: context,
        builder: (BuildContext context) {
          return const Center(
            child: CircularProgressIndicator(),
          );
        });

    final Fault fault = await exampleUpload(secret);
    Navigator.pop(context);
    return fault;
  }

  void deAuthorize() async {
    // need to get authorized before (valid token)
    final strava = Strava(
      true, // to get disply info in API
      secret, // Put your secret key in secret.dart file
    );
    var fault = await strava.deAuthorize();
  }

  @override
  void dispose() {
    strava.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(''),
            Text('Authentication'),
            Text('with segments Apis'),
            RaisedButton(
              key: Key('SegmentsButton'),
              child: Text('Segments'),
              // onPressed: exampleStrava,
              onPressed: exampleSeg,
            ),
            Text(''),
            Text('Authentication'),
            Text('with other Apis'),
            RaisedButton(
              key: Key('OthersButton'),
              child: Text('strava_flutter'),
              onPressed: exampleStrava,
            ),
            Text(''),
            Text(''),
            Text('Upload with authentication'),
            RaisedButton(
              key: Key('Uploadbutton'),
              child: Text('upload'),
              onPressed: upload,
            ),
            Text(''),
            Text(''),
            Text('Test insufficient permissions'),
            RaisedButton(
              key: Key('Permissionsbutton'),
              child: Text('permissions'),
              onPressed: permissions,
            ),
            Text(' '),
            Text(''),
            Text(''),
            Text('Push this button'),
            Text(
              'to revoke/DeAuthorize Strava user',
            ),
            RaisedButton(
              key: Key('DeAuthorizeButton'),
              child: Text('DeAuthorize'),
              onPressed: deAuthorize,
            ),
          ],
        ),
      ),
    );
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  strava_flutter: ^1.2.2+61

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:strava_flutter/API/Oauth.dart';
import 'package:strava_flutter/API/activities.dart';
import 'package:strava_flutter/API/athletes.dart';
import 'package:strava_flutter/API/clubs.dart';
import 'package:strava_flutter/API/constants.dart';
import 'package:strava_flutter/API/races.dart';
import 'package:strava_flutter/API/segmentEfforts.dart';
import 'package:strava_flutter/API/segments.dart';
import 'package:strava_flutter/API/upload.dart';
import 'package:strava_flutter/Models/OldsummaryActivity.dart';
import 'package:strava_flutter/Models/activity.dart';
import 'package:strava_flutter/Models/athlete.dart';
import 'package:strava_flutter/Models/club.dart';
import 'package:strava_flutter/Models/detailedAthlete.dart';
import 'package:strava_flutter/Models/fault.dart';
import 'package:strava_flutter/Models/gear.dart';
import 'package:strava_flutter/Models/runningRace.dart';
import 'package:strava_flutter/Models/segment.dart';
import 'package:strava_flutter/Models/segmentEffort.dart';
import 'package:strava_flutter/Models/stats.dart';
import 'package:strava_flutter/Models/summaryAthlete.dart';
import 'package:strava_flutter/Models/token.dart';
import 'package:strava_flutter/Models/uploadActivity.dart';
import 'package:strava_flutter/Models/zone.dart';
import 'package:strava_flutter/errorCodes.dart';
import 'package:strava_flutter/globals.dart';
import 'package:strava_flutter/strava.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
70
Health:
Code health derived from static analysis. [more]
99
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
85
Learn more about scoring.

We analyzed this package on Jul 10, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.4
  • pana: 0.13.14
  • Flutter: 1.17.5

Analysis suggestions

Package does not support Flutter platform android

Because:

  • package:strava_flutter/API/Oauth.dart that imports:
  • package:shared_preferences/shared_preferences.dart that imports:
  • package:shared_preferences_linux/shared_preferences_linux.dart that declares support for platforms: linux

Package does not support Flutter platform ios

Because:

  • package:strava_flutter/API/Oauth.dart that imports:
  • package:shared_preferences/shared_preferences.dart that imports:
  • package:shared_preferences_linux/shared_preferences_linux.dart that declares support for platforms: linux

Package does not support Flutter platform linux

Because:

  • package:strava_flutter/API/Oauth.dart that imports:
  • package:uni_links/uni_links.dart that declares support for platforms: android, ios

Package does not support Flutter platform macos

Because:

  • package:strava_flutter/API/Oauth.dart that imports:
  • package:uni_links/uni_links.dart that declares support for platforms: android, ios

Package does not support Flutter platform web

Because:

  • package:strava_flutter/API/Oauth.dart that imports:
  • package:uni_links/uni_links.dart that declares support for platforms: android, ios

Package does not support Flutter platform windows

Because:

  • package:strava_flutter/API/Oauth.dart that imports:
  • package:uni_links/uni_links.dart that declares support for platforms: android, ios

Package not compatible with SDK dart

Because:

  • strava_flutter that is a package requiring null.

Health issues and suggestions

Document public APIs. (-0.26 points)

1163 out of 1193 API elements have no dartdoc comment.Providing good documentation for libraries, classes, functions, and other API elements improves code readability and helps developers find and use your API.

Fix lib/API/segmentEfforts.dart. (-0.50 points)

Analysis of lib/API/segmentEfforts.dart reported 1 hint:

line 59 col 9: The value of the local variable '_pageNumber' isn't used.

Fix lib/Models/activity.dart. (-0.50 points)

Analysis of lib/Models/activity.dart reported 1 hint:

line 351 col 1: Prefer using /// for doc comments.

Format lib/Models/athlete.dart.

Run flutter format to format lib/Models/athlete.dart.

Format lib/Models/segmentEffort.dart.

Run flutter format to format lib/Models/segmentEffort.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
cupertino_icons ^0.1.2 0.1.3
flutter 0.0.0
http ^0.12.0+1 0.12.1
intl ^0.16.0 0.16.1
shared_preferences ^0.5.0 0.5.8
uni_links ^0.4.0 0.4.0
url_launcher ^5.4.5 5.5.0
Transitive dependencies
charcode 1.1.3
collection 1.14.12 1.14.13
file 5.2.1
flutter_web_plugins 0.0.0
http_parser 3.1.4
meta 1.1.8 1.2.2
path 1.7.0
path_provider_linux 0.0.1+2
path_provider_platform_interface 1.0.2
pedantic 1.9.0 1.9.2
platform 2.2.1
platform_detect 1.4.0
plugin_platform_interface 1.0.2
process 3.0.13
pub_semver 1.4.4
shared_preferences_linux 0.0.2+1
shared_preferences_macos 0.0.1+10
shared_preferences_platform_interface 1.0.4
shared_preferences_web 0.1.2+7
sky_engine 0.0.99
source_span 1.7.0
string_scanner 1.0.5
term_glyph 1.1.0
typed_data 1.1.6 1.2.0
url_launcher_linux 0.0.1+1
url_launcher_macos 0.0.1+7
url_launcher_platform_interface 1.0.7
url_launcher_web 0.1.2
vector_math 2.0.8 2.1.0-nullsafety
xdg_directories 0.1.0
Dev dependencies
flutter_test