Jetpack Compose was announced at Google IO 2019 and is going to change how we do UI development on Android. I’ve been working a lot with the Android image APIs as I’ve been developing Coil and was curious how it treats images and how the concept of image loaders would fit in.
In the current Android UI framework
ImageView is the main way to show an image on screen. Images are represented as
Drawables, which often (though not always) wrap
Bitmaps are raw, uncompressed pixel data stored in memory.
In Jetpack Compose there is no
ImageView because there is no
View. Instead, views are replaced by
Composables which define a composable piece of UI to add to the view hierarchy. Likewise, there are no
Drawables. Instead, it’s replaced by
Image which is a minimal interface that wraps a
NativeImage. At the moment
NativeImage is defined as a
NativeImage is prefixed with a commented out
expect declaration, which is a Kotlin Multiplatform keyword ?. Currently there isn’t any support for animated images, though I’d expect
AnimatedImage to be added later. Here are the rough API analogs:
At the moment, there is only one
Image creation function,
imageResource, which synchronously loads an image from resources. The API is noted as transient and will be replaced with an asynchronous API (likely using Coroutines) in the future. However, if we want to create an
Image from a URL, a file, a URI, or another data source we’ll have to write it ourselves for now. Fortunately, we can offload the heavy lifting to an image loading library.
The easiest way to accomplish this is to write an
effect. Effects are positionally memoized blocks of code that have a return value. They can be called from a
Composable and will cause the composition (basically the view hierarchy) to rebuild if its output value is updated. Here’s an
image effect implementation backed by Coil:
What do these functions do?
image(data: Any)is a simple version of
image(request: GetRequest)that uses the default options to launch an image request.
imageis called as part of a
Composableit will emit a null
Imageand begin asynchronously loading the given
- It will update the
imagestate when it’s successful and Jetpack Compose will re-render the
Composablewith the updated
- If the request is in-flight and the
Composableis removed from the composition, the request will be automatically cancelled.
Cool, now let’s take a look at the JetNews sample app. At the moment it eagerly loads all its resources in
image, we can replace all the eager loading with lazy, non-blocking, asynchronous calls. Additionally, we can replace all the hardcoded resources with URLs! Here’s what
PostImage looks like after being converted:
Great! We’re done, right? While this will theoretically work (currently Coroutines doesn’t work with the Jetpack Compose compiler), the
image function is missing a number of features and can be optimized:
- Automatic sizing: At the moment, Coil will load the image at its original size (bounded by the size of the display) since it has no way to resolve the size of the parent container. One way to solve this would be to write our own
Composableto render the images. However, that’s analogous to writing a custom
ImageViewwhich is more restrictive for API consumers.
- Bitmap pooling:
Coil.getprevents recycling the returned drawable’s
Bitmapsince Coil doesn’t know when it’s safe to return it to the pool. When you load an image into an
ImageViewCoil knows it’s safe to recycle the Bitmap when either
Lifecycle.onDestroyoccurs, or another image load request is started on that
ImageView. Jetpack Compose provides
CommitScope.onDisposeas a lifecycle callback to clean up your components and Coil (and other image loaders) will need to treat that as a valid request disposal callback.
Most of these issues stem from the clean separation between Compose and the traditional UI framework classes like
Drawable. That said, separating from those classes is absolutely the right idea since they are tied to the platform, rely on inheritance, and hold a lot of internal state (
View is almost 30k lines long!).
Image aren’t tied to the platform, favour composition over inheritance, and hold minimal to no internal state.
Overall I’m extremely excited by the progress on Jetpack Compose and look forward to ensuring Coil works effortlessly with Compose (when it’s ready). Also, if you want to see my fork of the JetNews app with the lazy loading changes, you can find it here.
T-Mobile launches GoTo, its own line of smartphone accessories
The market for smartphone accessories is a big one, helping you to protect your expensive smartphone, keep it charged up, and more. And today T-Mobile is getting into that market with its own line of mobile accessories.
GoTo is a new line of accessories from T-Mobile. They’re available starting today, January 24th, both online and in T-Mobile stores nationwide.
There’s a number of products available in the GoTo family of accessories. Those include USB-C, microUSB, and Lightning cables, car chargers and wall chargers, a wireless charging pad, and portable battery packs. There are cases and screen protectors for a variety of phones, too, including the OnePlus 7T, iPhone 11 and 11 Pro, Galaxy S10 series, Galaxy Note 10 and Note 10+, Revvlry and Revvlry Plus, Pixel 3a, and LG K40, among others.
Pricing for the cables starts at $9.99 while the chargers start at $14.99. Cases start at $19.99 each while the tempered glass screen protectors start at $39.99 apiece. Compared to accessories from other well-known brands, these GoTo products seem similarly priced if not a little more expensive than some other brands available elsewhere online, but they’re a bit cheaper than some of the other accessories sold in T-Mobile stores.
While T-Mo does already sell accessories from several other companies, the launch of GoTo gives customers more options when looking to outfit their phone with a case or charger. That’s especially true for devices like the Revvlry or LG K40 that may not have as many accessories available as something like the iPhone 11 or Galaxy Note 10.