Android 10 is officially here! On 3rd September it began rolling out for pixel devices, so we wanted to be sure that our app was ready to serve our users who would have Android 10 installed on their device. When these OS updates come around, as developers we sometimes don’t know how many changes we’re going to need to make to our applications. When Android Marshmallow was released, permissions changes were quite a big thing for many applications. As we move our way through versions up to Android Pie we’ve also seen restrictions in various forms for permissions as a whole, along with other changes to restrictions on the size of data passed through intents and various media focused changes. When it comes to Android 10, there are a collection of changes (as outlined on the developer site) that could potentially affect your app – these changes also include some enhancements that are also available in this latest release.
Whilst we we haven’t had to make too many changes for our app, in this post we’ll share the things we did to prepare our app for the Android 10 release and how we adapted some of the new features into our application.
Other than updating the targetSdkVersion of your application to 29, you’re going to want to update a couple of android related dependencies that will improve the release of your application for Android 10.
To begin with, you may have seen that Android 10 features the ability to enable gesture navigation – this removes the software buttons at the bottom of the device, allowing the user to rely on gestural navigation to move through applications. Whilst this is something that is not enabled by default, we wanted to make sure that our app behaved as expected when this gestural mode was enabled. In order to get the best experience with gestural navigation you’ll want to update to the latest version of the androidx drawlayout dependency. Yes, this is an alpha version but without this there may be some UX difficulties when it comes to certain interactions. For example, on 1.1.0-alpha02 we experienced some oddities when trying to open the navigation drawer within our app. For this reason you should be sure to use at least 1.1.0-alpha03 if you are targeting sdk 29.
If you’re using the androidx appcompat dependency, then updating this to 1.1.0-Rc01 might be required if you are planning on making use of the sharing improvements, with backwards compatibility, for Android 10. When it comes to the changes we made to sharing improvements, this update was required to make use of the ShortcutInfoCompat and ShortcutManagerCompat classes.
This was a big change coming for Android 10 – whilst an optional setting that can be toggled from the system settings, gestural navigation allows the user to navigate through the system, apps and backstack via the user of swipe gestures. With the aim to replace the software buttons located at the bottom of the device, this gives us more screen estate to play with as well as give the option to provide a more streamlined navigation experience.
After enabling gestural navigation through Settings > Gestures > System Navigation you’ll be able to try gestural navigation for yourself. Whilst at first this may just seem like a new way to navigate through your device, it can actually have a big impact on the user experience of applications. Because we can now swipe horizontally from either side of the screen, this can interfere with components within your application.
Now, this does really depend on the application in question, as some application may not be affected by this change – it really depends on the components that make up your project. Before updating to alpha03 of the navigation drawer dependency, within alpha02 we experienced an issue where the navigation drawer would not open correctly. As it was, this would not be releasable as that’s where our users changes their currently selected account, which is a core part of our application. If you’re unaware of gestural navigation or missed out on testing this, it’s worth double checking that you’re on the latest version of this library and that the navigation drawer in your app behaves correctly.
Alongside the navigation drawer, anything component that may usually be intercepted within the bounds of this back-swipe gesture should be tested to ensure that there is no interference here. For example, if we had an edge-to-edge swipe-able view component, this could potentially be affected by gestural navigation. Whilst we didn’t have anything else that was affected by the gestural navigation changes, this might not be the same for your application. Check out this blog post for more information on how to overcome these kind of issues.
As the versions of Android have progressed, we’ve often seen changes being made to permissions and/or how we access files on the device. This is an important topic and we can understand these changes being made as our users content needs to be both contained and available by other applications when requested. Scoped Storage has been an interesting topic, right back to when the original beta releases and documentation came out for Android 10. There are more information on these changes here, but to summarise how media access now looks across the system:
Compared to the original specification for scoped storage, what’s in place now for Android 10 aimed to provide a way that was both accessible for user and developers. For Publish, we needed to make a couple of changes as we were seeing some errors when trying to handle media from certain locations on the device. In some places we were previously making use of the file path to access files on the device from other apps, and on Android 10 this can cause issues as we may not have permission to access the file. If we are looking to retrieve a file then we should now be accessing it using the content resolver – on Android 10 there is now a function available called openFile() to do this.
val fileDescriptor = context.contentResolver.openFile(mediaUri, "r", null)
Once we have the ParcelFileDescriptor from this call we can use that to access the file, without running into any permission related issues (provided that we have been granted read access to the users storage where required). This method for access files will also need to be taken into account when trying to read Exif data for files – so when trying to create a new instance of an ExifInterface this will need to take the file retrieve from the media store using the above approach. The same rules will apply for any approaches involved when trying to deal with media – your best bet is to read up on the storage related changes for Android 10 and see how they might affect your app.
This feature allows users to access key settings for the device (related to connectivity and audio) so that they can easily change common settings without leaving the context of your application. This functionality is only available on Android 10, so is not backward compatible with older versions of the Android OS. A lot of applications make use of networking features, so the connectivity side of things is something that these apps will be able to (and should really) make use of.
Let’s take a look at one example of where we make use of this. Within the Buffer Publish app we load content from our API into the social queue of an account. If this request fails, we show an error view that allows the user to retry the request. At this point, the retry button can be pressed and we’ll attempt to reload the content. However, if there is a connectivity issue (maybe the user has airplane mode turned on, or is not connected to WiFi with their data connection disabled) then this retry button will send the user into an endless loop of failed retries. We decided to make use of settings panels here so that if there is no connection available then instead the button will instead launch a connectivity settings panel to allow the user to change their connectivity settings from within our application:
activity.startActivityForResult( Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY), REQUEST_CODE_SETTINGS_PANEL)
We use startActivityForResult here so that we can detect when the user has returned from this event using REQUEST_CODE_SETTINGS_PANEL. Ideally here you would register a connectivity listener and change the state of the error view based on events coming from there. However, to keep lean and manage our priorities right now we decided to just presume that the user had changed their settings and switch the button to present “Retry” so that they can reload the content. If that then fails again, we go back to step one based on whether or not there is a connection available. This approach works well and allowed us to get this enhancement in place – there’s definitely room for improvement there in future if we see that the settings panel is commonly interacted with.
Android 10 sees some improvements being made to the share sheet functionality, as outlined in the release notes. This is a huge improvement for users, as previously sharing routes would take some time to load – causing a lag effect when trying to share something using the system share sheet. With this new approach, share targets are shared in advance so that the system has them available for display instantly – the cool thing is that this approach uses the existing app shortcuts API to provide this functionality.
We’ve made use of sharing shortcuts so that the share sheet will show individual social accounts from your Buffer account – that way, our users can share content directly to the composer with that social account selected:
ShortcutInfoCompat.Builder(context, profile.id) .setShortLabel(profile.formattedUsername) .setIcon(IconCompat.createWithBitmap(bitmap)) .setCategories(shareCategories) .setIntents(arrayOf(intent .setAction(ACTION_MAIN) .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK))) .setPerson(Person.Builder() .setKey(profile.id) .setName(profile.formattedUsername) .setIcon(IconCompat.createWithBitmap(bitmap)) .build()) .build()
You may notice the setCategories(shareCategories) call in the builder. This is setting the share category for the shortcut, as per the documentation, we the have this shortcut defined within our shortcuts.xml file
<?xml version="1.0" encoding="utf-8"?> <shortcuts xmlns:android="http://schemas.android.com/apk/res/android"> <share-target android:targetClass="org.buffer.android.composer.ComposerActivity"> <data android:mimeType="text/plain" /> <data android:mimeType="image/*" /> <data android:mimeType="video/*" /> <category android:name="org.buffer.android.category.COMPOSER_SHARE_TARGET" /> </share-target> </shortcuts>
Because we have the intent set for the shortcut, this will launch the home screen for that account if the app shortcut is interacted with. On the other hand, the category declaration will handle the case where the shortcut is triggered from the share sheet.
With this approach above we can now provide a more streamlined (and frictionless) sharing experience into our application throughout the system.
Note: If you’re looking to use the Compat classes for sharing shortcuts, then you’ll need to be on at least version 1.1.0-Rc01 of the androidx compat dependency
Biometric prompt improvements
We never previously had biometric login from our application, mainly because it wasn’t a priority for us to implement. With Android 10 containing a few more improvements to biometric prompts, we decided to add some functionality to our application to take advantage of what the system has to offer here.
From within the settings of our app, the user can now enable a setting to require fingerprint authentication when the app is opened (provided that they are logged in). Then when the app is launched and this setting is enabled, we make use of the BiometricPrompt class (available from API level 28) to display this biometric prompt to the user.
BiometricPrompt.Builder(context) .setTitle(title) .setSubtitle(subtitle) .setDescription(description) .setNegativeButton(negativeButtonText, context.mainExecutor, onClickListener) .build() .authenticate(cancellationSignal, context.mainExecutor, callback)
The main change here is that behind the scenes the BiometricManager from API level 29 is used. This class provides us with a more convenient way to check the biometric capabilities of the device, as well as the ability to provide a fallback route (PIN, pattern etc) incase the user cannot use biometric authentication.
As you can see from this article, we didn’t have to make too many changes in-order to get our app ready for our users who are running Android 10. We found that whilst these changes were small, there was a lot of manual / automated testing that needed to take place to ensure that our app functioned as intended for this OS upgrade. As time goes on, we may look at adding some of the other features / enhancements that Android 10 has introduced – be it for current or future feature implementations.
Is your app ready for Android 10? We’d love to hear about the things you’ve put in place for this release, along with any questions that you may have during that process!