Flutter CRUD Application using PHP REST API

Geno Tech
App Dev Community
Published in
8 min readFeb 22, 2021

--

Flutter Knowledge sharing #14

Welcome to another Flutter knowledge sharing story. As a Flutter beginner, We all know that We should learn about fundamental usages which applying frequently in a mobile application. So far, We have done tutorials of those essentials. This is another step to create an API using PHP and MySQL. I hope this will really helpful. With this one, you can learn so much of what you do not know. I have written about what are the CRUD operations and how they implement with SQLite and Firebase. This story will describe how to perform CRUD operations with PHP and MySQL. I repeat that this is a very useful application.

What are the CRUD operations?

The basic four operations which we can perform with data. They are,

  1. Create
  2. Read
  3. Update
  4. Delete

Pre-requisites

  1. Install any apache + PHP + MySQL stack (XAMPP/ WAMP)
  2. Flutter installation. (Android Studio / VS Code)

Database Implementation

First, we want to set up the database and the table. we are going to implement a student registration app. Create a database call school and table name student. The table structure as follows.

PHP Implementation

Before every action with PHP, we want to keep connect the connection between the Database and the API. The following db.php file has to connect the API with the Database.

<?php

$db_name = "school";
$db_server = "127.0.0.1";
$db_user = "root";
$db_pass = "";

$db = new PDO("mysql:host={$db_server};dbname={$db_name};charset=utf8", $db_user, $db_pass);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

If you are working with XAMPP, this will need to place inside the htdocs/flutter_api directory.

Then create the PHP files for each and every action (create, delete, list and update).

To Get All Items in the Table (list.php)

<?php
header('Content-Type: application/json');
include "../flutter_api/db.php";

$stmt = $db->prepare("SELECT id, name, age FROM student");
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);

echo json_encode($result);

To Get one specific Item in the Table (details.php)

<?php
header('Content-Type: application/json');
include "../flutter_api/db.php";

$id = (int) $_POST['id'];

$stmt = $db->prepare("SELECT name, age FROM student WHERE ID = ?");
$stmt->execute([$id]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);

echo json_encode([
'result' => $result
]);

To insert a new student record (create.php)

<?php
header('Content-Type: application/json');
include "../flutter_api/db.php";


$name = $_POST['name'];
$age = (int) $_POST['age'];

$stmt = $db->prepare("INSERT INTO student (name, age) VALUES (?, ?)");
$result = $stmt->execute([$name, $age]);

echo json_encode([
'success' => $result
]);

To Delete s record from the table (delete.php)

<?php
header('Content-Type: application/json');
include "../flutter_api/db.php";

$id = (int) $_POST['id'];
$stmt = $db->prepare("DELETE FROM student WHERE id = ?");
$result = $stmt->execute([$id]);

echo json_encode([
'id' => $id,
'success' => $result
]);

To update a record (update.php)

<?php
header('Content-Type: application/json');
include "../flutter_api/db.php";

$id = $_POST['id'];
$name = $_POST['name'];
$age = (int) $_POST['age'];

$stmt = $db->prepare("UPDATE student SET name = ?, age = ? WHERE id = ?");
$result = $stmt->execute([$name, $age, $id]);

echo json_encode([
'success' => $result
]);

Flutter Implementation

Then we want to implement the Flutter code. So, create a new Flutter application as usual. Here I want to emphasize more about the Flutter, I am not going to explain PHP and MySQL more. Continue the following steps one by one. Here you want to focus on how to call to routes and how to handle those results in Flutter. Otherwise, you can create these function for your own purpose.

Inside the main.dart file, we should introduce those four route and their contexts which we are going to use.

import 'package:flutter/material.dart';

import './screens/home.dart';
import './screens/create.dart';
import './screens/details.dart';
import './screens/edit.dart';

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

class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter + PHP CRUD',
initialRoute: '/',
routes: {
'/': (context) => Home(),
'/create': (context) => Create(),
'/details': (context) => Details(),
'/edit': (context) => Edit(),
},
);
}
}

Create the student model class under the models directory. A student object contains the student id, name, and age.

class Student {
final int id;
final String name;
final int age;

Student({this.id, this.name, this.age});

factory Student.fromJson(Map<String, dynamic> json) {
return Student(
id: json['id'],
name: json['name'],
age: json['age'],
);
}

Map<String, dynamic> toJson() => {
'name': name,
'age': age,
};
}

Next, create the environment variable file, Here we use the URL of the API as the only variable. When you use the XAMPP and the Emulator, this is the URL you can use.

class Env {
static String URL_PREFIX = "http://10.0.2.2/flutter_api";
}

The first loading page is the Home() page. Inside the Home page, we create a list view to extract all list of students. and list them in a list view. They have a floating button to create a new record and each list item navigate to its detail view then tapping on that item.

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

import '../env.sample.dart';
import '../models/student.dart';
import './details.dart';
import './create.dart';

class Home extends StatefulWidget {
Home({Key key}) : super(key: key);
@override
HomeState createState() => HomeState();
}

class HomeState extends State<Home> {
Future<List<Student>> students;
final studentListKey = GlobalKey<HomeState>();

@override
void initState() {
super.initState();
students = getStudentList();
}

Future<List<Student>> getStudentList() async {
final response = await http.get("${Env.URL_PREFIX}/list.php");
final items = json.decode(response.body).cast<Map<String, dynamic>>();
List<Student> students = items.map<Student>((json) {
return Student.fromJson(json);
}).toList();

return students;
}

@override
Widget build(BuildContext context) {
return Scaffold(
key: studentListKey,
appBar: AppBar(
title: Text('Student List'),
),
body: Center(
child: FutureBuilder<List<Student>>(
future: students,
builder: (BuildContext context, AsyncSnapshot snapshot) {
// By default, show a loading spinner.
if (!snapshot.hasData) return CircularProgressIndicator();
// Render student lists
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
var data = snapshot.data[index];
return Card(
child: ListTile(
leading: Icon(Icons.person),
trailing: Icon(Icons.view_list),
title: Text(
data.name,
style: TextStyle(fontSize: 20),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Details(student: data)),
);
},
),
);
},
);
},
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return Create();
}));
},
),
);
}
}

It’s better to check up here, If you have any connection error you can resolve it before you go to the rest.

The details page is showing the currently selected item, User can also delete or, edit it. The current Item received was the response to the HTTP post request.

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import '../env.sample.dart';
import '../models/student.dart';
import './edit.dart';

class Details extends StatefulWidget {
final Student student;

Details({this.student});

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

class _DetailsState extends State<Details> {
void deleteStudent(context) async {
await http.post(
"${Env.URL_PREFIX}/delete.php",
body: {
'id': widget.student.id.toString(),
},
);
// Navigator.pop(context);
Navigator.of(context)
.pushNamedAndRemoveUntil('/', (Route<dynamic> route) => false);
}

void confirmDelete(context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Text('Are you sure you want to delete this?'),
actions: <Widget>[
RaisedButton(
child: Icon(Icons.cancel),
color: Colors.red,
textColor: Colors.white,
onPressed: () => Navigator.of(context).pop(),
),
RaisedButton(
child: Icon(Icons.check_circle),
color: Colors.blue,
textColor: Colors.white,
onPressed: () => deleteStudent(context),
),
],
);
},
);
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.delete),
onPressed: () => confirmDelete(context),
),
],
),
body: Container(
height: 270.0,
padding: const EdgeInsets.all(35),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Name : ${widget.student.name}",
style: TextStyle(fontSize: 20),
),
Padding(
padding: EdgeInsets.all(10),
),
Text(
"Age : ${widget.student.age}",
style: TextStyle(fontSize: 20),
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.edit),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => Edit(student: widget.student),
),
),
),
);
}
}

The following code snippet is the Edit page. User can edit the particular record using a POST request.

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import '../env.sample.dart';
import '../models/student.dart';
import '../widgets/form.dart';

class Edit extends StatefulWidget {
final Student student;

Edit({this.student});

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

class _EditState extends State<Edit> {
// This is for form validations
final formKey = GlobalKey<FormState>();

// This is for text onChange
TextEditingController nameController;
TextEditingController ageController;

// Http post request
Future editStudent() async {
return await http.post(
"${Env.URL_PREFIX}/update.php",
body: {
"id": widget.student.id.toString(),
"name": nameController.text,
"age": ageController.text
},
);
}

void _onConfirm(context) async {
await editStudent();
}

@override
void initState() {
nameController = TextEditingController(text: widget.student.name);
ageController = TextEditingController(text: widget.student.age.toString());
super.initState();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Edit"),
),
bottomNavigationBar: BottomAppBar(
child: RaisedButton(
child: Text('CONFIRM'),
color: Colors.blue,
textColor: Colors.white,
onPressed: () {
_onConfirm(context);
},
),
),
body: Container(
height: double.infinity,
padding: EdgeInsets.all(20),
child: Center(
child: Padding(
padding: EdgeInsets.all(12),
child: AppForm(
formKey: formKey,
nameController: nameController,
ageController: ageController,
),
),
),
),
);
}
}

The following page is used to create and add a new student record to the student table.

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import '../env.sample.dart';
import '../widgets/form.dart';

class Create extends StatefulWidget {
final Function refreshStudentList;

Create({this.refreshStudentList});

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

class _CreateState extends State<Create> {
// Required for form validations
final formKey = GlobalKey<FormState>();

// Handles text onchange
TextEditingController nameController = new TextEditingController();
TextEditingController ageController = new TextEditingController();

// Http post request to create new data
Future _createStudent() async {
return await http.post(
"${Env.URL_PREFIX}/create.php",
body: {
"name": nameController.text,
"age": ageController.text,
},
);
}

void _onConfirm(context) async {
await _createStudent();

// Remove all existing routes until the Home.dart, then rebuild Home.
Navigator.of(context)
.pushNamedAndRemoveUntil('/', (Route<dynamic> route) => false);
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Create"),
),
bottomNavigationBar: BottomAppBar(
child: RaisedButton(
child: Text("CONFIRM"),
color: Colors.blue,
textColor: Colors.white,
onPressed: () {
if (formKey.currentState.validate()) {
_onConfirm(context);
}
},
),
),
body: Container(
height: double.infinity,
padding: EdgeInsets.all(20),
child: Center(
child: Padding(
padding: EdgeInsets.all(12),
child: AppForm(
formKey: formKey,
nameController: nameController,
ageController: ageController,
),
),
),
),
);
}
}

The following page should create under the widget directory as form.dart. It has contains the validations and the content of the student form.

import 'package:flutter/material.dart';

class AppForm extends StatefulWidget {
// Required for form validations
GlobalKey<FormState> formKey = GlobalKey<FormState>();

// Handles text onchange
TextEditingController nameController;
TextEditingController ageController;

AppForm({this.formKey, this.nameController, this.ageController});

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

class _AppFormState extends State<AppForm> {
String _validateName(String value) {
if (value.length < 3) return 'Name must be more than 2 charater';
return null;
}

String _validateAge(String value) {
Pattern pattern = r'(?<=\s|^)\d+(?=\s|$)';
RegExp regex = new RegExp(pattern);
if (!regex.hasMatch(value)) return 'Age must be a number';
return null;
}

@override
Widget build(BuildContext context) {
return Form(
key: widget.formKey,
autovalidate: true,
child: Column(
children: <Widget>[
TextFormField(
controller: widget.nameController,
keyboardType: TextInputType.text,
decoration: InputDecoration(labelText: 'Name'),
validator: _validateName,
),
TextFormField(
controller: widget.ageController,
keyboardType: TextInputType.number,
decoration: InputDecoration(labelText: 'Age'),
validator: _validateAge,
),
],
),
);;
}
}

You are at the end of the story. The final file structure looks likes follows. Under the lib folder, there have three directories according to there usages. Follow up on these good practices as a beginner is very important.

Finally, I have sketched the whole application scenario as follows. you can run and see the output on your emulator.

Conclusion

This story gives you the knowledge to build REST API crud operations using flutter, PHP and MySQL. Please feel free to ask any question you will face in the response section below.
Happy Coding !!!!
Found this post useful? Kindly tap the 👏 button below! :)

--

--

Geno Tech
App Dev Community

Software Development | Data Science | AI — We write rich & meaningful content on development, technology, digital transformation & life lessons.