Disappearing images


#1

I’m really frustrated with how Expo handles image assets and am hoping someone can tell me that I’m doing something wrong. I am precaching all my images using the instructions here: https://docs.expo.io/versions/latest/guides/preloading-and-caching-assets.html

All of my images are referenced using require and look like this:

export const Backgrounds = {
   Login: require('./images/bg-login.jpg')
};

I then use it like this:

<Image source={Backgrounds.Login}>...</Image>

Here’s the problem: if my app loses internet connectivity or wifi is very slow, many of the images stop showing up. This means I have blank icons and missing background images all over my app, even though I specifically precached all images when the app loaded. Sometimes when wifi is really slow, the images (some of which are .png icons that are ~1kb) are missing and then after a few seconds appear.

This all makes me think that Expo is downloading each image every time it appears on the screen. Is this expected behavior? Or am I doing something wrong?


#2

Sorry this is frustrating. Expo.Asset.fromModule(require('./images/icon.png')).downloadAsync() should give you an asset object that you can pass into <Image source />. This might resolve your issue.

The precaching also downloads the images to the OS’s cache directory which might get cleared, which could explain why they sometimes disappear. We might need to offer a way to pin files for the lifetime of the app.


#3

Thanks, @ide. So you’re saying I should try using this instead?

export const Backgrounds = {
   Login: Expo.Asset.fromModule(require('./images/bg-login.jpg')).downloadAsync()
};

Also, something else I’ve noticed: when I force close the app and immediately open it up again it seems that it redownloads all my assets, even though I expect them to be cached from the previous time I had the app open. Here are two scenarios I’ve tested:

  1. Open the app, allow all assets to load, browse around the app, then force close it. Change to wifi/cell data network with high latency/low bandwidth. Open app again. It will take several minutes to load up again.

  2. Open the app, allow all assets to load, browse around the app, then force close it. Switch to Airplane Mode. Open app again. App and all assets load immediately.

So it seems like if it knows there is a network connection, it will redownload everything. If it knows there is no network connection, it just uses the cached assets. This doesn’t seem like the expected behavior.


#4

Yes, I’d try something similar to that code snippet you pasted, but downloadAsync() returns a promise so you’ll need to resolve it.

I’m not sure off the top of my head what the behavior for downloading assets is actually like. The behavior we want to provide is to honor the caching headers sent from the server (which, in the case of Expo-hosted assets, is to have an infinite cache lifetime so the client would need to download the images only once).


#5

This is potentially going to be an Expo dealbreaker for us. The app we’re working on will often be used in high school gyms and workout rooms which will often have very limited internet access. If it takes someone several minutes to load the app, or if images don’t appear, because he/she has poor connectivity, that’s going to be a huge problem. Is there someone with knowledge of how the asset loading works that can weigh in on the behavior I’ve seen?


#6

We’re designing support for bundled image assets (so when you build a standalone app, it embeds both your JS and images in the IPA/APK) but we’re not sure how it will work out or when it will happen. I don’t want to add risk to your shipping schedule, so one thing you could do is embed the image assets as base-64 data URIs.

You can do something like this:

export const Backgrounds = {
   Login: { uri: 'data:image/jpeg;base64,XXXXX' }
};

You might want to write a script that generates this JS module from your images (and be sure to run them through JPEG/PNG compressors or use tinypng.com).


#7

Okay, thanks. This solution is not super ideal, but seems like that might be the safest bet for us right now. Is there a link to somewhere that I can +1 for bundled image assets?


#8

Yep, this feature request here should cover it: https://expo.canny.io/feature-requests/p/allow-standalone-apps-to-load-and-function-with-no-internet-connection


Images load really slow
#9

man, I’m so confused by this…

In “Preloading & Caching assets” it says something like…
"do this:
Expo.Asset.fromModule(require(’./images/icon.png’)).downloadAsync()

and then require your images as you normally would. "

which made me think that Expo was performing some kind of conditional processing on images cached with this method… which I’ve tried and seems to have no effect.

here in this post it sounds like that call returns an "asset object’.
either way it doesn’t seem to be working for me…
tiny 2k icons sometimes take several seconds to load.

I’m using Expo 23.0.0
running the XDE in dev mode over LAN
I’ve tried turning off dev mode as well…no help

I’m still even having trouble understanding the underlying principles. I’m a fairly new dev and I’ve been assuming that’s most of my problem…but, at this point I feel like I’ve wasted quite a lot of time trying to troubleshoot this.

My main questions are these:
what is the default local image resource behavior of my App using expo? if I have an component with an icon.png (being required) as the source am I making network requests every time the component loads?
is there no “Expo” way to cache local resources on the device itself?

Seems like maybe this should be presented right up front as a caveat for using expo?

I wish there was just a simple config in app.json where I could designate a folder as “assets I want cached on device”


#10

which made me think that Expo was performing some kind of conditional processing on images cached with this method… which I’ve tried and seems to have no effect.

when you run Expo.Asset.fromModule(require(’./images/icon.png’)).downloadAsync() it downloads the image and sets a property on the asset to indicate that it is local and resolves the uri to the path on disk rather than remote url next time you use require('./images/icon.png').

make sure you defer rendering the component using the image until it’s done downloading, otherwise you just end up downloading it twice. here’s an example of how to do that: https://github.com/expo/new-project-template/blob/5cd8d262d1f2f6fbb890981e12935a53dd7726fc/App.js#L12-L46

I wish there was just a simple config in app.json where I could designate a folder as “assets I want cached on device”

this is coming in sdk24 (within next couple of weeks), at least for standalone apps. first version will only support images thought, sdk25 should support all assets if all goes well. see example here: https://github.com/expo/native-component-list/blob/142495d524613e982b6ad6f9a0cf2347de59a69a/app.json#L49-L54


#11
  • the default behavior is indeed to load it over the network
  • you cache resources using the function that you shared above. to bundle it in standalone app, this new assetBundlePatterns field will be useful. we will continue to explore how to make it easier to cache in the client as well, perhaps leveraging this field to automatically cache assets on load, or maybe something else.
  • i’m not sure how much more clear we can make the caveat – it’s the top point in the “Why not Expo?” doc: https://docs.expo.io/versions/latest/introduction/why-not-expo.html

Image are displayed only compressed
#12

lastly, can you share your app code with me? there’s no reason why a 3kb image should take seconds to load. makes me think there is some bug in your code. feel free to share privately with brentvatne on github or just post the url here


#13

brent!
thanks so much for the response!
that helps me understand for sure…and touche’ regarding caveats…I remember reading that now, I’m a junior dev, and this is my first react-native project. I’m still working to be comfortable with some core JS concepts so there was a disconnect in my brain from then and now :wink:

I think I’m handing the deferring correctly. I’m using the component in my root routing file and only rendering my view onFinish.

I think you maybe right about other bugs happening. This is a project for a company I’m contracted with so the repo is private, but now that you’ve confirmed this stuff for me I feel like I can assume I’ve got other issues.

this is an ecommerce app the loads “merchant offers” to be purchased. I hava a view that renders a list of offers that all have main product thumnail images.
that view is rendered together with a tabs bar that I built that has nav Icons.
it seems like the loading of those images is blocking the loading of my icons. perhaps I just need to defer loading of the offers until my tabs bar has loaded?

anyway…thanks so much for your time!