How to Create Sortable DataTable with API Data in Flutter
Data tables are an essential component in many applications, especially when working with large amounts of data. In this tutorial, we will learn how to create a sortable data table in Flutter using API data.
We consume this API: https://jsonplaceholder.typicode.com/albums and show the response data as a data table using the DataTable widget. Then we make the Title column sortable.
Before proceeding, make sure you have installed the http package for API consumption.
The structure of the API response is as follows.
[
{
"userId": 1,
"id": 1,
"title": "quidem molestiae enim"
},
{
"userId": 1,
"id": 2,
"title": "sunt qui excepturi placeat culpa"
}
]
Following is the complete code to display DataTable with API content.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
Future<List<Data>> fetchData() async {
var url = Uri.parse('https://jsonplaceholder.typicode.com/albums');
final response = await http.get(url);
if (response.statusCode == 200) {
List jsonResponse = json.decode(response.body);
return jsonResponse.map((data) => Data.fromJson(data)).toList();
} else {
throw Exception('Unexpected error occured!');
}
}
class Data {
final int userId;
final int id;
final String title;
Data({required this.userId, required this.id, required this.title});
factory Data.fromJson(Map<String, dynamic> json) {
return Data(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(useMaterial3: true),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter DataTable Example'),
),
body: const MyStatefulWidget());
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Data>>(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return SingleChildScrollView(
child: DataTable(
border: TableBorder.all(width: 1),
columnSpacing: 30,
columns: const [
DataColumn(label: Text('USER ID'), numeric: true),
DataColumn(label: Text('ID'), numeric: true),
DataColumn(label: Text('TITLE')),
],
rows: List.generate(
snapshot.data!.length,
(index) {
var data = snapshot.data![index];
return DataRow(cells: [
DataCell(
Text(data.userId.toString()),
),
DataCell(
Text(data.id.toString()),
),
DataCell(
Text(data.title),
),
]);
},
).toList(),
showBottomBorder: true,
),
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
// By default show a loading spinner.
return const CircularProgressIndicator();
},
);
}
}
Now, we will modify the code to make the TITLE column sortable by the user.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
Future<List<Data>> fetchData() async {
var url = Uri.parse('https://jsonplaceholder.typicode.com/albums');
final response = await http.get(url);
if (response.statusCode == 200) {
List jsonResponse = json.decode(response.body);
return jsonResponse.map((data) => Data.fromJson(data)).toList();
} else {
throw Exception('Unexpected error occured!');
}
}
class Data {
final int userId;
final int id;
final String title;
Data({required this.userId, required this.id, required this.title});
factory Data.fromJson(Map<String, dynamic> json) {
return Data(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(useMaterial3: true),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter DataTable Example'),
),
body: const MyStatefulWidget());
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool _sortAscending = false; // To keep track of sorting direction
int? _sortColumnIndex; // Initially no column is sorted
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Data>>(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (_sortColumnIndex == 2) {
snapshot.data!.sort((a, b) => _sortAscending
? a.title.compareTo(b.title)
: b.title.compareTo(a.title));
}
return SingleChildScrollView(
child: DataTable(
sortColumnIndex: _sortColumnIndex,
sortAscending: _sortAscending,
border: TableBorder.all(width: 1),
columnSpacing: 30,
columns: [
const DataColumn(label: Text('USER ID'), numeric: true),
const DataColumn(label: Text('ID'), numeric: true),
DataColumn(
label: const Text('TITLE'),
onSort: (columnIndex, ascending) {
setState(() {
_sortColumnIndex = columnIndex;
_sortAscending = ascending;
});
}),
],
rows: List.generate(
snapshot.data!.length,
(index) {
var data = snapshot.data![index];
return DataRow(cells: [
DataCell(
Text(data.userId.toString()),
),
DataCell(
Text(data.id.toString()),
),
DataCell(
Text(data.title),
),
]);
},
).toList(),
showBottomBorder: true,
),
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
// By default show a loading spinner.
return const CircularProgressIndicator();
},
);
}
}
We have done three things here.
- Added _sortAscending and _sortColumnIndex to keep track of the sort direction and the column being sorted. We used sortColumnIndex and sortAscending properties of DataTable.
- Sorted the data based on the title column when the sort direction changes.
- Added the onSort callback in the title column to update the sort direction and column being sorted when the user taps on the column header.
Following is the output.
As you see, you can sort the TITLE column by clicking on it. I hope this Flutter tutorial to create a sortable DataTable is useful for you.