How to build a simple CRUD with GraphQL and Flutter

How to build a simple CRUD with GraphQL and Flutter

In this Flutter tutorial, I am going to show you how to build a simple CRUD with Flutter (client-side) and GraphQL (server-side)

Flutter is an SDK (Software development kit) to build applications for Android and IOs with a single codebase. On the other hand, GraphQL is a query language that allows us to create queries that will be executing on the server. In this tutorial, I am going to show you how to build a simple CRUD with Flutter (client-side) and GraphQL (server-side). Let’s start

Once we have created our Flutter project we need to import some dependencies in our pubspec.yaml file in order to use GraphQL and HTTP with our API. It’s essential to notice that the API was created on NodeJS, so for this tutorial I’m not going to show how to build it.

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^2.0.1
  http: ^0.12.0+2

Then, we are going to create a file called graphQLConf.dart, this file will contain the basic configuration to use GraphQL like our link to the API and the cache configuration. So, we create a class called GraphQLConfiguration, inside this, there are three main parts. The first one is the link to the API, the second one is the ValueNotifier which will notify its listener when any kind of value changed, and finally, the method clientToQuery() will let us make queries and mutation in our application.

import "package:flutter/material.dart";
import "package:graphql_flutter/graphql_flutter.dart";

class GraphQLConfiguration { static HttpLink httpLink = HttpLink( uri: "https://examplegraphql.herokuapp.com/graphql", );

ValueNotifier<GraphQLClient> client = ValueNotifier( GraphQLClient( link: httpLink, cache: OptimisticCache(dataIdFromObject: typenameDataIdFromObject), ), );

GraphQLClient clientToQuery() { return GraphQLClient( cache: OptimisticCache(dataIdFromObject: typenameDataIdFromObject), link: httpLink, ); } }

In our main.dart file we will wrap the MaterialApp Widget with the previous configuration as follow:

import 'package:flutter/material.dart';
import "package:graphql_flutter/graphql_flutter.dart";
import "services/graphqlConf.dart";
import "package:example/components/principal.dart";

GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration();

void main() => runApp( GraphQLProvider( client: graphQLConfiguration.client, child: CacheProvider(child: MyApp()), ), );

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

As you can saw we called the GraphQLObject from the graphQLConf.dart file and then we wrap it into the main method. But we don’t know how the Principal Widget use there is made. Basically, during this example we are going to work with a class Person, this class will contain basic information such as the id, the name, the last name, and the age. The application lets us add a person, edit a person, see all the persons added and finally delete a person (a basic CRUD). So let’s create a class person.

class Person {
  Person(this.id, this.name, this.lastName, this.age);

final String id; final String name; final String lastName; final int age;

getId() => this.id;

getName() => this.name;

getLastName() => this.lastName;

getAge() => this.age; }

As you can see is a simple class Person with the id, name, lastName and age attributes, also we had a constructor and the get methods to access its values. Then we need to do our queries to the API. GraphQL provides two forms:

  • Query: It allows us to access data and read it.
  • Mutation: It allows to modify our data. Add, edit or delete something.

For this, let’s create a class that will contain all the queries and mutation that we are going to use. (Note: Dart uses a notation with three quotes to make the queries).

class QueryMutation {
  String addPerson(String id, String name, String lastName, int age) {
    return """
      mutation{
          addPerson(id: "$id", name: "$name", lastName: "$lastName", age: $age){
            id
            name
            lastName
            age
          }
      }
    """;
  }

String getAll(){ return """ { persons{ id name lastName age } } """; }

String deletePerson(id){ return """ mutation{ deletePerson(id: "$id"){ id } } """; }

String editPerson(String id, String name, String lastName, int age){ return """ mutation{ editPerson(id: "$id", name: "$name", lastName: "$lastName", age: $age){ name lastName } }
"""; } }

Then, our Principal widget will use an AlertDialog to do the insert, read and delete operations, and then it will be called from our Widget. So let’s create the AlertDialog widget.

import "package:flutter/material.dart";
import "../services/graphqlConf.dart";
import "../services/queryMutation.dart";
import "package:graphql_flutter/graphql_flutter.dart";
import "package:example/components/person.dart";

class AlertDialogWindow extends StatefulWidget { final Person person; final bool isAdd;

AlertDialogWindow({Key key, this.person, this.isAdd}) : super(key: key);

@override State<StatefulWidget> createState() => _AlertDialogWindow(this.person, this.isAdd); }

class _AlertDialogWindow extends State<AlertDialogWindow> { TextEditingController txtId = TextEditingController(); TextEditingController txtName = TextEditingController(); TextEditingController txtLastName = TextEditingController(); TextEditingController txtAge = TextEditingController(); GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration(); QueryMutation addMutation = QueryMutation();

final Person person; final bool isAdd;

_AlertDialogWindow(this.person, this.isAdd);

@override void initState() { super.initState(); if (!this.isAdd) { txtId.text = person.getId(); txtName.text = person.getName(); txtLastName.text = person.getLastName(); txtAge.text = person.getAge().toString(); } }

@override Widget build(BuildContext context) { // TODO: implement build return AlertDialog( title: Text(this.isAdd ? "Add" : "Edit or Delete"), content: Container( child: SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width, ), child: Stack( children: <Widget>[ Container( child: TextField( maxLength: 5, controller: txtId, enabled: this.isAdd, decoration: InputDecoration( icon: Icon(Icons.perm_identity), labelText: "ID", ), ), ), Container( padding: EdgeInsets.only(top: 80.0), child: TextField( maxLength: 40, controller: txtName, decoration: InputDecoration( icon: Icon(Icons.text_format), labelText: "Name", ), ), ), Container( padding: EdgeInsets.only(top: 160.0), child: TextField( maxLength: 40, controller: txtLastName, decoration: InputDecoration( icon: Icon(Icons.text_rotate_vertical), labelText: "Last name", ), ), ), Container( padding: EdgeInsets.only(top: 240.0), child: TextField( maxLength: 2, controller: txtAge, keyboardType: TextInputType.number, decoration: InputDecoration( labelText: "Age", icon: Icon(Icons.calendar_today)), ), ), ], ), ), ), ), actions: <Widget>[ FlatButton( child: Text("Close"), onPressed: () => Navigator.of(context).pop(), ), !this.isAdd ? FlatButton( child: Text("Delete"), onPressed: () async { GraphQLClient _client = graphQLConfiguration.clientToQuery(); QueryResult result = await _client.mutate( MutationOptions( document: addMutation.deletePerson(txtId.text), ), ); if (!result.hasErrors) Navigator.of(context).pop(); }, ) : null, FlatButton( child: Text(this.isAdd ? "Add" : "Edit"), onPressed: () async { if (txtId.text.isNotEmpty && txtName.text.isNotEmpty && txtLastName.text.isNotEmpty && txtAge.text.isNotEmpty) { if (this.isAdd) { GraphQLClient _client = graphQLConfiguration.clientToQuery(); QueryResult result = await _client.mutate( MutationOptions( document: addMutation.addPerson( txtId.text, txtName.text, txtLastName.text, int.parse(txtAge.text), ), ), ); if (!result.hasErrors) { txtId.clear(); txtName.clear(); txtLastName.clear(); txtAge.clear(); Navigator.of(context).pop(); } } else { GraphQLClient _client = graphQLConfiguration.clientToQuery(); QueryResult result = await _client.mutate( MutationOptions( document: addMutation.editPerson( txtId.text, txtName.text, txtLastName.text, int.parse(txtAge.text), ), ), ); if (!result.hasErrors) { txtId.clear(); txtName.clear(); txtLastName.clear(); txtAge.clear(); Navigator.of(context).pop(); } } } }, ) ], ); } }

In the _AlertDialog we used all the mutations created to add, edit and delete a person. The process is as follows:

  • We called our GraphQl configuration from GraphQLConfiguration: graphQLConfiguration = GraphQLConfiguration();
  • We get the mutations from the class we have created: QueryMutation addMutation = QueryMutation();
  • We create our GraphQL client to connect to our backend: GraphQLClient _client = graphQLConfiguration.clientToQuery();
  • Finally we do our mutation with the required parameters.

Once we have done the previous steps we check is our result is correct, and if that is true, just return to the main page and clear all the TextFields. Basically, all the queries and mutations done during the example are based under the same principle: declare your variables, do your query or mutation and finally check if that has errors.

Finally, we have our whole Principal widget, and there is something more we need to remark. Inside our initState() we are going to call all the data saved in our backend, and to do that we use our function fillList(), and basically it will fill a listPerson with all the data stored, to access to it, we do result.data[“name our query or mutation”][index][“name of the variable”] and that is it.

import "package:flutter/material.dart";
import "package:example/components/alertDialogs.dart";
import "package:graphql_flutter/graphql_flutter.dart";
import "package:example/components/person.dart";
import "../services/graphqlConf.dart";
import "../services/queryMutation.dart";

class Principal extends StatefulWidget { @override State<StatefulWidget> createState() => _Principal(); }

class _Principal extends State<Principal> { List<Person> listPerson = List<Person>(); GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration();

void fillList() async { QueryMutation queryMutation = QueryMutation(); GraphQLClient _client = graphQLConfiguration.clientToQuery(); QueryResult result = await _client.query( QueryOptions( document: queryMutation.getAll(), ), ); if (!result.hasErrors) { for (var i = 0; i < result.data["persons"].length; i++) { setState(() { listPerson.add( Person( result.data["persons"][i]["id"], result.data["persons"][i]["name"], result.data["persons"][i]["lastName"], result.data["persons"][i]["age"], ), ); }); } } }

@override void initState() { super.initState(); fillList(); }

void _addPerson(context) { showDialog( context: context, builder: (BuildContext context) { AlertDialogWindow alertDialogWindow = new AlertDialogWindow(isAdd: true); return alertDialogWindow; }, ).whenComplete(() { listPerson.clear(); fillList(); }); }

void _editDeletePerson(context, Person person) { showDialog( context: context, builder: (BuildContext context) { AlertDialogWindow alertDialogWindow = new AlertDialogWindow(isAdd: false, person: person); return alertDialogWindow; }, ).whenComplete(() { listPerson.clear(); fillList(); }); }

@override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: Text("Example"), actions: <Widget>[ IconButton( icon: Icon(Icons.add_circle_outline), onPressed: () => _addPerson(context), tooltip: "Insert new person", ), ], ), body: Stack( children: <Widget>[ Container( width: MediaQuery.of(context).size.width, child: Text( "Person", textAlign: TextAlign.center, style: TextStyle(fontSize: 30.0), ), ), Container( padding: EdgeInsets.only(top: 50.0), child: ListView.builder( itemCount: listPerson.length, itemBuilder: (context, index) { return ListTile( selected: listPerson == null ? false : true, title: Text( "${listPerson[index].getName()}", ), onTap: () { _editDeletePerson(context, listPerson[index]); }, ); }, ), ), ], ), ); } }

Our result!


flutter graphql ios mobile-apps database

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Google's Flutter 1.20 stable announced with new features - Navoki

Google has announced new flutter 1.20 stable with many improvements, and features, enabling flutter for Desktop and Web

How To Succeed In Mobile App Wireframe Design?

This article covers everything about mobile app wireframe design: what to do and what not, tools used in designing a mobile or web app wireframe, and more.

How long does it take to develop/build an app?

This article covers A-Z about the mobile and web app development process and answers your question on how long does it take to develop/build an app.

Top 25 Flutter Mobile App Templates in 2020

Flutter has been booming worldwide from the past few years. While there are many popular mobile app development technologies out there, Flutter has managed to leave its mark in the mobile application development world. In this article, we’ve curated the best Flutter app templates available on the market as of July 2020.

Best Mobile App Development Company | Android and iOS Apps

iPrism Tech is a one of the best and offshore mobile app development company in India, Saudi Arabia and USA. We are a major providers of android, iphone and ipad mobile app development services at economical prices.