Implementing Instagram's Basic Display API in Flutter

Flutter Apr 30, 2020

Recently, I was trying to get some basic info from my users' Instagram accounts for a Flutter app and check if they possessed a verified account.

I also wanted users to be able to sign up/in using Instagram, which the new APIs do not support, unfortunately- instead, you're now required to use Facebook Login, so I abandoned that idea.

Anyways, after some googling & trial and error (because of lacking documentation), I managed to get it working. Here's how.

If you want to jump straight into the code, here's the Github repo.
NOTE 🚨: This guide has not been tested on iOS yet. It likely won't work right now, but as I'm also developing for iOS users, I will update with the additional steps soon. I did some quick googling and it seems I'll need to start with universal links.

1) Register an Instagram app using your Facebook developer account

First, we create a new app on Facebook for Developers.

Now we hit Set Up Instagram and go to the Basic Display tab.

Here we enter a redirect URL. As the name suggests, Instagram will redirect to this URL after the user has logged in and authorized our app- but in our case, we need to be redirected back to our app and not to some website.

To achieve this, we'll enter a (very) unique URL and then create a deep link so that our app is opened. You don't need actually need to have a site at this URL, so anything goes, as long as it starts with https:// Β and doesn't trip FB's malicious URL detection. Copy your redirect URL into the other fields as well, then hit Save Changes.

Copy the Instagram App ID and Instagram App Secret and make note of it- we'll need it soon.

Instagram App Basic Display Configuration Tab
Note down the App ID and App Secret.

2) Invite Instagram Testers

Scroll down and send invites to the Instagram accounts you will use to authenticate/test the app while in Sandbox mode.

3) Define a custom URL scheme

Next, we define a custom URL scheme by adding an intent filter inside the <application> block of our Android Manifest (android\app\src\main\AndroidManifest.xml) file as shown.

Don't forget to replace the link in android:host property with the redirect URI we used earlier. Omit the https:// prefix, since we've already defined the scheme with the previous lines.

Note: We'll also need to do this for iOS, but I haven't got to that yet. I'll likely have to use the universal links package. I'll update this post as soon as I figure it out.

4) Write Flutter code!

First, let's add the package dependencies that we'll be using to our pubspec.yaml file. simple_auth & simple_auth_flutter will be used to grab the access token, while dio will be used to send our GET requests to the API.

Now, let's create a new file called constants.dart inside our lib folder, and put in our client app ID and client secret from Step 1. Note the slash at the end of our redirect URL- You'll get a 400 status error without it.

class Constants {
  static const igClientId = "YOURCLIENTIDHERE";
  static const igClientSecret = "YOURCLIENTSECRETHERE";
  static const igRedirectURL = "https://YOURLINKHERE.com/";
}
lib/constants.dart

Next, in our main.dart file, let's write the code for the UI. Nothing too fancy, just a page with a column containing three widgets: a Text widget which will display the values we store in _userData, a FlatButton which will show execute our logic (contained in _loginAndGetData()) when pressed, and a Text widget that will show error messages if any.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'IG Flutter',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  HomePage({Key key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  String _errorMsg;
  Map _userData;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Instagram Basic Display API Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Visibility(
              visible: _userData != null,
              child: Text("We'll display values here"), // TODO pass values string here
                textAlign: TextAlign.center,
              ),
              replacement:
                  Text("Click the button below to get Instagram Login."),
            ),
            FlatButton.icon(
              icon: Icon(Icons.input),
              label: Text(
                "Get Profile Data",
              ),
              onPressed: _loginAndGetData,
              color: Colors.pink.shade400,
            ),
            if (_errorMsg != null) Text("Error occured: $_errorMsg"),
          ],
        ),
      ),
    );
  }

  Future<void> _loginAndGetData() async {
    // TODO implement function
  }
}
lib/main.dart
The UI for our Basic Display API Flutter API
The UI

Okay, now we'll import our packages and the constants file:

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:instagram_basic_display_api_flutter/constants.dart';
import 'package:simple_auth/simple_auth.dart' as simpleAuth;
import 'package:simple_auth_flutter/simple_auth_flutter.dart';

...
lib/main.dart

Then, let's initialise our Instagram provider. Notice the scopes we've provided.

...

class _HomePageState extends State<HomePage> {
  String _errorMsg;
  Map _userData;

  final simpleAuth.InstagramApi _igApi = simpleAuth.InstagramApi(
    "instagram",
    Constants.igClientId,
    Constants.igClientSecret,
    Constants.igRedirectURL,
    scopes: [
      'user_profile', // For getting username, account type, etc.
      'user_media', // For accessing media count & data like posts, videos etc.
    ],
  );
  
...

Almost there! Let's implement our logic.

  ...
  
  Future<void> _loginAndGetData() async {
    _igApi.authenticate().then(
      (simpleAuth.Account _user) async {
        simpleAuth.OAuthAccount user = _user;

        var igUserResponse =
            await Dio(BaseOptions(baseUrl: 'https://graph.instagram.com')).get(
          '/me',
          queryParameters: {
            // Get the fields you need.
            // https://developers.facebook.com/docs/instagram-basic-display-api/reference/user
            "fields": "username,id,account_type,media_count",
            "access_token": user.token,
          },
        );

        setState(() {
          _userData = igUserResponse.data;
          _errorMsg = null;
        });
      },
    ).catchError(
      (Object e) {
        setState(() => _errorMsg = e.toString());
      },
    );
  }

...

First, we get the user to log in to Instagram using authenticate(), which will return an Account object with an access token. Other providers will also supply user data which you would find in _user.userData, but not Instagram.

For IG, we need to query their user endpoint. We do that by sending a GET request to https://graph.instagram.com/me along with the user's access token and the fields we need (username, id, account type, etc). This will return the currently signed-in user's details as a JSON object. At the end, we call setState() to rebuild the concerned widgets so that we can see the data on our UI.

Ok, let's hit login and-

Error in Flutter Basic Display API app
Woops, error!

Welps, what happened here? We forgot to initialise our simple_auth plugin before we used it! Easy to miss that in the README. Let's do that in initState():

  @override
  void initState() {
    super.initState();
    SimpleAuthFlutter.init(context);
  }

Okay, we should be all good now! Here's the our complete main.dart file along with some changes to the Text widget so we can see the values:

Aaaand done! Now, if we wanted to get the list of media of this user, we'd have to make a second GET request at the https://api.instagram.com/{user-id}/media?access_token={access-token} and supply the fields we need.

Of course, if you want to use this functionality in production, you'll need to submit your App Review on developers.facebook.com.

Bonus: Get extra data on our user JSON

Once we have the username, we can get some more information that Instagram does not expose in its API, like the verification status. Here's how:

  Future<bool> _getMoreData(String username) {
    return Dio().get('https://instagram.com/$username/?__a=1').then(
      (Response response) {
        var userData = response.data['graphql']['user'];

        return userData;
      },
    );
  }
  
  // For verification status:
  bool isVerified = (await _getMoreData)['is_verified'];

Hope this helps. If you'd like to show your appreciation, you can support me on Buy Me a Coffee. If there's anything unclear, I'm here to help in the comments or at my email. See you around!

Tags

Kifah Meeran (MaskyS)

Hi, I'm Kifah, a creator from Mauritius. I read code, books, and people. I'm also passionate about cogsci, design, programming, and life in general. More about me on https://maskys.com

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.