Pre-Loading/Caching Images from URL


#1

The Problem
My app is intended to display dynamic images managed by a server. Including the entire library of images as AppResources is unfeasible and would bloat the size of the application to astronomical proportions (not to mention the time involved in translating all the images into 6 different draw sizes each). Pointing an Image tag at the relevant URL’s results in very buggy operation on Android 5.1.1 on an emulated Google Pixel XL:

  • images are repeated across tags as you scroll until the correct image request is fulfilled, then it’s properly displayed
  • nothing is displayed at all because the request is never fulfilled.
  • the console is going haywire with System.err: java.net.ProtocolException: unexpected end of stream over and over again

The Goal
The images belong to a dataset as a property of each item in the set. When I go to retrieve the dataset from the server, I’d like to download the images and store them in cache as well, during the fulfillment of that initial promise to get the dataset. Then, after all the downloading is done, I want to display the images from cache, along with the rest of the data from the set.

I also want this to happen on the latest version of nativescript possible, and be functional on iOS as well…

The History
I have attempted to use several different approaches to manually manage the image cache:

  • tns plugin add nativescript-image-cache
  • tns plugin add nativescript-web-image-cache
  • import * as imageCache from 'ui/image-cache'
  • import * as imageSource from 'image-source'

Following what documentation is available on each of those packages, I can’t seem to make it work using nativescript v3.4.2 and tns-core-modules v3.4.1.

I attempted using imageSource’s fromUrl() getter at one point, but that also resulted in many unexpected end of stream errors…its as if while I’m looping through the set, I’m starting too many requests for the phone to deal with it. That’s why, ideally, I’m loading things into cache based on a URL key, and pulling them out based on the URL key, so each image will only be requested once. The real kick-in-the-gut is making this synchronous. I need to pause, and hold the user at a “Loading…” prompt until all the necessary images are fully loaded…

And I did find this documentation which explains to use the getImage() function on the http service provider instead…however, I can’t seem to find the correct definition of the http service provider where the function actually exists - it doesn’t exist on import { Http } from '@angular/http';


#2

The documentation you are referring to is for NativeScript Core Image Source / Http Client, it won’t be available inside angular’s http client module. But still you are allowed to access image source or core http client packages in a angular project.

Images are by default cached by NativeScript Android to improve performance. You can set the decoded width and height on image component to render it smoothly up-to your needs.

You may also try the nativescript-fresco plugin, that adds more value and control over handling remote images in Android.


#3

Thanks. That clarify’s a bit on why I can’t find the right method on the http service provider. Also, I sort of suspected defining explicit widths and heights would assist in the rendering troubles…unfortunately, the processes involved sort of dictate that the images will vary in dimension, a trivial amount, but variances have to be allowed…one set of images is logos. Some logos are perfectly square, others are rectangular.

Fresco was going to be my next move, however, fresco only works on android. I wanted a solution for both platforms - that’s what initially led me to nativescript-image-cache, which I quickly realized is an outdated fork of nativescript-web-image-cache. Both purport that they’re basic implementations of fresco and iOS’s SDWebImageCache, so the functionality is cross-platform…supposedly. But then I discovered I’d have to roll nativescript back to 2.3.* to make it work, which I’d like to avoid doing…

And what about those end of stream errors…and pre-loading…? It seems like there should be an event emitter on the Page object which fires off when all Image tag src attributes have been resolved…all media related elements for that matter.

If only this would do it…oy, what a headache it might’ve saved!
Page.loaded().subscribe(event => { this.loading = false; })


#4

Well, I’ve used RxJS’s forkJoin() to wait for all the image requests to finish, and imageSource.fromUrl() method to make the request for an ImageSource. Subscribing to the forkJoin, I can assign the image sources to the dataset, then turn off the loading prompt once that has happened. Works pretty good…I still get unexpected end of stream on the first attempt usually, but if that happens it falls back to injecting the URL’s directly into the XML properties of the Image tags. This works…but…is less than ideal. I wish there were a consistently reliable method for preloading the images that didn’t violate java.net protocols…I would assume making all the requests in parallel at once is the problem - I need them to happen sequentially.


#5

FWIW, nativescript-web-image-cache is working fine with:

> tns info
All NativeScript components versions information
┌──────────────────┬────────────────────────┬────────────────┬─────────────┐
│ Component        │ Current version        │ Latest version │ Information │
│ nativescript     │ 4.0.0-2018-02-21-10607 │ 3.4.3          │ Up to date  │
│ tns-core-modules │ 3.4.1                  │ 3.4.1          │ Up to date  │
│ tns-android      │ 3.4.2                  │ 3.4.2          │ Up to date  │
│ tns-ios          │ 3.4.1                  │ 3.4.1          │ Up to date  │
└──────────────────┴────────────────────────┴────────────────┴─────────────┘