DEV Community

Cover image for How to Build a Flutter App with GraphQL
David Adewoyin
David Adewoyin

Posted on

How to Build a Flutter App with GraphQL

What are we building?

We will be building a flutter app that fetches a list of countries using the Countries GraphQL API and displays brief information about the country.

Prerequisite.

This article assumes the user is familiar with Flutter and have a basic understanding of GraphQL.

Countries GraphQL API

The Countries GraphQL API is a public GraphQL API for getting information about countries, continents, and languages. To understand how a given GraphQL API works, It is best to first interact with the endpoint using a test playground which enables you to run test queries, for this tutorial a playground for interacting with countries api can be found here .

Example of a running a query in the playground.
Screenshot from 2021-11-18 19-52-12-1000x500.png

How to use GraphQL with flutter

To use GraphQL with flutter, we use the graphql package, which provides the most popular GraphQL client for dart.

Creating the country directory app

We create a new flutter project called country_directory

flutter create country_directory
Enter fullscreen mode Exit fullscreen mode

and add the graphql package as a dependency in the pubspec.yaml file

dependencies:
  graphql: ^5.0.0

Enter fullscreen mode Exit fullscreen mode

Basic Usage

To connect to a GraphQL Server, we first need to create a GraphQLClient. A GraphQLClient requires both a cache and a link to be initialized.

Creating a file called** api.dart** to define our apis, we add the following lines of code which set the URL of the GraphQL endpoint and create an instance of the GraphQLClient needed to interact with the endpoint. HttpLink is used to customize the network access such as adding access token for authentication but since the countries apis is free authentication is not needed.

import 'package:graphql/client.dart';


const baseURL = "https://countries.trevorblades.com/";

final _httpLink = HttpLink(
  baseURL,
);

final GraphQLClient client = GraphQLClient(
  link: _httpLink,
  cache: GraphQLCache(),
);

Enter fullscreen mode Exit fullscreen mode

Testing Query with the Playground

Our application consists of initially making a request to the endpoint in other to fetch a list of country name and their country code from the API and providing a dropdown that enables the user to choose a country in which another query will be sent to the endpoint in other to fetch details of the country.
The initial query sent to the endpoint is shown below;

query {
  countries {
    code
    name
 }
Enter fullscreen mode Exit fullscreen mode

And the endpoint returns a response which contains a list of countries with the country code and name which part is shown below.

"data": {
    "countries": [
      {
        "code": "AD",
        "name": "Andorra"
      },
      {
        "code": "AE",
        "name": "United Arab Emirates"
      },
      {
        "code": "AF",
        "name": "Afghanistan"
      },

]
}
Enter fullscreen mode Exit fullscreen mode

The next step is to make a query request to the endpoint for retrieving the data of a particular country. The example below shows querying the endpoint for data about Nigeria with the country code of "NG"

query {
  country(code:"NG"){
     name
    capital
    code
    native
    currency
    phone
    emoji
    }
}

Enter fullscreen mode Exit fullscreen mode

Response returned from the GraphQL endpoint:


  "data": {
    "country": {
      "name": "Nigeria",
      "capital": "Abuja",
      "code": "NG",
      "native": "Nigeria",
      "currency": "NGN",
      "phone": "234",
      "emoji": "🇳🇬"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Examining the responses from the two queries.We create a new file model.dart and create a class in the file called Country with the following content.

class Country {
  String code;
  String name;
  String? capital;
  String? currency;
  String? native;
  String? phone;
  String? emoji;

  Country.fromJson(Map<String, dynamic> json)
      : code = json["code"],
        name = json["name"],
        capital = json["capital"],
        currency = json["currency"],
        native = json["native"],
        phone = json["phone"],
        emoji = json["emoji"];
}
Enter fullscreen mode Exit fullscreen mode

Notice apart from the code and name field in the country class all other fields are nullable since the first request to the endpoint only returns the country code and name which is needed to create a country instance.We also create a factory method for easily creating an instance of the country from the json response.

In the api.dart file we create two constant that represent the two queries string to be sent to the GraphQL endpoint.

const _getAllCountries = r'''
query {
  countries{
    code
    name
    }
  }
''';
Enter fullscreen mode Exit fullscreen mode

// parameters to be supplied to the query is firstly defined in the query head with the datatype, since we need to supply the country code in other to fetch details of a particular country we add the code parameter with the ID datatype as shown below

const _getCountry = r'''
query getCountry($code:ID!){
  country(code:$code){
    name
    capital
    code
    native
    currency
    phone
    emoji

  }
}

''';
Enter fullscreen mode Exit fullscreen mode

Fetching the list of countries

import the country model we earlier created as we will be using it to store the details of the country we get as the responses from the api

``import 'package:country_directory/model.dart';

We create a function getAllCountries which returns a list of countries as shown below:
//returns a list of countries names with the country code



Future<List<Country>> getAllCountries() async {
  var result = await client.query(
    QueryOptions(
//gql is used to parse the _getAllCountries string constant we previously created into a format or document that the GraphQL client understand. 
      document: gql(_getAllCountries),
    ),
  );

  var json = result.data!["countries"];
  List<Country> countries = [];
  for (var res in json) {
    var country = Country.fromJson(res);
    countries.add(country);
  }
  return countries;
}


Enter fullscreen mode Exit fullscreen mode

We also create a second function for returning the data associated with a particular country. The function takes in a a country code and use that to fetch the country data.



// returns a country with the given country code
Future<Country> getCountry(String code) async {
  var result = await client.query(
    QueryOptions(
      document: gql(_getCountry),
      variables: {
        "code": code,
      },
    ),
  );

  var json = result.data!["country"];


  var country = Country.fromJson(json);
  return country;
}


Enter fullscreen mode Exit fullscreen mode

Creating the view

Importing the the two files we created in the main.dart



import 'package:country_directory/api.dart';
import 'package:country_directory/model.dart';


Enter fullscreen mode Exit fullscreen mode

we create a stateful widget called HomePage in the main.dart file which we contains the view of our program
We add the following lines of code to our main.dart.




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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<Country> countries = [];
  Country? selectedCountry;
// the future is set to getAllCountries since we need to initially get the list of all countries first.
  Future<List<Country>> future = getAllCountries();

}



Enter fullscreen mode Exit fullscreen mode

Since we will be using a dropdown to select a country, we create a function for building the items of the dropdown



List<DropdownMenuItem<Country>> buildDropDownItem(List<Country> countries) {
    return countries
        .map((country) => DropdownMenuItem<Country>(
              child: Text(country.name),
              value: country,
            ))
        .toList();
  }


Enter fullscreen mode Exit fullscreen mode

We also create a pickCountriesWidget function for picking a particular country from the list of country




  Widget pickCountriesWidget(
      BuildContext context, AsyncSnapshot<List<Country>> snapshot) {
    var countries = snapshot.data;

    if (snapshot.connectionState == ConnectionState.done) {
      return Container(
        padding: const EdgeInsets.symmetric(horizontal: 10),
        child: DropdownButtonFormField(
          decoration: const InputDecoration(
            labelText: "Choose Country",
            border: OutlineInputBorder(),
          ),
          items: buildDropDownItem(countries!),
          value: selectedCountry,
          onChanged: (Country? country) {
            setState(() {
              selectedCountry = country;
            });
          },
        ),
      );
    }
    return const Center(
      child: CircularProgressIndicator(),
    );


Enter fullscreen mode Exit fullscreen mode

When a country is selected from the dropdown we make sent a query to the api for fetching the details of the country.

The following lines create a widget for displaying the details of the country.



Widget countryDetailsWidget(BuildContext context, AsyncSnapshot snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const Padding(
        padding: EdgeInsets.only(top: 20),
        child: Center(
          child: CircularProgressIndicator(),
        ),
      );
    }
    if (snapshot.hasError) {
      return const Center(
        child: Text("Unable to fetch country data"),
      );
    }
    Country country = snapshot.data;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Padding(
          padding: EdgeInsets.symmetric(horizontal: 10),
          child: Text(
            "Country Info",
            style: TextStyle(fontWeight: FontWeight.bold),
          ),
        ),
        const SizedBox(height: 10),
        Card(
          color: Colors.grey.shade50,
          child: Padding(
            padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
            child: Row(
              children: [
                Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: const [
                    Text("Name"),
                    Text("Capital"),
                    Text("Country code"),
                    Text("Native"),
                    Text("Currency"),
                    Text("Phone Code"),
                    Text("Emoji"),
                  ],
                ),
                const Spacer(flex: 3),
                Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(": ${country.name}",
                        style: const TextStyle(fontWeight: FontWeight.bold)),
                    Text(": ${country.capital}",
                        style: const TextStyle(fontWeight: FontWeight.bold)),
                    Text(": ${country.code}",
                        style: const TextStyle(fontWeight: FontWeight.bold)),
                    Text(": ${country.native}",
                        style: const TextStyle(fontWeight: FontWeight.bold)),
                    Text(": ${country.currency}",
                        style: const TextStyle(fontWeight: FontWeight.bold)),
                    Text(": ${country.phone!}",
                        style: const TextStyle(fontWeight: FontWeight.bold)),
                    Text(": ${country.emoji}",
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                        )),
                  ],
                ),
                const Spacer(),
              ],
            ),
          ),
        ),
      ],
    );
  }


Enter fullscreen mode Exit fullscreen mode

Putting everything together in the build method



 @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        backgroundColor: Colors.black,
        title: const Text("Country Directory"),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 15),
        child: ListView(
          children: [
            const SizedBox(height: 50),
            FutureBuilder<List<Country>>(
              future: future,
              builder: (context, snapshot) {
                return pickCountriesWidget(context, snapshot);
              },
            ),
            const SizedBox(height: 20),
            if (selectedCountry != null)
              FutureBuilder<Country>(
                future: getCountry(selectedCountry!.code),
                builder: (context, snapshot) {
                  return countryDetailsWidget(context, snapshot);
                },
              ),
          ],
        ),
      ),
    );
  }


Enter fullscreen mode Exit fullscreen mode

The link to the application is at github . The article shows how to integrate GraphQL with flutter.Further articles will shows how to do mutations and subscriptions but the fundamentals of operation is the same.

Discussion (0)