Accompanist — the first chord. A Jetpack Compose library reviewed
Normally new frameworks tend to lack good use cases, tutorials, and tools. This, however, is not the case with Jetpack Compose: along with the framework Google has been working on the repository offering a wide range of use cases. As a result a set of handy utilities, reusable widgets, and other perks has been shaping up.
And just like that the repository full of use cases has evolved into the Accompanist — a set of libraries for Jetpack Compose you can add to your project as a dependency.
Hi! This is Surf, and in this article we’ll take a closer look at the tooling it offers.
Want to stay on top of app development trends and know how to create apps millions of people love?
AppCompat Theme Adapter
A theme can be quite massive in a legacy project, consisting of a huge subtree of themes and custom attributes. Legacy projects usually have the AppCompat theme, while Compose uses the MaterialTheme. This means you need to have a “bridge” between the two on them.
Accompanist offers an AppCompatTheme — a handy wrapper that can take your theme out of the world of legacy and XML and into the world of Compose. The source code is pretty simple: you can add something to it or create your own version based off of it, if your theme needs lots of custom parameters.
If your project already has the Material Design Theme, then according to the documentation you need to use an MDC-Android Compose Theme Adapter from Material Design Components.
SystemUiController
SystemUiController is a handy wrapper for the WindowInsetsControllerCompat. With the help of this one, you can change the properties of a system bar (transparency and color) with just your code. It’s a must-have if your project supports an API level 21 or higher (Android 5.0).
Insets
Insets is a wrapper for the WindowInsetsCompat and a set of extensions providing compatibility with Compose. With the help of this one, you can listen to changes in Insets.
Apart from parameters in WindowInsetsCompat, you can also use Modifier extensions.
Paddings:
Dimensions:
Permissions
It takes some effort to request dangerous permissions. What you have to do is:
- Set up a specific permission request for your user.
- Explain why you need this permission showing a custom dialogue (view, snackbar, etc.).
- Accurately process the response you get.
- Let the user switch to the application settings and decide what permissions to grant.
The authors of Accompanist have done a great job for us to feel comfortable working with permissions. And the coolest part is — they made it so you can request permissions via ActivityResultLauncher: no more overriding onActivityResult in Activity, and the contract can now be explicitly written for a permission request.
The API is quite simple.
permissionState — the state with request parameters. Together with boolean flags, it is what’s needed to request permissions.
permissionNotGrantedContent — the content displayed when no permission is granted.
permissionNotAvailableContent — the content displayed when permission is not available. The user is then redirected to app settings.
content — the content shown when permission is granted.
FlowLayout
FlexBoxLayout is a container offering flexible options for your layouts. This one makes it easier to arrange elements horizontally or vertically: e.g., to display a collection of items. It can work out the dimensions all by itself and figure out the best way to move an item to the next row or column.
FlexBox for Android is not a native layout container. The first version of FlexBoxLayout for CSS was announced in 2009 but it made it to Android development a lot later: the first alpha version of it came out in 2016. There’s been a lot of versions released ever since, the latest one being 3.0.0.
There’s also a FlexBoxLayoutManager available for RecyclerView. This container is pretty handy so it had to be available in Compose. The essence of it stayed the same but the name got changed to FlowLayout. The new name is just as neat and self-explanatory.
Just like its predecessors, it’s got the same layout parameters. So if you’ve ever tried it before, you won’t have any trouble creating a layout with Compose.
SwipeRefreshLayout
SwipeRefreshLayout is one of the most commonly used containers in Android development.
It’s easy to use: for instance, to refresh the content on your screen in XML you need to take a container with some content in it, wrap it with SwipeRefreshLayout and then you need to implement the OnRefreshListener interface in the code. Everything seems fine, what else is there to need?
But say you need to customize a loader. This is where things get tough. The options you have are limited despite how fat the code is (~1200 lines).
You can recolor an arrow, resize the indicator, or change the way it appears on screen out-of-the-box. If you need to change the color of an indicator or (Lord, save us) “make it look like in iOS”, you’ll have to look for a third-party library or write it yourself. Both options take time and are quite risky.
The Accompanist helps deal with that.
There’s no XML in the Accompanist, but it works the same way: you still need to wrap the content in SwipeRefresh and use a lambda to trigger the relevant action when the screen is refreshed.
Let’s look at what an out-of-the-box SwipeRefresh can do and what it can customize.
state — stores the loading state and the progress of the animation the refresh indicator has.
onRefresh — a lambda-trigger that causes the app to refresh the content of your screen.
modifier — modifies the container: its size, padding, etc.
swipeEnabled — enables and disables a container’s reaction to a swipe.
refreshTriggerDistance — sets the minimum distance at which the animation is triggered. The bigger the distance, the later the indicator reacts to swipes.
indicatorAlignment — equivalent to Gravity. It’s a neat functionality you may need to implement a custom indicator.
indicatorPadding — an easy way to customize indicator padding. You can add padding to one particular side or make it equal on both sides.
indicator — implements the indicator itself. You can get a standard indicator from the material design out of the box, but you can also tweak that or create your own indicator.
clipIndicatorToPadding — a flag enabling you to clip an indicator to the padding of the content.
content — the actual content within SwipeRefresh.
Here are some examples of how SwipeRefresh works taken from the sample in the Accompanist’s library.
SwipeRefreshTweakedIndicatorSample
SwipeRefreshCustomIndicatorSample
An out-of-the-box SwipeRefresh can already offer pretty neat customization options for the SwipeRefreshLayout as we know it. It takes about 600 lines to add a container and an indicator.
Pager
In UI a pager is a horizontal or a vertical list where each element takes up the whole screen or a part of it. The key difference between this one here and a regular list is the Snap effect, i.e. scroll snapping.
The Pager, or ViewPager, has gone through a number of stages as an Android UI element. First, there was ViewPager, then there came RecyclerView, which was then adapted to paging. More recently, ViewPager2 was introduced, which has the same RecyclerView under the hood.
Let’s see what the Pager can look like written in Compose. ViewPager, ViewPager2, and RecyclerView all have one thing in common: since they are UI elements that display collections, they need an adapter to fill containers with data. However, in Compose the Pager doesn’t need that: instead of an adapter, you need to initialize PagerState. So let’s start there.
There aren’t many parameters and you only need to specify one of them to initialize the PagerState.
pageCount — number of elements.
currentPage — current page.
currentPageOffset — page offset can have values from 0.0 to 1.0 and can be used to implement physical scrolling.
offscreenLimit — defines the number of pages that should be retained on either side of the current element.
infiniteLoop — a scroll loop.
And right from the get-go you can tell which of the out-of-the-box functionalities is the coolest. Scroll loop. Duh. Oh, the many band-aid fixes there are to loop ViewPager and RecyclerView! :)
Accompanist offers two ways to implement a pager: HorizontalPager and VerticalPager. If you look at the source code, both of them are simple — just a wrapper function for Compose. The only difference is in the way the isVertical parameter is initialized.
You may be familiar with many of the parameters from RecyclerView and ViewPager. So let’s look at the ones you won’t have out of the box.
verticalAlignment — an equivalent of Gravity used to align an element in the center: e.g., to adjust the scroll offset to an edge of a container, not the middle of it.
horizontalAlignment — similar to vercticalAligment, only it’s horizontal.
flingBehavior — helps adjust scroll animation, when a user stops scrolling or makes a flings motion. What is noteworthy about these parameters is that they aren’t easily configured out-of-the-box in either ViewPager or RecyclerView. In ViewPager you’d have to tinker with the parameters of the adapter and the ViewPager itself. RecyclerView, on the other hand, implements scroll snapping with SnapHelper extensions.
The code looks pretty straightforward: you initialize PagerState and add elements to the pager.
Pagers often need to have page indicators, tabs, and swipe animation. Accompanist has a solution for that as well.
As I was saying, states of the Pager are stored in the PagerState. You can reuse it to add animated indicators, swipe animation and tabs. It’s pretty handy: a single state for all components — no need to register listeners. As a bonus, we get maximum consistency.
Coil
Coil (Coroutine Image Loader) is a relatively new library: the first version of it was out in 2019. It’s written in Kotlin and backed by Kotlin Coroutines, which is why it wins over Glide and Picasso. It has a simple and flexible API, can easily be integrated into a Kotlin project, and supports Svg, Gif, and videos.
Until quite recently, Accompanist used to be integrated with Coil and Glide as well. However, starting with version 0.15.0 this toolkit is deprecated: Coil developers have added support for Compose taking Accompanist as a basis with some slight changes introduced. That’s why the following examples of code will be from Coil instead of the Accompanist.
In order to understand how Coil integrates with Compose, we need to look at ImageView parameters in Compose. Almost all of them may turn out to be familiar to an Android developer.
painter — an image parameter that can draw a color, a Bitmap, or a Vector image.
contentDescription — a parameter specifically designed to work with Accessibility. Hopefully, you’re a good developer and don’t put null in here, right? :)
modifier — sets parameters of an element: its size, padding, etc.
alignment — equivalent to Gravity.
contentScale — equivalent to ScaleType in ImageView.
alpha — transparency of images drawn with painter.
colorFilter — a color filter applied to images drawn with the painter.
All of the magic happens in the painter, specifically when you extend the abstract class Painter. In fact, Coil offers its own implementations of Painter for integration.
CrossFadePainter animates the change between two Painters with a Crossfade effect.
DrawablePainter — the simplest type of Painters in the library, but a handy one nonetheless. It can display Drawable and AnimatableDrawable.
ImagePainter — the most powerful of all, supporting String, Uri (“android.resource”, “content”, “file”, “http”, and “https”), HttpUrl, File, DrawableRes, Drawable, and Bitmap.
ImageRequest and ImageLoader are two base classes from Coil.
- ImageRequest describes where an image should be loaded from: a link, listeners, Cashe and Decode policies.
- ImageLoader executes ImageRequest and contains settings that you need to work with images. It usually needs to be adjusted once and can then be reused throughout the whole app.
PlaceHolder
Skeleton-loaders have long become standard in many platforms. While the screen is loading users see a mockup layout: based on sizes and shapes of elements you can guess where texts/photos/controls will be. This provides a positive user experience.
Accompanist offers a number of placeholders you can implement to create skeleton loaders:
- Basic — a placeholder without animation.
- Fade — a fade in/out placeholder.
- Shimmer — a placeholder with a shimmer effect.
Accompanist is a great backup for those trying Compose while it’s still fresh
Even though Accompanist doesn’t yet have a 1.0 version, the library already has a set of tools powerful enough for you to avoid many of the common mistakes.
Finally, I must say that Accompanist isn’t the only library you can use to support Compose or integrate it into your project. Compose can already be integrated with Hilt, Rx, Jetpack Navigation, and Jetpack Paging. In addition to that, Lottie 4.0.0 also supports Compose.