RSSAmplifier

Blog

Jake Wharton

jakewharton.comRSS feed ↗100 posts

Latest posts

Live coding with dir stepper

Two months ago I gave my first ever live programming talk at KotlinConf. It was called "Talking to terminals (and how they talk back)", and you can watch it here . I've previously done slide-heavy talks which contained small demos or navigating through an existing codebase. Since this talk was starting from nothing, I decided to just write all the code from nothing. This meant spending the…

The lysine contingency

I'm happy to announce that Retrofit, OkHttp, and Okio are moving to a new GitHub organization, and along with SQL Delight are all joining the Commonhaus Foundation . You can read their announcement of the move . This move is meant to reinforce our commitment to these projects' longevity and honor the fact that they've long since outgrown the stewardship of a single company. The transfer of these…

An update on Android KTX

Eight years ago we launched a library of Kotlin extensions for the Android platform, Android KTX. In the time since, the library became Core KTX, and numerous other KTX libraries were written for other AndroidX libraries. And while KTX's approach to adding Kotlin niceties was built on a strong technology foundation, we’ve made the difficult decision to begin winding down our KTX extension…

Let's defuse the Compose BOM

Many people rely on the Compose bill of materials (BOM) artifact to provide the complete set of Compose dependency versions. If we use Compose’s foundation 1.8.0 but a transitive dependency bumps foundation-layout, there’s a risk that these two versions are incompatible with each other despite otherwise being stable libraries. The Compose BOM will unify the versions so that all are guaranteed to…

You should use AndroidX betas

Did you know the versioning of AndroidX libraries and their stability guarantees are different from most libraries? Their betas and RCs are actually production-ready, and you should be using them! In a “normal” library, such as the ones I release, features are added and known bugs are fixed to produce a stable release which might be released as version 1.2.0. If any bugs are found in that release,…

Custom short-link redirector

I used to use Bit.ly to put links into my presentations. Their service allowed you to customize the path portion of the link, so I was able to create links like bit.ly/ok-libs . If you were attending the talks live, watching the recording, or browsing the slides, the short URL was easy to type into a browser. Unfortunately, the path customization of Bit.ly is a global namespace. Short and…

Fan-in to a single required GitHub Action

It doesn't take long for a project to spawn multiple jobs in their GitHub Actions. Parallelization can lead to huge speedups for PRs. Job grouping makes it easier to conditionally enable or disable multiple steps. Each time you add a new job, however, you have to mark it as required in branch protection to prevent failing PRs from accidentally merging. Being a clever person, you might create a…

Compile-time validation of JNI signatures

JNI allows managed code inside the JVM or ART to call into native code. Java methods can be declared as native , and then a corresponding C function 1 can be written and automatically wired together when the native library is loaded. Native code lacks mechanisms like packages and overloads, so a special format is used to encode the Java method signature. A Java method defined as: package…

Deprecating idling resource libraries

When Espresso was made public a decade ago, one of its banner features was the "idling resource" concept. This monitored the main thread and any background thread pools to prevent your test from progressing until the app became idle. Waiting until idle generally increased the stability of tests since at that point the UI should be stable. We released RxIdler and okhttp-idling-resource for…

Using Renovate to update build JDK

You want to be using the latest JDK for development. Don't use Gradle toolchains , they'll needlessly force you to use old JDKs. You can still target and test on old JVM versions but develop with the latest and greatest. Java and the JDK are literally built for this. Locally this hasn't been a problem. Homebrew (or your favorite equivalent) will keep your default JDK at the latest. Keeping my…

Nonsensical Maven is still a Gradle problem

There was a time when I used Maven heavily, but today all the libraries I work on build with Gradle. Even though I'm publishing with Gradle, consumers can use Gradle, Maven, Bazel, jars in libs/ (but please don't), or anything else. That's a huge JVM ecosystem win! In general, I don't have to think about what build system someone is using. I'm not here to debate subjective pros and cons of one…

Gradle toolchains are rarely a good idea

The last post featured some Kotlin code inadvertently targeting a new Java API when the build JDK was bumped to 21. This can be solved with the -Xjdk-release Kotlin compiler flag, or by using Gradle toolchains to build with an old JDK. If you read the Gradle docs … Using Java toolchains is a preferred way to target a language version …or the Android docs … We recommend that you always specify the…

Kotlin's JDK release compatibility flag

Yesterday, our Android app crashed with a weird NoSuchMethodError . java.lang.NoSuchMethodError: No interface method removeFirst()Ljava/lang/Object; in class Ljava/util/List; or its super classes (declaration of 'java.util.List' appears in /apex/com.android.art/javalib/core-oj.jar) at app.cash.redwood.lazylayout.widget.LazyListUpdateProcessor.onEndChanges(SourceFile:165) at…

Perils of duplicate finding

Given an array of integers ( [1, 2, 3, 1, 3, 1] ), find the elements which are duplicated. No, we're not interviewing. I'm trying to prevent a user from specifying a reserved value twice. Elsewhere in the file I already have duplicate detection for object tags. val dupes : Map < Int , List < Widget >> = widgets . groupBy ( Widget :: tag ) . filterValues { it . size > 1 } I can do the same…

Intermediate collection avoidance

Given a list of users, extract their names and join them into a comma-separated list. Kotlin's extension functions on collections make this trivial. users . map { it . name }. joinToString () Writing this in IntelliJ IDEA produces a "weak warning" offering advice. Call chain on collection type may be simplified An intention action will refactor the code for you to a more efficient form. users .…

A stable, multiplatform Molecule 1.0

This post was published externally on Cash App Code Blog. Read it at https://code.cash.app/molecule-1-0 .

Native UI and multiplatform Compose with Redwood

This post was published externally on Cash App Code Blog. Read it at https://code.cash.app/native-ui-and-multiplatform-compose-with-redwood .

Flow testing with Turbine

This post was published externally on Cash App Code Blog. Read it at https://code.cash.app/flow-testing-with-turbine .

Using jlink to cross-compile minimal JREs

jlink is a JDK tool to create bespoke, minimal JREs for your applications. Let's try it with a "Hello, world!" program: class Main { public static void main ( String ... args ) { System . out . println ( "Hello, world!" ); } } My laptop is an M1 Mac and I have downloaded the Azul Zulu JDK 19 build for it. With the JDK I can both compile Java and then run the resulting program. $ mkdir out $…

Report card: Java 19 and the end of Kotlin

Three years ago I gave the talk "What's new in Java 19: The end of Kotlin?" which forecasted what a future Java language would look like in September 2022 when Java 19 was released. Check your calendars, folks. It's September 2022 right now and Java 19 was released today! As expected my predictions were not perfect, but I'm pretty happy with the results. Let's check in with each feature and see…

Build on latest Java, test through lowest Java

In the past, when a new version of Java was released, I would add that version to our open source project's CI builds. strategy: matrix: java-version: - 8 - 9 ⋮ - 17 + - 18 This ensures that each project can be built and its tests pass on every major version. But this makes no sense! No user is building these projects on different versions. No user is building these projects at all. Consumers are…

Slope-intercept library design

The equation y=mx+b defines a line in slope-intercept form. The line will intercept the y-axis at the value b and for each change in x its slope (the amount the line goes up or down) will change by m . Slope-intercept gives me a way to think about the design of libraries in relation to each other. The intercept is the initial cost of learning and setup for a library, and the slope is how the…

The state of managing state (with Compose)

This post was published externally on Cash App Code Blog. Read it at https://code.cash.app/the-state-of-managing-state-with-compose .

Multiplatform Compose and Gradle module metadata abuse

My primary work project for the better part of a year (named Redwood) is built on top of Compose 1 and runs on every platform that Kotlin supports. This of course means Android, but we also have Compose running on iOS, the web, the JVM, and all other native targets. It's truly a multiplatform Compose project 2 . Getting Compose to run on all these platforms isn't as hard as you would think. The…

Gradle dependency license validation

This post was published externally on Cash App Code Blog. Read it at https://code.cash.app/gradle-dependency-license-validation .

Case-insensitive filesystems considered harmful (to me)

Having been burned by case-insensitive filesystem bugs one too many times, I long ago switched my development folder to a case-sensitive filesystem partition on my otherwise case-insensitive Mac. Unfortunately this can actually work against me as I interact with the computers of coworkers and service providers which use the default. Well I was burned again, and this is the tale! I've been working…

Cross-compiling static Rust binaries in Docker for Raspberry Pi

Earlier this year I built a web-based garage door controller using Rust for the Raspberry Pi called Normally Closed . My deployment includes a Pi 3b and a Pi Zero which are ARMv7 and ARMv6 devices, respectively. I deploy services with Docker and wanted to continue using it here for simplicity. Getting all of this set up and working together was not easy. There's also a lot of words in that title…

Migrating from Burst to TestParameterInjector

This post was published externally on Cash App Code Blog. Read it at https://code.cash.app/migrating-from-burst-to-testparameterinjector .

Integration verbosity and good layering

One of my favorite non-features from building view binding is that it lacks integration with activities or fragments. If you use view binding with activities or fragments, however, this fact might be to your disdain. Every activity using view binding is forced to do something along the lines of: override fun onCreate ( savedInstanceState : Bundle ) { super . onCreate ( savedInstanceState ) val…

AssistedInject is dead, long live AssistedInject!

This post was published externally on Cash App Code Blog. Read it at https://code.cash.app/assisted-inject-is-dead-long-live-assisted-inject .

A Jetpack Compose by any other name

I really like Jetpack Compose. Between work and personal stuff I have three projects which are each built on top of it. It's great! So far my biggest problem is its name… but that requires some explaining. Welcome to one of the hills I'll die on! What is Jetpack Compose? If you're already familiar with it, something should pop in your head when asked: What is Jetpack Compose? A new UI toolkit for…

Treating Dockerfiles as shell scripts

I use Docker to run a lot of tools. With the tools all wrapped up in containers, my computers are free of Python and Go and the various other dependencies needed for their use. While this is a nice win for isolation and reproducibility, the user experience is sub-par. To run a tool, I just use docker run : $ docker run --rm tool arguments... But the editing workflow is something like: $ nano…

Peeking at command-line ANSI escape sequences

Command-line programs use color to convey additional information and to look pretty. For example, compare the output of ls with and without the --color flag: The color helps convey information in this compact output that would otherwise only be available in more verbose forms ( -l ). In addition to color, a program may update existing output. You can see this when updating images with…

Smaller APKs with resource optimization

How many times does the name of a layout file appear in an Android APK? We can build a minimal APK with a single layout file to count the occurrences empirically. Building an Android app with Gradle requires only one thing: an AndroidManifest.xml file with a package. From there we can add a dummy layout whose contents are just <merge/> since we only care about its name. . ├── build.gradle └── src…

Shrinking a Kotlin binary by 99.2%

We'll get to the shrinking, but first let's motivate the binary in question. Three years ago I wrote the "Surfacing Hidden Change to Pull Requests" post which covered pushing important stats and diffs into PRs as a comment. This avoids surprises with changes that affect binary size, manifests, and dependency trees. Showing dependency trees used Gradle's dependencies task and diff -U 0 to display…

Wire Support For Swift, Part 1

This post was published externally on Cash App Code Blog. Read it at https://code.cash.app/wire-support-for-swift-part-1 .

Sixteen corners

Last year I built a library called Picnic for rendering data tables in monospaced environments like your terminal. Part of rendering the table is calculating what character to use for each wall and each corner separating the cells. Here's a representative output with a bunch of different corner styles: │ compressed │ uncompressed ├───────────┬───────────┬───────┼───────────┬───────────┬────────…

R8 Optimization: Lambda Groups

Note: This post is part of a series on D8 and R8, Android's new dexer and optimizer, respectively. For an intro to D8 read "Android's Java 8 support" . For an intro to R8 read "R8 Optimization: Staticization" . Lambda usage in Kotlin feels more pervasive than Java because of the functional nature of the Kotlin standard library. Some lambdas are merely syntactic constructs that are eliminated at…

Which is better on Android: divide by 2 or shift by 1?

I've been porting the AndroidX collection library to Kotlin multiplatform to experiment with binary compatibility, performance, tooling, and the different memory models. Some of the data structures in the library use array-based binary trees to store elements. The Java code has a lot of shifts to replace power-of-two multiplications and divides. When ported to Kotlin, these turn into the…

Simple Multiplatform RPC with Kotlin Serialization

I recently played a minor role in helping add Cast support to an Android app. Both the Android app and Cast display are written in Kotlin. The Android Cast SDK relays JSON strings to the JavaScript SDK which invokes your callback with the deserialized equivalent as a JS object. A multiplatform library holds the model objects so that they can be shared between Android and JS. class Game ( val…

Litmus-Testing Kotlin's Many Memory Models

When writing multiplatform code, Kotlin's three compiler backends each have different memory models which must be considered. JavaScript is single-threaded so you really can do no wrong. The JVM model is arguably too permissive where you can do incorrect things and have them work 99.9% of the time. When targeting native, Kotlin enforces some invariants which helps prevent you from those 0.1% bugs…

D8 Optimization: Assertions

Note: This post is part of a series on D8 and R8, Android's new dexer and optimizer, respectively. For an intro to D8 read "Android's Java 8 support" . For an intro to R8 read "R8 Optimization: Staticization" . The assert keyword is quirky Java language syntax used for testing invariants. That is: things you expect to always be true. Its syntax has two forms: assert < bool - expression >; assert <…

Removing Google as a Single Point of Failure Part 2: Gmail

I want to remove Google as a single point of failure in my life. In the first blog post on this subject I detailed my setup for backing up Google Photos and Google Drive contents onto my home server and remotely to rsync.net . Left out of that post was a solution for Gmail because I hadn't found one yet. Now I have. Source of truth That first post started with an important qualification: This does…

Removing Google as a Single Point of Failure

I want to remove Google as a single point of failure in my life. They have two decades of my email. They have two decades of my photos. They have the only copy of thousands of documents, projects, and other random files from the last two decades. Now I trust Google completely in their ability to correctly retain my data. But I think it's clear that over the last 5 years the company has lost…

Extracting 100% of Data From a Stubborn, Dying ZFS Pool

In 2010 I built a home server with five 2TB drives. It ran Solaris and ZFS for the redundancy and data checksumming to ensure no data could be lost or corrupted. Just 16 months later five 3TB drives were added to the pool. This computer took the 2600-mile trip to live in San Francisco with me. It then endured the 2600-mile return trip when I left. Having sat unplugged for five years, I recently…

D8 Library Desugaring

Note: This post is part of a series on D8 and R8, Android's new dexer and optimizer, respectively. For an intro to D8 read "Android's Java 8 support" . For an intro to R8 read "R8 Optimization: Staticization" . So far in this series the coverage of D8 has been about desugaring of Java 8 language features , working around vendor- and version-specific bugs in the platform, and performing…

Public API challenges in Kotlin

Kotlin is justifiably lauded for its language features compared to today's Java. It has constructs which allow expressing common patterns with more concise alternatives. An overused example in every intro-to-Kotlin talk or blog post is comparing a Java "POJO" to a Kotlin data class . Here's yet another one of those comparisons, but bear with me as it will be used to illustrate the points in this…

D8 Optimizations

Note: This post is part of a series on D8 and R8, Android's new dexer and optimizer, respectively. For an intro to D8 read "Android's Java 8 support" . For an intro to R8 read "R8 Optimization: Staticization" . No, that's not a typo! While the optimizations in this series so far have been done by R8 during whole-program optimization, D8 can also perform some simple optimizations. D8 was introduced…

R8 Optimization: Enum Switch Maps

Note: This post is part of a series on D8 and R8, Android's new dexer and optimizer, respectively. For an intro to D8 read "Android's Java 8 support" . For an intro to R8 read "R8 Optimization: Staticization" . The previous post on R8 covered enum ordinals which then allowed branch elimination to apply to a switch statement. In that post, the full bytecode for switch on an enum was omitted because…

R8 Optimization: Enum Ordinals and Names

Note: This post is part of a series on D8 and R8, Android's new dexer and optimizer, respectively. For an intro to D8 read "Android's Java 8 support" . For an intro to R8 read "R8 Optimization: Staticization" . Enums are (and have always been!) a recommended way to model a fixed set of constants. Most commonly an enum only provides a set of possible constants and nothing more. But being full…