Flutter: How I optimized my ListView of images from URLs. Part 1. Storing and fetching image data.

In this article I will tell you how I optimized the image displaying in LinkVault, where I need to display preview details of URLs like you…

Flutter: How I optimized my ListView of images from URLs. Part 1. Storing and fetching image data.

In this article I will tell you how I optimized the image displaying in LinkVault, where I need to display preview details of URLs like you see in Chrome feeds.

Part 1. Storing and fetching image data.

In this Part 1, I will discuss how I optimized storing and retrieving image data into and from a remote database (Firebase) as a layer-by-layer optimization.

Intro

So recently I have been developing a utility app for me where I need to show the list of URL previews like those in Chrome feeds.

So Initially I had parsed all the URL MetaData in a data object so one of the two basic properties it stores are

  1. URL Favicon or Website Logo data
  2. URL Banner Image Data

So Initially I was storing Favicon Logo Url , Favicon ImageData(bytes)
and similarly for the banner image also.

So the first problem I encountered was the initial download time from the remote database (I am using Firebase) and the database cost of storing and fetching the metadata.

How did I get through it?

So, in the first step, I changed the format in which I was storing data in Firebase from Uint8List or List<int> to ⁣base64String. But also before that, I also compressed the raw image bytes using FlutterImageCompress.

static Future<Uint8List?> compressImage( 
    Uint8List imageBytes, { 
    required int quality, 
    required bool autofillPng, 
    (int r, int g, int b)? autofillColor, 
  }) async { 
    final imageSize = getImageDimFromUintData(imageBytes); 
 
    if (imageSize == null) return null; 
 
    final image = img.decodeImage(imageBytes); 
    if (image == null) return null; 
 
    // Check if the image has an alpha channel 
    final hasAlpha = image.numChannels == 4; 
 
    // Compress while preserving transparency if it has alpha channel 
    final compressedBytes = await FlutterImageCompress.compressWithList( 
      imageBytes, 
      quality: quality, 
      format: 
          hasAlpha && autofillPng ? CompressFormat.png : CompressFormat.jpeg, 
      keepExif: true, 
    ); 
 
    return Uint8List.fromList(compressedBytes); 
  }

So by doing so I got an approx. 1/10th to 1/3threduction in image bytes.

Now converting these compressed bytes into aBase64String firebase is optimized for storing strings, and also storing large List<int> of images is not looking good on Firebase if you want to see the data in the Firebase document (only for my case).

import 'dart:convert';   
 
  // Function to convert Uint8List to Base64 string 
  static String? convertUint8ListToBase64(Uint8List? data) { 
    if (data == null) return null; 
    return base64Encode(data); 
  } 
 
  // Function to convert Base64 string back to Uint8List 
  static Uint8List? convertBase64ToUint8List(String? base64String) { 
    if (base64String == null) { 
      return null; 
    } 
    return base64Decode(base64String); 
  }

And this is how I use it in my UrlMetaData.

factory UrlMetaData.fromJson(Map<String, dynamic> json) { 
    return UrlMetaData( 
      favicon: StringUtils.convertBase64ToUint8List(json['favicon'] as String?), 
      faviconUrl: json['favicon_url'] as String?, 
      bannerImage: 
          StringUtils.convertBase64ToUint8List(json['banner_image'] as String?), 
      bannerImageUrl: json['banner_image_url'] as String?, 
      title: json['title'] as String?, 
      description: json['description'] as String?, 
      websiteName: json['websiteName'] as String?, 
    ); 
  } 
 
Map<String, dynamic> toJson() { 
    return { 
      'favicon': StringUtils.convertUint8ListToBase64(favicon), 
      'favicon_url': faviconUrl, 
      'banner_image': StringUtils.convertUint8ListToBase64(bannerImage), 
      'banner_image_url': bannerImageUrl, 
      'title': title, 
      'description': description, 
      'websiteName': websiteName, 
    }; 
  }
Now I had pretty optimized the database but still the UrlImageData base64String still has 50–150KB of image data. That is for every url the user will add.
And keeping in mind there will be many users and many-many urls of them.

So, I figured out other ways to store and optimize image data fetching and storage, and following Clean Architecture Design PatternsI can anytime switch whether to store image data on Firebase or not; it will not give errors in the project design.

Since my main users for now are on mobile, I can use the offline device storage for storing image data

For this I used Isar Flutter local database, which is a successor to Hive, a SQL-like database, and blazingly ⚡faster than Hive.

And here is the model for ImageData in Isar

import 'dart:typed_data'; 
 
import 'package:isar/isar.dart'; 
import 'package:link_vault/core/utils/string_utils.dart'; 
 
part 'url_image.g.dart'; 
 
@collection 
class UrlImage { 
  UrlImage({ 
    required this.imageUrl, 
    required this.base64ImageBytes, 
    this.id, 
  }); 
 
  factory UrlImage.fromBytes({ 
    required String imageUrl, 
    required Uint8List bytes, 
  }) { 
    return UrlImage( 
      imageUrl: imageUrl, 
      base64ImageBytes: StringUtils.convertUint8ListToBase64(bytes)??'', 
    ); 
  } 
 
  final Id? id; 
   
  @Index(unique: true, replace: true) 
  String imageUrl; 
 
  String base64ImageBytes; 
}

For query optimization, I used indexation in the Isar database on imageUrl.

And Id is an Isar type alias for int, and if it's null, then Isar will autoincrement. In my case I am more concerned about fetching image data by imageUrl.

But why all of these optimizations if I could just use image URLs and Image.network() to show all images?

The reason I could not use only Image.network() is because sometimes banner images are too large to download and could not be downloaded through mobile data.
1. And it will not be a good user experience, and downloading images each time will cost internet data.
2. Second, it will consume system resources while fetching it.

Therefore, I have to leave this approach.

Now From here most of the optimization has been done; it is pretty fast now, and the only thing left is to manage fetching and managing concurrent changing(copying) states of Cubits of UrlDataModel, as it forms a little glitch while this whole process is displaying images on the screen.

In Part 2, I will discuss how I optimized displaying of these image with minimalistic or unnoticeable glitch.
Coming soon ….

If you find this helpful, please leave a clap or comment; it helps me and helps Medium know to show this to more developers.

Get My latest articles directly in your Inbox.

Get My New Articles Directly in your Email Inbox :)
Get My New Articles Directly in your Email Inbox :) You will get instant update when I publish new articles and a…

Connect With Me