Parsear JSON en segundo plano
Cómo realizar una tarea en segundo plano.
Por defecto, las aplicaciones de Dart hacen todo su trabajo en un solo hilo. En muchos casos, este modelo simplifica la codificación y es lo suficientemente rápido como para no provocar un rendimiento deficiente de la aplicación ni animaciones entrecortadas, lo que a menudo se denomina "jank."
Sin embargo, es posible que necesites realizar un cálculo costoso, como parsear un documento JSON muy grande. Si este trabajo tarda más de 16 milisegundos, tus usuarios experimentarán jank.
Para evitar el jank, debes realizar cálculos costosos como este en segundo plano, utilizando un Isolate independiente. Esta receta utiliza los siguientes pasos:
- Agrega el paquete
http. - Realiza una solicitud de red utilizando el paquete
http. - Convierte la respuesta en una lista de fotos.
- Mueve este trabajo a un isolate independiente.
1. Agregar el paquete http
#
Primero, agrega el paquete http a tu proyecto.
El paquete http facilita la realización de solicitudes
de red, como la obtención de datos desde un endpoint JSON.
Para agregar el paquete http como dependencia,
ejecuta flutter pub add:
flutter pub add http
2. Realizar una solicitud de red
#Este ejemplo cubre cómo obtener un documento JSON grande
que contiene una lista de 5000 objetos de fotos desde la
API REST de JSONPlaceholder,
utilizando el método http.get().
Future<http.Response> fetchPhotos(http.Client client) async {
return client.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
}
3. Parsear y convertir el JSON en una lista de fotos
#A continuación, siguiendo la guía de la receta
Obtener datos de internet,
convierte la respuesta http.Response en una lista de objetos de Dart.
Esto hace que sea más fácil trabajar con los datos.
Crear una clase Photo
#
Primero, crea una clase Photo que contenga datos sobre una foto.
Incluye un método factory fromJson() para facilitar la creación de una
Photo a partir de un objeto JSON.
class Photo {
final int albumId;
final int id;
final String title;
final String url;
final String thumbnailUrl;
const Photo({
required this.albumId,
required this.id,
required this.title,
required this.url,
required this.thumbnailUrl,
});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
albumId: json['albumId'] as int,
id: json['id'] as int,
title: json['title'] as String,
url: json['url'] as String,
thumbnailUrl: json['thumbnailUrl'] as String,
);
}
}
Convertir la respuesta en una lista de fotos
#Ahora, utiliza las siguientes instrucciones para actualizar la
función fetchPhotos() para que devuelva un
Future<List<Photo>>:
- Crea una función
parsePhotos()que convierta el cuerpo de la respuesta en unaList<Photo>. - Utiliza la función
parsePhotos()en la funciónfetchPhotos().
// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
final parsed = (jsonDecode(responseBody) as List<Object?>)
.cast<Map<String, Object?>>();
return parsed.map<Photo>(Photo.fromJson).toList();
}
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response = await client.get(
Uri.parse('https://jsonplaceholder.typicode.com/photos'),
);
// Synchronously run parsePhotos in the main isolate.
return parsePhotos(response.body);
}
4. Mover este trabajo a un isolate independiente
#Si ejecutas la función fetchPhotos() en un dispositivo más lento,
es posible que notes que la aplicación se congela por un breve momento mientras parsea y
convierte el JSON. Esto es jank, y querrás deshacerte de él.
Puedes eliminar el jank moviendo el parseo y la conversión
a un isolate en segundo plano utilizando la función compute()
proporcionada por Flutter. La función compute() ejecuta funciones
costosas en un isolate en segundo plano y devuelve el resultado. En este caso,
ejecuta la función parsePhotos() en segundo plano.
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response = await client.get(
Uri.parse('https://jsonplaceholder.typicode.com/photos'),
);
// Use the compute function to run parsePhotos in a separate isolate.
return compute(parsePhotos, response.body);
}
Notas sobre el trabajo con isolates
#Los isolates se comunican pasando mensajes de un lado a otro. Estos mensajes pueden
ser valores primitivos, como null, num, bool,
double, o String, u
objetos simples como la List<Photo> en este ejemplo.
Es posible que experimentes errores si intentas pasar objetos más complejos,
como un Future o http.Response entre isolates.
Como solución alternativa, echa un vistazo a los paquetes worker_manager
o
workmanager para el procesamiento en segundo plano.
Ejemplo completo
#import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response = await client.get(
Uri.parse('https://jsonplaceholder.typicode.com/photos'),
);
// Use the compute function to run parsePhotos in a separate isolate.
return compute(parsePhotos, response.body);
}
// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
final parsed = (jsonDecode(responseBody) as List<Object?>)
.cast<Map<String, Object?>>();
return parsed.map<Photo>(Photo.fromJson).toList();
}
class Photo {
final int albumId;
final int id;
final String title;
final String url;
final String thumbnailUrl;
const Photo({
required this.albumId,
required this.id,
required this.title,
required this.url,
required this.thumbnailUrl,
});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
albumId: json['albumId'] as int,
id: json['id'] as int,
title: json['title'] as String,
url: json['url'] as String,
thumbnailUrl: json['thumbnailUrl'] as String,
);
}
}
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
const appTitle = 'Isolate Demo';
return const MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late Future<List<Photo>> futurePhotos;
@override
void initState() {
super.initState();
futurePhotos = fetchPhotos(http.Client());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: FutureBuilder<List<Photo>>(
future: futurePhotos,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(child: Text('An error has occurred!'));
} else if (snapshot.hasData) {
return PhotosList(photos: snapshot.data!);
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
}
class PhotosList extends StatelessWidget {
const PhotosList({super.key, required this.photos});
final List<Photo> photos;
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: photos.length,
itemBuilder: (context, index) {
return Image.network(photos[index].thumbnailUrl);
},
);
}
}
A menos que se indique lo contrario, la documentación de este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-05-05. Ver código fuente oreportar un problema.