Logo

Image Optimisation for Train-Based Food Delivery Apps

Arpit Anand, Product Manager

Fri Jun 06 2025

Image Optimization, Mobile Apps, Food Delivery, eCatering, CDN, Android

How We Engineered a Visual Experience That Works Even When the Network Doesn’t

At Ipsator, we don’t just build another food delivery app—we design a platform that serves hot, fresh meals to passengers on moving trains. And with our journey powering IRCTC eCatering over the years, we’ve learned that delivering a great product experience isn’t just about what users order—it’s about how it feels while they do it.

That “feel” is often defined by visuals. The mouthwatering Paneer Tikka, the clean layout with logos and banners, and the subtle cues say, “Trust us, your food is coming.” But here’s the catch: all these images have to load on a train ride where the internet is as unpredictable as the Indian monsoon.

This isn’t just an edge case. This is the main case. So, we had to rethink how images work in our app—how they load, how they adapt to each user’s device and network, and how they stay beautiful without slowing the app down.

Why networks matter more than you think

Most apps assume a reasonably stable internet connection. But for passengers on a moving train, network quality is anything but predictable. A user might browse effortlessly for a moment and then suddenly hit a no-signal patch. That’s where many apps start to stumble—especially if they try to load large assets like high-resolution images.

To make the image loading experience adaptive, we designed the system to respond to the user’s real-time connection quality. Based on internally defined thresholds, the app categorises the network into quality bands—high, moderate, and low. Depending on this, we morph the quality of the image being delivered rather than its resolution—ensuring that even users on weaker networks see images quickly, while those on stronger networks enjoy richer, more detailed visuals. This balances performance and visual appeal intelligently.

This approach kept our logic flexible and adaptable—something that proved essential when the system went live.

Every phone is different, so images need to be too

In India, there’s no “standard phone.” A user could be on an ultra-budget Android with a 360px-wide screen, or a premium AMOLED display with high pixel density. A one-size-fits-all image strategy just doesn’t cut it.

Initially, we tried to account for screen DPI and calculate the perfect image size based on the resolution. While technically correct, it was complex, CPU-heavy, and made caching chaotic.

So, we took a step back and made the system simpler and smarter. We defined five width “buckets” for images—300px, 600px, 900px, 1200px, and 1400px. When an image needs to be shown in a slot of, say, 700px, it picks the 900px version. This simplified decision-making, improved consistency, and allowed efficient reuse of cached images.

It’s a case where simplifying the logic improved both performance and user experience. No more blurry banners or overkill file sizes—just images that are sized right for the moment.

The Android challenge: Knowing the image size before it’s drawn

On Android, especially in dynamic layouts like RecyclerViews or ConstraintLayouts, image containers don’t always reveal their final size immediately. If you try to get view.getWidth() too early, it returns zero.

But since we were trying to load just the right image, we had to know the size.

The workaround was subtle but powerful—we used a ViewTreeObserver.OnPreDrawListener() to hook into the layout process right before the view is drawn. This gave us the actual dimensions we needed without guessing or hardcoding. It’s one of those “invisible” changes that improves both efficiency and image accuracy.

A smart CDN can do the heavy lifting

Once we had a sense of the right size and quality, the next step was figuring out how to deliver it. Generating multiple versions of every image manually wasn’t scalable. Storing them all wasn’t economical either.

So we turned to something we already used: our image CDN. But this time, we gave it more responsibility.

Instead of hosting static images, we started using CDN-level transformations. That means we generate the right version of an image on demand using URL parameters like width and quality. If the network is weak, we tweak the quality down. If the view is small, we ask for a scaled-down width.

It’s fast, cost-effective, and very flexible. The best part? It adapts in real time to the context of the user—something pre-generated images could never do.

Caching—but make it intelligent

Image caching is a given in modern apps. But for us, it had to do more than just store and fetch.

Let’s say a user saw a 600px image on a low network earlier, and now they’re on a better connection. Shouldn’t they get a higher-quality version next time without having to manually clear the cache?

We wanted to avoid redundant fetches while still upgrading quality when appropriate. So we created a smarter caching mechanism.

By customising how cache keys are named—based on image size—we allowed the app to differentiate between different versions of the same image. When a user revisits a screen, the app checks if a higher-quality version is already cached. If not, it requests it anew—especially if the network now allows it.

In some views, we even show a lower-quality thumbnail instantly, then upgrade to a better one once it’s ready. It’s a small touch that makes the app feel fast, even when the internet isn’t.

What if the connection improves midway?

This was a tricky one. A user boards the train in a rural zone with low network, and sees medium-quality images. Thirty minutes later, the train is near a city with an excellent signal. Should we just keep showing the older cached version?

We decided not to interrupt unless necessary. For example, if the user is in a listing or scrolling quickly, they continue to see cached images for speed. But if they open a product page, zoom into a banner, or revisit a screen, we quietly fetch the higher-quality version in the background—only if the network supports it.

The idea wasn’t to replace every image immediately—but to upgrade only when it mattered, and without user friction.

Handling Image Failures Gracefully

No matter how robust the infrastructure, there’s always the chance of an image failing to load—especially in train journeys where connectivity can swing wildly. To handle such cases gracefully, we built multi-level fallbacks for every image URL.

If an image fails to load with optimised parameters (like a transformed CDN variant), the system automatically drops down to a standard, less parameterised image. If that still doesn’t work, it can even bypass the CDN and attempt to fetch the original image directly—ensuring that the UI never feels broken due to missing visuals.

This multi-tiered fallback strategy has been critical in maintaining a stable experience across varied conditions.

RecyclerView Optimisation with Glide Prefetching

Image-heavy interfaces like ours depend heavily on smooth scrolling—especially in long lists like restaurant carousels or food categories. To optimise this, we use Android’s RecyclerView to load only those images that are visible on screen.

But we didn’t stop there.

To further improve perceived performance, we integrated Glide’s RecyclerView prefetching library. This allows the system to preload images for items just about to come into view, reducing jank and making the entire experience feel snappier—even on average phones.

Combined with our network-aware image delivery and caching, this creates a smooth visual experience without eating up unnecessary bandwidth or memory.

Real-world tuning: The unexpected benefit of going live

No matter how carefully we modelled our logic during development, nothing beats the real world for revealing edge cases.

When the image delivery system first rolled out, we kept a close eye on performance. Did the images load quickly? Were users on low-speed networks seeing too many placeholders? Were high-end users getting the visual polish we intended?

We began analysing logs, tracking load times, and monitoring user journeys across different geographies and train routes. And soon enough, patterns emerged.

We noticed, for instance, that some “moderate” networks were holding up better than expected, while others needed to be classified as “low” despite technically meeting the bandwidth cutoff. So we quietly adjusted our internal thresholds—not by rewriting logic, but simply by tuning the values that powered our quality bands.

This ability to tweak without redeploying was key. It meant we could improve the user experience iteratively, without risking regressions or slowing development.

In a way, the field helped us calibrate the lab.

The bigger picture: Why this matters for eCatering

Over the years, we’ve grown IRCTC eCatering not just as a service, but as a product. One that combines logistics, commerce, design, and systems thinking into something deeply Indian—and deeply functional.

Optimising images might seem like a small technical detail. But for our users—often hungry, travelling, and offline for parts of the journey—it makes a world of difference.

We’ve learned that good engineering isn’t always about adding more. Sometimes, it’s about understanding the context deeply enough to do less in just the right places. And this, for us, is one such place.


More from the journey

Want to read more about how we think and build at Ipsator?

logo_image

100+ million lives touched