Open In App

Flutter – Avoiding Jank

Improve
Improve
Like Article
Like
Save
Share
Report

Parsing large JSON files can lead to poor performance of apps and shuttering animations. If parsing and computing a large JSON file take more than 16 milliseconds, the users will experience Jank. Dart by default uses a single thread to perform these tasks, though being simple and fast for less expensive computation, it fails when the computation in large.

To avoid these janks, isolates can be used to perform all computations in a different thread in the background. In this article, we will explore the process of parsing JSON in the background. To do so follow the below steps:

  1. Import the http package.
  2. Make a network request using the http package
  3. Change the response into dart objects in list form
  4. Move this work to a different isolate

Importing The http Package:

To install the http package use the below command in your command prompt:

pub get

or, if you’re using the flutter cmd use the below command:

flutter pub get

After the installation add the dependency to the pubsec.yml file as shown below:

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

Requesting Data:

We can use the http.get() method to fetch the sample data of 5000 images from JSONPlaceholder REST API as shown below:

Dart




Future<http.Response> fetchPhotos(http.Client client) async {
}


Parsing and Converting the Data:

We now have 5000 photos in JSON form received from the http.response and these need to be parsed and converted into a list of Dart objects. To do show First create a Photo class as shown below.

Here we will create a Photo class that contains the JSON data as shown below:

Dart




class Photo_list {
  final int photo_id;
  final String photo_title;
  final String photo_thumbnailUrl;
  
  Photo({this.photo_id, this.photo_title, this.photo_thumbnailUrl});
  
  factory Photo_list.fromJson(Map<String, dynamic> json) {
    return Photo_list(
      photo_id: json['id'] as int,
      photo_title: json['title'] as String,
      photo_thumbnailUrl: json['thumbnailUrl'] as String,
    );
  }
}


Now update the getPhoto() function to returns a Future<List<Photo>> through the below steps:

  1. Make a Photos_parser() function to convert the response body into a List<Photo>.
  2. Use the Photos_parser() function in the getPhotos() function.

Dart




List<Photo>Photos_parser(String responseBody) {
  final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
  
  return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}
  
Future<List<Photo>> getPhotos(http.Client client) async {
  final response =
      await client.get('https://jsonplaceholder.typicode.com/photos');
  
  return Photos_parser(response.body);
}


Moving work to a different Isolate:

The compute() function can be used to move the work to a separate isolate where it will be parsed and converted in the background. It runs expensive functions in a background isolate and returns the result. 

Dart




Future<List<Photo>> getPhotos(http.Client client) async {
  final response =
      await client.get('https://jsonplaceholder.typicode.com/photos');
  return compute(parsePhotos, response.body);
}


Complete Source Code:

Dart




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>> getPhotos(http.Client client) async {
  final response =
  
  // run Photos_parser in a separate isolate.
  return compute(Photos_parser, response.body);
}
  
List<Photo> Photos_parser(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
  
  return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}
  
class Photo_list{
  final int Photo_albumId;
  final int Photo_id;
  final String Photo_title;
  final String Photo_url;
  final String Photo_thumbnailUrl;
  
  Photo_list({this.Photo_albumId, this.Photo_id, this.Photo_title, this.Photo_url, this.Photo_thumbnailUrl});
  
  factory Photo_list.fromJson(Map<String, dynamic> json) {
    return Photo_list(
      Photo_albumId: json['albumId'] as int,
      Photo_id: json['id'] as int,
      Photo_title: json['title'] as String,
      Photo_url: json['url'] as String,
      Photo_thumbnailUrl: json['thumbnailUrl'] as String,
    );
  }
}
  
void main() => runApp(MyApp());
  
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final appTitle = 'Isolate Demo';
  
    return MaterialApp(
      title: appTitle,
      home: MyHomePage(title: appTitle),
    );
  }
}
  
class HomePage extends StatelessWidget {
  final String title;
  
  HomePage({Key key, this.title}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: FutureBuilder<List<Photo>>(
        future: fetchPhotos(http.Client()),
        builder: (context, snapshot) {
          if (snapshot.hasError) print(snapshot.error);
  
          return snapshot.hasData
              ? PhotosList(photos: snapshot.data)
              : Center(child: CircularProgressIndicator());
        },
      ),
    );
  }
}
  
class PhotosList extends StatelessWidget {
  final List<Photo> photos;
  
  PhotosList({Key key, this.photos}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
      ),
      itemCount: photos.length,
      itemBuilder: (context, index) {
        return Image.network(photos[index].Photo_thumbnailUrl);
      },
    );
  }
}


Output:



Last Updated : 17 Sep, 2020
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads