Fetching API Data in Flutter Using Dio and Isolates

Mohammed shamseer pv
6 min readFeb 6, 2025

--

flutter isolate

Fetching data from an API is a common task in mobile applications. However, handling large amounts of data or complex JSON parsing can block the main thread, causing UI lag. To address this, we can use Isolates in Flutter to offload computation-heavy tasks to a separate thread, ensuring a smooth user experience.

In this tutorial, we’ll build a Flutter app that fetches notifications from an API using Dio for network requests and Isolates for parsing JSON.

Why Use Isolates?

Flutter’s UI runs on a single thread, known as the main thread. If a heavy operation, like JSON parsing, runs on this thread, the app may freeze momentarily. To prevent this, we can use Isolates, which run code on a separate thread, ensuring smooth UI interactions.

Understanding compute in Isolates

Flutter provides a built-in function called compute, which makes it easy to offload CPU-intensive tasks to another Isolate. The compute function works by spawning a new Isolate and running a separate function in it.

Project Setup

Step 1: Add Dependencies

Add the required dependencies in your pubspec.yaml file:

dependencies:
flutter:
sdk: flutter
dio: ^5.3.2 # For making HTTP requests

Then, run:

flutter pub get

Implementing API Fetching with Isolates

Step 2: Create a Model for Notifications

We define a NotificationModel class to structure the API response:

import 'dart:convert';
class NotificationModel {
final String image;
final String title;
final String body;
final DateTime timestamp;
NotificationModel({
required this.image,
required this.title,
required this.body,
required this.timestamp,
});
factory NotificationModel.fromJson(Map<String, dynamic> json) {
return NotificationModel(
image: json['image'] ?? '',
title: json['title'] ?? '',
body: json['body'] ?? '',
timestamp: DateTime.parse(json['timestamp']),
);
}
}

Step 3: Fetch Data Using Dio

We define a function to fetch data from the API asynchronously:

import 'dart:developer' as developer;
import 'package:dio/dio.dart';
import 'dart:isolate';
const String notificationApi =
'https://raw.githubusercontent.com/shabeersha/test-api/main/test-notifications.json';
Dio dio = Dio();Future<List<NotificationModel>> fetchNotifications() async {
try {
final response = await dio.get(notificationApi);
if (response.statusCode == 200) {
developer.log('Fetched notifications successfully', name: 'fetchNotifications');
return await Isolate.run(() => parseNotifications(response.data));
} else {
throw Exception('Failed to load notifications: ${response.statusCode}');
}
} catch (e) {
developer.log('Error fetching notifications: $e', name: 'fetchNotifications');
throw Exception('Error fetching notifications: $e');
}
}

Step 4: Parse Data in an Isolate

The parseNotifications function runs in an Isolate, preventing the main thread from being blocked:

List<NotificationModel> parseNotifications(dynamic responseBody) {
try {
final Map<String, dynamic> jsonResponse = json.decode(responseBody);
final List<dynamic> data = jsonResponse['data'];
return data.map((notification) => NotificationModel.fromJson(notification)).toList();
} catch (e) {
developer.log('Error parsing notifications: $e', name: 'parseNotifications');
throw Exception('Error parsing notifications: $e');
}
}

Step 5: Display Data in the UI

We now integrate this API into a simple Flutter app:

import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Dio with Isolate',
theme: ThemeData(primarySwatch: Colors.blue),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<NotificationModel> _notifications = [];
bool _isLoading = false;
String _responseMessage = "Fetching data...";
@override
void initState() {
super.initState();
}
Future<void> _fetchData() async {
setState(() {
_isLoading = true;
});
try {
final notifications = await fetchNotifications();
setState(() {
_notifications = notifications;
_responseMessage = 'Notifications fetched successfully!';
_isLoading = false;
});
} catch (e) {
setState(() {
_responseMessage = 'Error fetching notifications: $e';
_isLoading = false;
});
}
}
String formatDateTime(DateTime dateTime) {
return '${dateTime.year}-${dateTime.month}-${dateTime.day} ${dateTime.hour}:${dateTime.minute}:${dateTime.second}';
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Notification List')),
body: Column(
children: [
ElevatedButton(
onPressed: _fetchData,
child: Text('Fetch Notifications'),
),
Expanded(
child: _isLoading
? Center(child: CircularProgressIndicator())
: _notifications.isEmpty
? Center(child: Text(_responseMessage))
: ListView.builder(
itemCount: _notifications.length,
itemBuilder: (context, index) {
final notification = _notifications[index];
return Card(
margin: EdgeInsets.all(8.0),
child: ListTile(
title: Text(notification.title),
subtitle: Text(notification.body),
trailing: Text(formatDateTime(notification.timestamp)),
),
);
},
),
),
],
),
);
}
}

Full Code

import 'dart:convert';
import 'dart:isolate';
import 'dart:developer' as developer;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:flutter/widgets.dart';
// import 'package:flutter/foundation.dart'; // For compute

// Model to represent a notification
class NotificationModel {
final String image;
final String title;
final String body;
final DateTime timestamp;

NotificationModel({
required this.image,
required this.title,
required this.body,
required this.timestamp,
});

// Factory constructor to create an instance from JSON
factory NotificationModel.fromJson(Map<String, dynamic> json) {
return NotificationModel(
image: json['image'] ?? '', // Providing default empty string if no image
title: json['title'] ?? '', // Default empty string if no title
body: json['body'] ?? '', // Default empty string if no body
timestamp: DateTime.parse(
json['timestamp']), // Parsing the timestamp field to DateTime
);
}
}

// URL to fetch notifications from
const String notificationApi =
'https://raw.githubusercontent.com/shabeersha/test-api/main/test-notifications.json';

// Dio instance for making HTTP requests
Dio dio = Dio();

// Function to fetch notifications using Dio
Future<List<NotificationModel>> fetchNotifications() async {
try {
final response = await dio.get(notificationApi);

// Check for successful response (200 OK)
if (response.statusCode == 200) {
developer.log('Fetched notifications successfully',
name: 'fetchNotifications', level: 0);
// Use compute to parse notifications in the background
// return await compute(parseNotifications, response.data);

return parseNotifications(response.data);
} else {
throw Exception('Failed to load notifications: ${response.statusCode}');
}
} catch (e) {
developer.log('Error fetching notifications: $e',
name: 'fetchNotifications', level: 1);
throw Exception('Error fetching notifications: $e');
}
}

// Function to parse the response and convert it into NotificationModel in an isolate
Future<List<NotificationModel>> parseNotifications(dynamic responseBody) async {
try {
final Map<String, dynamic> jsonResponse = json.decode(responseBody);
final List<dynamic> data = jsonResponse['data'];
return data
.map((notification) => NotificationModel.fromJson(notification))
.toList();
} catch (e) {
developer.log('Error parsing notifications: $e',
name: 'parseNotifications', level: 1);
throw Exception('Error parsing notifications: $e');
}
}

// Main function to run the app
void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Dio with Isolate',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
List<NotificationModel> _notifications = [];
String _responseMessage = "Fetching data...";
bool _isLoading = false;

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

// Function to fetch data from API
Future<void> _fetchData() async {
setState(() {
_isLoading = true;
});
try {
final notifications = await fetchNotifications();
setState(() {
_notifications = notifications;
_responseMessage = 'Notifications fetched successfully!';
_isLoading = false;
});
} catch (e) {
setState(() {
_responseMessage = 'Error fetching notifications: $e';
_isLoading = false;
});
}
}

// Function to format the DateTime into a readable string
String formatDateTime(DateTime dateTime) {
return '${dateTime.year}-${dateTime.month}-${dateTime.day} ${dateTime.hour}:${dateTime.minute}:${dateTime.second}';
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Notification List'),
),
body: Column(
children: [
ElevatedButton(
onPressed: () {
_fetchData();
},
child: Text('data')),
Expanded(
child: _isLoading
? Center(
child:
CircularProgressIndicator()) // Show loading indicator while fetching
: _notifications.isEmpty
? Center(child: Text('No notifications available.'))
: ListView.builder(
itemCount: _notifications.length,
itemBuilder: (context, index) {
final notification = _notifications[index];

// Directly accessing notification data
final imageUrl = notification.image;
final title = notification.title;
final body = notification.body;
final timestamp =
formatDateTime(notification.timestamp);

return Card(
margin: EdgeInsets.all(8.0),
child: ListTile(
// leading: imageUrl.isNotEmpty
// ? Image.network(imageUrl)
// : Icon(Icons.image_not_supported), // Fallback icon if no image
title: Text(title),
subtitle: Text(body),
trailing: Text(
timestamp), // Displaying formatted timestamp
),
);
},
),
),
],
),
);
}
}

Conclusion

By using Dio for network requests and Isolates for parsing JSON, we ensure a smooth and responsive Flutter app. This approach is particularly useful when handling large JSON responses.

Feel free to try this out and optimize your Flutter apps for better performance! 🚀

--

--

Mohammed shamseer pv
Mohammed shamseer pv

Written by Mohammed shamseer pv

skilled in Flutter, Node.js, Python, and Arduino, passionate about AI and creating innovative solutions. Active in tech community projects.

Responses (1)