By default, Dart apps do all of their work on a single thread. In many cases,this model simplifies coding and is fast enough that it does not result inpoor app performance or stuttering animations, often called “jank.”

However, you may need to perform an expensive computation, such as parsing avery large JSON document. If this work takes more than 16 milliseconds, yourusers will experience jank.

To avoid jank, you need to perform expensive computations like this in thebackground. On Android, this would mean scheduling work on a different thread.In Flutter, you can use a separateIsolate.

Directions

  • Add the http package
  • Make a network request using the http package
  • Convert the response into a List of Photos
  • Move this work to a separate isolate

1. Add the http package

First, add the http package to your project.The http package makes it easier to perform networkrequests, such as fetching data from a JSON endpoint.

  1. dependencies:
  2. http: <latest_version>

2. Make a network request

In this example, you’ll fetch a JSON large document that contains a list of5000 photo objects from the JSONPlaceholder RESTAPIusing the http.get() method.

  1. Future<http.Response> fetchPhotos(http.Client client) async {
  2. return client.get('https://jsonplaceholder.typicode.com/photos');
  3. }

Note: You’re providing an http.Client to the function in this example.This makes the function easier to test and use in different environments.

3. Parse and Convert the json into a List of Photos

Next, following the guidance from the Fetch data from theinternetrecipe, you’ll want to convert the http.Response into a list of Dart objects.This makes the data easier to work with in the future.

Create a Photo class

First, create a Photo class that contains data about a photo.You will include a fromJson factory method to make it easy to create aPhoto starting with a json object.

  1. class Photo {
  2. final int id;
  3. final String title;
  4. final String thumbnailUrl;
  5. Photo({this.id, this.title, this.thumbnailUrl});
  6. factory Photo.fromJson(Map<String, dynamic> json) {
  7. return Photo(
  8. id: json['id'] as int,
  9. title: json['title'] as String,
  10. thumbnailUrl: json['thumbnailUrl'] as String,
  11. );
  12. }
  13. }

Convert the response into a List of Photos

Now, update the fetchPhotos function so it can return aFuture<List<Photo>>. To do so, you’ll need to:

  • Create a parsePhotos that converts the response body into a List<Photo>
  • Use the parsePhotos function in the fetchPhotos function
  1. // A function that converts a response body into a List<Photo>
  2. List<Photo> parsePhotos(String responseBody) {
  3. final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
  4. return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
  5. }
  6. Future<List<Photo>> fetchPhotos(http.Client client) async {
  7. final response =
  8. await client.get('https://jsonplaceholder.typicode.com/photos');
  9. return parsePhotos(response.body);
  10. }

4. Move this work to a separate isolate

If you run the fetchPhotos function on a slower phone, you may notice the appfreezes for a brief moment as it parses and converts the json. This is jank,and we want to be rid of it.

So how can we do that? By moving the parsing and conversion to a backgroundisolate using the computefunction provided by Flutter. The compute function runs expensivefunctions in a background isolate and returns the result. In this case,we want to run the parsePhotos function in the background.

  1. Future<List<Photo>> fetchPhotos(http.Client client) async {
  2. final response =
  3. await client.get('https://jsonplaceholder.typicode.com/photos');
  4. // Use the compute function to run parsePhotos in a separate isolate
  5. return compute(parsePhotos, response.body);
  6. }

Notes on working with Isolates

Isolates communicate by passing messages back and forth. These messages canbe primitive values, such as null, num, bool, double, or String, orsimple objects such as the List<Photo> in this example.

You may experience errors if you try to pass more complex objects, such asa Future or http.Response between isolates.

Complete example

  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:flutter/foundation.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:http/http.dart' as http;
  6. Future<List<Photo>> fetchPhotos(http.Client client) async {
  7. final response =
  8. await client.get('https://jsonplaceholder.typicode.com/photos');
  9. // Use the compute function to run parsePhotos in a separate isolate
  10. return compute(parsePhotos, response.body);
  11. }
  12. // A function that converts a response body into a List<Photo>
  13. List<Photo> parsePhotos(String responseBody) {
  14. final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
  15. return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
  16. }
  17. class Photo {
  18. final int albumId;
  19. final int id;
  20. final String title;
  21. final String url;
  22. final String thumbnailUrl;
  23. Photo({this.albumId, this.id, this.title, this.url, this.thumbnailUrl});
  24. factory Photo.fromJson(Map<String, dynamic> json) {
  25. return Photo(
  26. albumId: json['albumId'] as int,
  27. id: json['id'] as int,
  28. title: json['title'] as String,
  29. url: json['url'] as String,
  30. thumbnailUrl: json['thumbnailUrl'] as String,
  31. );
  32. }
  33. }
  34. void main() => runApp(MyApp());
  35. class MyApp extends StatelessWidget {
  36. @override
  37. Widget build(BuildContext context) {
  38. final appTitle = 'Isolate Demo';
  39. return MaterialApp(
  40. title: appTitle,
  41. home: MyHomePage(title: appTitle),
  42. );
  43. }
  44. }
  45. class MyHomePage extends StatelessWidget {
  46. final String title;
  47. MyHomePage({Key key, this.title}) : super(key: key);
  48. @override
  49. Widget build(BuildContext context) {
  50. return Scaffold(
  51. appBar: AppBar(
  52. title: Text(title),
  53. ),
  54. body: FutureBuilder<List<Photo>>(
  55. future: fetchPhotos(http.Client()),
  56. builder: (context, snapshot) {
  57. if (snapshot.hasError) print(snapshot.error);
  58. return snapshot.hasData
  59. ? PhotosList(photos: snapshot.data)
  60. : Center(child: CircularProgressIndicator());
  61. },
  62. ),
  63. );
  64. }
  65. }
  66. class PhotosList extends StatelessWidget {
  67. final List<Photo> photos;
  68. PhotosList({Key key, this.photos}) : super(key: key);
  69. @override
  70. Widget build(BuildContext context) {
  71. return GridView.builder(
  72. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  73. crossAxisCount: 2,
  74. ),
  75. itemCount: photos.length,
  76. itemBuilder: (context, index) {
  77. return Image.network(photos[index].thumbnailUrl);
  78. },
  79. );
  80. }
  81. }

Isolate Demo