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.
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
and add the graphql package as a dependency in the pubspec.yaml file
dependencies:
graphql: ^5.0.0
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(),
);
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
}
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"
},
]
}
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
}
}
Response returned from the GraphQL endpoint:
"data": {
"country": {
"name": "Nigeria",
"capital": "Abuja",
"code": "NG",
"native": "Nigeria",
"currency": "NGN",
"phone": "234",
"emoji": "🇳🇬"
}
}
}
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"];
}
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
}
}
''';
// 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
}
}
''';
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;
}
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;
}
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';
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();
}
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();
}
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(),
);
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(),
],
),
),
),
],
);
}
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);
},
),
],
),
),
);
}
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.
Top comments (0)