GitHub Manager

Matt Jones | April 10, 2021

This Project uses the new Flutter 2 SDK and Graphql to connect to the GitHub Graphql API.

Both are new topics for me, so this was a big learning experience.



Flutter 2 just came out on March 3, 2021, so it's very new.

There are a few notable changes, such as being able to develop on more platforms like Windows and MacOS. Flutter 2 is also now Null safe, which can be frustrating at first but isn't hard to learn. There a lot to say about this so please check out my blog post if you'd like to learn more.


Graphql is a query and manipulation language for APIs.

This uses a single endpoint to access all the data, rather than traditional APIs that have many complex paths/urls. It also allows you to request what you want rather than having to use preformed calls. This makes it very simple and easy to use. I wanted to try this and learn how to use Graphql, since it's new makes APIs super easy, and I like easy. So far, I like it a lot and will be using it in the future.

You can check out the GitHub API explorer here.


I’m going to be building this on windows since that's what I'm currently using. If you’d like to try out the end product, follow the link below and download the package. Leave the application in the folder and click the githubmanager.exe.

Click Me!


This will application is split up into 4 functions:

1. Uses OAuth2 to connect to your personal GitHub

2. Shows your Repositories / Pull request / Assigned issues / Gists with links

3. Uses a theme changer to change to light / dark / system color themes

4. Add a Gist link for ease of use.


OAuth2

The first and the most important is setting up OAuth2 with GitHub. This is well documented on GitHub's site and easy to set up. 


First, we set up our own OAuth App on GitHub and we'll set the using localhost as the callback url. The idea here is to set up a temporary server that will send the request to the authorization endpoint. We’ll be making an oauth2.client and if this request is authorized, we will update the client’s state. We pass this client to our main page for our queries to the API.


class _GithubLoginState extends State<GithubLoginWidget> {
  HttpServer? _redirectServer;
  oauth2.Client? _client;

  @override
  Widget build(BuildContext context) {
    final client = _client;
    if (client != null) {
      return widget.builder(context, client);
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('Github Login'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            await _redirectServer?.close();
            // Bind to an ephemeral port on localhost
            _redirectServer = await HttpServer.bind('localhost', 0);
            var authenticatedHttpClient = await _getOAuth2Client(
                Uri.parse('http://localhost:${_redirectServer!.port}/auth'));
            setState(() {
              _client = authenticatedHttpClient;
            });
          },
          child: const Text('Login to Github'),
        ),
      ),
    );
  }
}


Show Data

Now we get to use Graphql. I’m taking advantage of the flutter build_runner by setting up a build.yaml file that will build pre-set requests to the API. To do this we have to make queries.graphql file that we can set our queries up in. Using GitHub Explorer you can customize queries and see the results right away. 


targets:
  $default:
    builders:
      gql_build|ast_builder:
        enabled: true
      gql_build|req_builder:
        enabled: true
        options:
          schema: githubmanager|lib/graphql/github-schema.graphql
      gql_build|serializer_builder:
        enabled: true
        options:
          schema: githubmanager|lib/graphql/github-schema.graphql
      gql_build|schema_builder:
        enabled: true
      gql_build|data_builder:
        enabled: true
        options:
          schema: githubmanager|lib/graphql/github-schema.graphql
      gql_build|var_builder:
        enabled: true
        options:
          schema: githubmanager|lib/graphql/github-schema.graphql



I’m using Android studio with the JS GraphQL plugin that makes this whole process super easy. You make one config file to set the endpoint and authorization token and it will recognize and download a schema of the whole API for it to reference for quick queries and checking for autocompletion. 


query ViewerDetail {
    viewer {
        login
    }
}

query GetGists($count: Int!) {
    viewer {
        gists(first: $count, privacy: ALL) {
            nodes {
                files {
                    name
                    encodedName
                    size
                    text(truncate: 50)
                }
                url
            }
        }
    }
}


After that's all done we run:

$ flutter pub run build_runner build --delete-conflicting-outputs

And a ton of premade code has been generated. Using the premade code we can start to get data from the API and present it on the application.


class GistsList extends StatefulWidget {
  const GistsList({required this.link});
  final Link link;
  @override
  _GistsListState createState() => _GistsListState(link: link);
}

class _GistsListState extends State<GistsList> {
  _GistsListState({required Link link}) {
    _gists = _retrieveGists(link);
  }
  late Future<List<GGetGistsData_viewer_gists_nodes>> _gists;
  Future<List<GGetGistsData_viewer_gists_nodes>> _retrieveGists(Link link) async {
    final req = GGetGists((b) => b..vars.count = 10);
    final result = await link
        .request(Request(
      operation: req.operation,
      variables: req.vars.toJson(),
    )).first;

    final errors = result.errors;
    if (errors != null && errors.isNotEmpty) {
      throw QueryException(errors);
    }
    return GGetGistsData.fromJson(result.data!)!
        .viewer.gists.nodes!
        .asList();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<List<GGetGistsData_viewer_gists_nodes>>(
      future: _gists,
        builder: (context, snapshot) {
          if (snapshot.hasError) {
            return Center(child: Text('${snapshot.error}'));
          }
          if (!snapshot.hasData) {
            return Center(child: CircularProgressIndicator());
          }
          var gistList = snapshot.data;
          return ListView.builder(
              itemBuilder: (context, index) {
            var gists = gistList![index];
            var file = gists.files;
            return ListTile(
              title: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                // crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Text('${file!.first.name}', textAlign: TextAlign.justify),
                  Text('${file.first.size}Mb', textAlign: TextAlign.center,),
                ],
              ),
              subtitle: Text(file.first.text ?? 'No description'),
              onTap: () => _launchUrl(context, gists.url.value),
            );
          },
          itemCount: gistList!.length,
          );
        },
    );
  }
}


Theme Changer


For the theme changing, I tried a few ways of doing this but I found it wasn't working for the desktop version. Eventually, I found this flex_color_scheme package and it worked great. There are tons of preset themes to pick from or you can make your own. I tried both but ended up with one of their preset designs. To add it into the project I started by adding a FlexColorScheme as the theme for my MaterialApp. You can preset a lot of different customization from there on what schemes you would like to use.


return MaterialApp(
    debugShowCheckedModeBanner: false,
    title: 'GitHub Manager',
    theme: FlexColorScheme.light(
      colors: FlexColor.schemes[FlexScheme.money]!.light,
      visualDensity: FlexColorScheme.comfortablePlatformDensity,
      surfaceStyle: FlexSurface.strong,
    ).toTheme,
    darkTheme: FlexColorScheme.dark(
      colors: FlexColor.schemes[FlexScheme.money]!.dark,
      visualDensity: FlexColorScheme.comfortablePlatformDensity,
      surfaceStyle: FlexSurface.strong,
    ).toTheme,
    themeMode: themeMode,
    home: HomePage(
      title: 'Github Manager',
      themeMode: themeMode,
      onThemeModeChanged: (ThemeMode mode) {
        setState(() {
          themeMode = mode;
        });
      },
      flexSchemeData: customFlexScheme,
    )
);


You can change the theme context within the application by adding a FlexThemeModeSwitch. When you first do this it's 3 large boxes so I changed the height and width to 0 and found a look I liked.


Card(
  borderOnForeground: true,
  elevation: 3,
  child: FlexThemeModeSwitch(
    optionButtonMargin: EdgeInsets.all(10),
    labelAbove: false,
    height: 0,
    width: 0,
    themeMode: widget.themeMode,
    onThemeModeChanged: widget.onThemeModeChanged,
    flexSchemeData: widget.flexSchemeData,
  ),
),



GistLink

At first, I was hoping to make new Gists through mutations in the API but I found out this is not possible through the Graphql version. There certainly was another workaround but I didn't want to go to that much trouble for a function that will probably be barely used. Another easy solution was adding a link to a floating button.


floatingActionButton: FloatingActionButton(
  onPressed: () => launch('https://gist.github.com/'),
  child: Icon(Octicons.gist),


That's it! This was a complete oversimplification of the project so to see it in more detail check out the GitHub page.


Demo



Check out the Blog Post

Made with Django and ❤️