The CommonsBlog


Upgrading to dev15 of Jetpack Compose

Upgrading an existing Jetpack Compose project, running dev14 or earlier, to dev15 is a bit more involved than past updates. Here is what you need to do to move from dev14 to dev15, for a project using traditional Groovy-based Gradle files. If you are upgrading from an older Compose version, or you are using Kotlin Gradle scripts, some modifications will be needed to these instructions.

Step #1: Upgrade Your Kotlin and Compose Versions

Compose now works off of a pre-release version of Kotlin 1.4, and we need to have our projects adapt to that.

In your top-level build.gradle file, you probably have a kotlin_version constant declared, such as:

ext.kotlin_version = "1.3.61"

Change that to point to the 1.4-M3 version instead. That is not available in the standard repositories, so you will need to add https://dl.bintray.com/kotlin/kotlin-eap/ as a Maven repository, both for the buildscript dependencies and for the rest of the modules.

If you have a compose_version constant declared here, set it to point to 0.1.0-dev15.

So, for example, you might wind up with something like:

buildscript {
    ext {
        compose_version = '0.1.0-dev15'
        kotlin_version = "1.4-M3"
    }

    repositories {
        google()
        jcenter()
        maven {
            url "https://dl.bintray.com/kotlin/kotlin-eap/"
        }
    }

    dependencies {
        classpath "com.android.tools.build:gradle:4.2.0-alpha05"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        maven {
            url "https://dl.bintray.com/kotlin/kotlin-eap/"
        }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Step #2: Upgrade the Compose Build

In your modules’ build.gradle files, you should have a composeOptions closure, with something like this:

composeOptions {
    kotlinCompilerExtensionVersion "${compose_version}"
    kotlinCompilerVersion '1.3.70-dev-withExperimentalGoogleExtensions-20200424'
}

If you have a Compose version hard-coded for kotlinCompilerExtensionVersion, change it to point to 0.1.0-dev15. And, change the kotlinCompilerVersion as well:

composeOptions {
    kotlinCompilerExtensionVersion "${compose_version}"
    kotlinCompilerVersion '1.4.0-dev-withExperimentalGoogleExtensions-20200720'
}

Also, towards the bottom of the build.gradle file, outside of any other closure, add:

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
    kotlinOptions {
        jvmTarget = "1.8"
        freeCompilerArgs += ["-Xallow-jvm-ir-dependencies", "-Xskip-prerelease-check"]
    }
}

It is not completely clear why Google is advising this approach, rather than having this kotlinOptions closure in the android closure. In my experiments, rather than using that tasks.withType() block, I added the kotlinOptions to the android closure:

kotlinOptions {
    jvmTarget = '1.8'
    freeCompilerArgs += ["-Xallow-jvm-ir-dependencies", "-Xskip-prerelease-check"]
}

One or both of those techniques hopefully will work for your project.

Some developers report also needing to include this in kotlinOptions:

languageVersion = "1.4"

Step #3: Upgrading Your Dependencies

In addition to switching to new Kotlin and plugin versions, the Compose team is in the process of completely refactoring the artifacts used by Compose. You are going to need to switch to the new artifacts to get things to work.

Fortunately, many of the key Compose bits are set up as transitive dependencies of the material artifact:

implementation "androidx.compose.material:material:$compose_version"

The biggest thing not covered by that is the @Preview annotation, which for the time being still resides in:

implementation "androidx.ui:ui-tooling:$compose_version"

However, there are many other artifacts, not all of which are necessarily pulled in by material:

  • androidx.compose.animation:animation:0.1.0-dev15
  • androidx.compose.animation:animation-core:0.1.0-dev15
  • androidx.compose.foundation:foundation:0.1.0-dev15
  • androidx.compose.foundation:foundation-layout:0.1.0-dev15
  • androidx.compose.foundation:foundation-text:0.1.0-dev15
  • androidx.compose.material:material:0.1.0-dev15
  • androidx.compose.material:material-icons-core:0.1.0-dev15
  • androidx.compose.material:material-icons-extended:0.1.0-dev15
  • androidx.compose.runtime:runtime:0.1.0-dev15
  • androidx.compose.runtime:runtime-dispatch:0.1.0-dev15
  • androidx.compose.runtime:runtime-livedata:0.1.0-dev15
  • androidx.compose.runtime:runtime-rxjava2:0.1.0-dev15
  • androidx.compose.runtime:runtime-saved-instance-state:0.1.0-dev15
  • androidx.compose.ui:ui:0.1.0-dev15
  • androidx.compose.ui:ui-geometry:0.1.0-dev15
  • androidx.compose.ui:ui-graphics:0.1.0-dev15
  • androidx.compose.ui:ui-text:0.1.0-dev15
  • androidx.compose.ui:ui-text-android:0.1.0-dev15
  • androidx.compose.ui:ui-unit:0.1.0-dev15
  • androidx.compose.ui:ui-util:0.1.0-dev15

Between the documentation and looking up specific classes at AndroidX Tech, you should be able to identify the new artifacts to use.


With those changes, you can migrate a simple Compose project from dev14 to dev15. Again, the more complex your project, or the further removed from dev15 it is, you may have additional changes that you need to make.

Jul 26, 2020


With <intent-filter>, Only Advertise What You Can Fulfill

It seems like only yesterday that I pointed out problems when apps have <intent-filter> elements that they do not support. But, that was a bit over 8 years ago. Time flies!

(in related news: I’m getting old)

In that post, my example was an app that had an <intent-filter> matching one from a popular app (Barcode Scanner) that many other apps would launch. In this case, the offending app could not fulfill the contract of that <intent-filter>, largely because the activity in question was not exported. At the time, a bug caused non-exported activities to still show up in the chooser, though fortunately this has since been fixed.

More generally: if your app has an <intent-filter>, there is an implied contract that your app needs to fulfill. If you are not going to fulfill that contract, do not have that <intent-filter>, or at least make it user-controllable.

For example, some file managers advertise activities for the ACTION_OPEN_DOCUMENT <intent-filter>, or ACTION_GET_CONTENT.

In theory, this is fine, if such activities fulfill the contract. In the case of ACTION_OPEN_DOCUMENT, apps are expecting to get back a Uri that works with DocumentsContract, perhaps by way of DocumentFile. And they are expecting the Uri to support basic things, like getType() and openInputStream() on ContentResolver. If your activity will return such a Uri, great! If not… why are you advertising the ACTION_OPEN_DOCUMENT <intent-filter>?

My guess is that the file manager developers are offering a subset of ACTION_OPEN_DOCUMENT support, with an eye towards environments that lack such an activity, such as Android TV. If so, that too is fine, but those activities should either be completely opt-in by the user, or at least opt-out only on devices that lack a real ACTION_OPEN_DOCUMENT implementation. You can do this by putting the <intent-filter> on an <activity-alias> that you enable and disable based on the device and based on user input. This lets you toggle the availability of that <intent-filter> without affecting the general availability of the activity itself.

Frankly, it would not surprise me if some future version of Android adopted a protected activity Intent approach, akin to how it has the concept of protected broadcasts, ones that only system apps can send. And, it would not surprise me if ACTION_OPEN_DOCUMENT were protected in this fashion.

But, in general, if you have an <intent-filter>, make sure that it does what other apps will expect. In particular, if the protocol for that Intent involves startActivityForResult()/setResult(), then make sure that your result matches expectations. This is no different that completely supporting the API of some interface or abstract class that you implement… except that there is no compiler telling you what to do, just some balding guy.

Jul 25, 2020


"Exploring Android" Version 1.2 Released

Subscribers now have access to an update to Exploring Android, known as Version 1.2, in PDF, EPUB, and MOBI/Kindle formats, in addition to the online reader. Just log into your Warescription page and download away, or set up an account and subscribe!


This update overhauls the tutorials. We still build the same app in terms of functionality. However, the tutorials are a bit more streamlined, eliminating bits of rework performed in the original tutorials. In addition, a number of implementation details were changed, including:

  • Migrating from data binding to view binding

  • Switching from Calendar to Instant

  • Refining the use of coroutines

  • Having better defensive programming for activity starts

  • Revamping the unit and instrumented tests to simplify them a bit

The tutorials were also updated for Android Studio 4.0.1 and newer versions of various dependencies.

If you are already working through the tutorials, you have two main options:

  1. Start over with the ones in this book

  2. Continue with the tutorials from Version 1.1 (or whatever book version you were using)

It will be impractical for you to try to switch from the older tutorials to this book’s set mid-way through the work.

The next update to this book will be sometime after Android Studio 4.1 ships in stable form.

Jul 20, 2020


Multipart Uploads with OkHttp... and a Uri

As changes like scoped storage increase the need to be able to work with content Uri values, I have been increasingly pointing developers to this OkHttp issue comment. If you have a need to do a multipart form upload, and your content is identified by a Uri instead of a File, the InputStreamRequestBody that Jared Burrows created (with help from Jake Wharton) is what you need.

However, I never created a sample app for this, simply because I did not have a Web service needing that sort of upload. I realized a week ago that RequestBin exists and can handle this scenario. So, this evening I tossed together this sample app to demonstrate a Kotlin port of InputStreamRequestBody and its use.


The OkHttp recipes page shows how to use MultipartBody.Builder to create the body of a POST request. That MultipartBody.Builder has addFormDataPart() methods that let you fill in the elements of the “form”. For including a “file” (from the standpoint of the form), addFormDataPart() can accept a RequestBody. The specific recipe in that OkHttp documentation shows creating one from a File, such as by using the asRequestBody() extension function in Kotlin.

If you have a content Uri, though, OkHttp alone will not be sufficient. OkHttp supports non-Android environments. As a result, OkHttp knows nothing about Uri or ContentResolver or other Android SDK classes. But RequestBody is an abstract class, so we can create our own implementation that can use Android SDK classes for our own Android projects. That’s what Jared did in that OkHttp issue.

In Kotlin, an equivalent implementation looks like this:

class InputStreamRequestBody(
  private val contentType: MediaType,
  private val contentResolver: ContentResolver,
  private val uri: Uri
) : RequestBody() {
  override fun contentType() = contentType

  override fun contentLength(): Long = -1

  @Throws(IOException::class)
  override fun writeTo(sink: BufferedSink) {
    val input = contentResolver.openInputStream(uri)

    input?.use { sink.writeAll(it.source()) }
      ?: throw IOException("Could not open $uri")
  }
}

Now, I spent perhaps an hour on this sample. As the saying goes, “your mileage may vary”, and your standard concerns about using code from blog posts are warranted. That said, it’s a fairly straight-up port of Jared’s original Java sample, and it works in light testing.

You can create an instance of InputStreamRequestBody and include it in an addFormDataPart() call where the OkHttp recipe shows the one based on the File. So, you wind up with something like this:

val contentPart = InputStreamRequestBody(type, resolver, content)

val requestBody = MultipartBody.Builder()
  .setType(MultipartBody.FORM)
  // TODO add other form elements here
  .addFormDataPart("something", name, contentPart)
  .build()

val request = Request.Builder()
  .url(serverUrl)
  .post(requestBody)
  .build()

ok.newCall(request).execute().use { response ->
  // TODO something with the response
}

Here:

  • type is an OkHttp MediaType identifying the MIME type of the content

  • resolver is a ContentResolver, obtained from a handy Context

  • content is the Uri to the content to upload

  • name is some “filename” for the content

  • serverUrl is the URL you want to POST to

In the sample app, I have a version of that code that uses DocumentFile to obtain the MIME type and name to use. My serverUrl comes from BuildConfig; see the sample app’s README for information about where that URL will need to come from if you clone and try running the sample. The content Uri comes from ACTION_OPEN_DOCUMENT: the user clicks a big “Upload Something!” button, chooses a document, and that document gets uploaded to the server.

If you are using a different HTTP client library, it needs some similar solution if it is going to behave well on modern versions of Android, where filesystem access is constrained and content Uri values are increasingly common.

Many thanks to Jared and Jake for the original implementation!

UPDATE: 2020-07-06: See also this related blog post from cketti. It shows a similar ContentUriRequestBody and also shows the download-to-a-Uri scenario.

Jul 05, 2020


"Elements of Android Jetpack" Version 1.0 Released

Subscribers now have access to Version 1.0 of Elements of Android Jetpack, in PDF, EPUB, and MOBI/Kindle formats. Just log into your Warescription page to download it, or set up an account and subscribe!

This update contains no substantial changes, just a bunch of bug fixes and other refinements to the prose. In particular, this update still covers Android Studio 3.6.3, because of lousy luck between my release schedules and Google’s Android Studio release schedules.

The next update to this book should be in a couple of months, to cover Android Studio 4.0 (or 4.1 if that is final by then).

Jun 29, 2020


Older Posts