Android jumps on Java release train | | https://mobileit.cz/Blog/Pages/android-java-release-train.aspx | Android jumps on Java release train | <p>For many years, Android was stuck with Java 8. Finally, we got a
<a href="https://android-developers.googleblog.com/2020/12/announcing-android-gradle-plugin.html">
big update</a>. The gap between Java 8 and Java 9 in terms of build compatibility has been
overcome and more modern Java versions (up to Java 11) are officially supported on Android. On
top of that, Android Gradle Plugin 7.0.0 now requires JDK 11 for running Gradle builds.</p><p>In this post, I’ll describe the technical background of this change and how it might affect
your project, even if it’s written exclusively in Kotlin.</p><h2>What is the release train and why have Java 9 and 10 been skipped?</h2><p>Historically, a new major Java version has been released “every once in a while”. This has led to
an irregular release schedule and the language not evolving rapidly.</p><p>Beginning with Java 9, it was decided that a new major Java version would be released every 6
months and LTS (long-term support) releases would arrive every 3 years.</p><table cellspacing="0" width="90%" class="ms-rteTable-default" style="margin-left:auto;margin-right:auto;border:1px solid black;"><tbody><tr class="ms-rteTableHeaderRow-default"><th class="ms-rteTableHeaderEvenCol-default" style="width:18%;">
<strong>Java version</strong>
</th><th class="ms-rteTableHeaderOddCol-default" style="width:18%;">
<strong>Release date</strong>
</th><th class="ms-rteTableHeaderEvenCol-default" style="width:64%;">
<strong>Selected language features</strong>
</th></tr><tr class="ms-rteTableOddRow-default" style="background-color:#f4cccc;"><td class="ms-rteTableEvenCol-default">Java 6</td><td class="ms-rteTableOddCol-default">December 2006</td><td class="ms-rteTableEvenCol-default">No language changes</td></tr><tr class="ms-rteTableEvenRow-default" style="background-color:#f4cccc;"><td class="ms-rteTableEvenCol-default">Java 7</td><td class="ms-rteTableOddCol-default">July 2011</td><td class="ms-rteTableEvenCol-default">
<a href="https://docs.oracle.com/javase/7/docs/technotes/guides/language/enhancements.html#javase7">
Project Coin</a>: Diamond operator, Strings in switch, etc.
</td></tr><tr class="ms-rteTableOddRow-default" style="background-color:#fff2cc;"><td class="ms-rteTableEvenCol-default">Java 8 LTS</td><td class="ms-rteTableOddCol-default">March 2014</td><td class="ms-rteTableEvenCol-default">
<a href="https://openjdk.java.net/jeps/126">Lambdas</a><br>
<a href="https://openjdk.java.net/jeps/104">Type Annotations</a><br>
<a href="https://openjdk.java.net/jeps/126">Default methods in interfaces</a>
</td></tr><tr class="ms-rteTableEvenRow-default" style="background-color:#fff2cc;"><td class="ms-rteTableEvenCol-default">Java 9</td><td class="ms-rteTableOddCol-default">September 2017</td><td class="ms-rteTableEvenCol-default">
<a href="https://openjdk.java.net/jeps/213">Private methods in interfaces</a>
</td></tr><tr class="ms-rteTableOddRow-default" style="background-color:#fff2cc;"><td class="ms-rteTableEvenCol-default">Java 10</td><td class="ms-rteTableOddCol-default">March 2018</td><td class="ms-rteTableEvenCol-default">
<a href="https://openjdk.java.net/jeps/286">Local-Variable Type Inference</a>
</td></tr><tr class="ms-rteTableEvenRow-default" style="background-color:#d9ead3;"><td class="ms-rteTableEvenCol-default">Java 11 LTS</td><td class="ms-rteTableOddCol-default">September 2018</td><td class="ms-rteTableEvenCol-default">
<a href="https://openjdk.java.net/jeps/323">Local-Variable Syntax for Lambda
Parameters</a>
</td></tr><tr class="ms-rteTableOddRow-default" style="background-color:#fff2cc;"><td class="ms-rteTableEvenCol-default">Java 12</td><td class="ms-rteTableOddCol-default">March 2019</td><td class="ms-rteTableEvenCol-default">No stable language features</td></tr><tr class="ms-rteTableEvenRow-default" style="background-color:#fff2cc;"><td class="ms-rteTableEvenCol-default">Java 13</td><td class="ms-rteTableOddCol-default">September 2019</td><td class="ms-rteTableEvenCol-default">No stable language features</td></tr><tr class="ms-rteTableOddRow-default" style="background-color:#fff2cc;"><td class="ms-rteTableEvenCol-default">Java 14</td><td class="ms-rteTableOddCol-default">March 2020</td><td class="ms-rteTableEvenCol-default">
<a href="https://openjdk.java.net/jeps/361">Switch Expressions</a>
</td></tr><tr class="ms-rteTableEvenRow-default" style="background-color:#d9ead3;"><td class="ms-rteTableEvenCol-default">Java 15</td><td class="ms-rteTableOddCol-default">September 2020</td><td class="ms-rteTableEvenCol-default">
<a href="https://openjdk.java.net/jeps/378">Text Blocks</a>
</td></tr><tr class="ms-rteTableOddRow-default" style="background-color:#cfe2f3;"><td class="ms-rteTableEvenCol-default">Java 16</td><td class="ms-rteTableOddCol-default">March 2021</td><td class="ms-rteTableEvenCol-default">
<a href="https://openjdk.java.net/jeps/394">Pattern Matching for instanceof</a><br>
<a href="https://openjdk.java.net/jeps/395">Records</a>
</td></tr><tr class="ms-rteTableEvenRow-default" style="background-color:#cfe2f3;"><td class="ms-rteTableEvenCol-default">Java 17 LTS</td><td class="ms-rteTableOddCol-default">September 2021</td><td class="ms-rteTableEvenCol-default">Nothing announced yet</td></tr></tbody></table>
<br>
<p>Standard releases have quite a short support period and receive just 2 minor updates exactly 1
and 4 months after their initial release. The LTS releases are guaranteed to be supported till
another LTS version is released, in 3 years timeframe (for details about Java release trains, I
recommend reading
<a href="https://blog.joda.org/2018/09/java-release-chains-features-and-security.html">Stephen
Colebourne's posts</a>).</p><p>Many projects have decided to follow the LTS releases only and now it seems that Google has the
same plans for Android. Even though Java 15 is the latest released version, it is a non-LTS
version, so Android maintains the latest LTS release, Java 11, as the required minimum.</p><h2>What complicates the update from Java 8 to Java 9 and onwards?</h2><p>Java 9 was the first version released in the new era and brought a lot of new features the
community desired. The most significant of these is probably the new modular system known by the
codename <a href="https://openjdk.java.net/projects/jigsaw">“Project Jigsaw”</a>. It has a
concept of dependencies that can define a public API and can keep the implementation private at
the same time.</p><p>This feature is first and foremost meant to be used by libraries. As the JDK is a library itself,
it has also been modularized. The main advantage is that it is possible to create a smaller
runtime with only a subset of necessary modules.</p><p>During this journey, some Java APIs have been made private and others were moved to different
packages. This causes trouble for some well-known annotation processors like Dagger. The
generated code is usually annotated with <span class="pre-inline">@Generated</span> annotation,
which has been moved to a different package in JDK 9. In the case of an Android project written
in Kotlin (which has to use kapt to enable Dagger), the build fails on JDK 9 or newer due to a
<a href="https://github.com/google/dagger/issues/1449">missing @Generated annotation class</a>.
Dagger itself has a check for the target Java level and uses
<span class="pre-inline">@Generated</span> annotation from the correct package. However, there
was a <a href="https://youtrack.jetbrains.com/issue/KT-33050">bug in kapt</a> - it didn’t report
configured target Java level to Java compiler, failing the build and leaving poor developers
scratching their heads for hours.</p><p>The restructuralization of the JDK was actually wider than just moving Java classes around. The
Java compiler needed to be changed as well in order to understand the module system and know how
to handle its classpaths appropriately.</p><p>As a result, the <span class="pre-inline">-bootclasspath</span> compiler option (that was used to
include the <span class="pre-inline">android.jar</span> to the build) was removed, effectively
making all Android classes unavailable to the build. Projects that are written 100% in Kotlin
are not affected by this until Android view binding (or similar feature) is enabled. View
binding build step generates Java classes that need to be compiled by <span class="pre-inline">javac</span>.
As the generated classes have dependencies on <span class="pre-inline">android.jar</span>
classes, the compilation fails when the project is configured to target Java 9 or newer. This
limitation has been <a href="https://issuetracker.google.com/issues/139013660">known and tracked
</a> for quite a long time and now it has finally been resolved as part of AGP 7.0.0.</p><p>Other tools that also needed an update were D8 and R8, as they work directly with new Java
versions of class files.</p><h2>What to do to upgrade to Android Gradle Plugin 7.0?</h2><p>When a project with AGP 7.0 build is executed on JDK 8, the build will fail immediately with the
following error:</p><pre><code class="hljs"> An exception occurred applying plugin request [id: 'com.android.application']
Failed to apply plugin 'com.android.internal.application'.
Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8.
You can try some of the following options:
- changing the IDE settings.
- changing the JAVA_HOME environment variable.
- changing `org.gradle.java.home` in `gradle.properties`.
</code></pre><p>The only requirement is to use at least JDK 11 for Gradle when building the project. This can be
set through <strong>JAVA_HOME</strong> environmental variable,
<strong>org.gradle.java.home</strong> Gradle property or in <strong>Project Structure</strong>
dialog in Android Studio:</p>
<img alt="Project Structure dialog in Android Studio" src="/Blog/PublishingImages/Articles/android-java-release-train-01.png" data-themekey="#" />
<h2>Can we use new Java language features?</h2><p>Since Java 9, new language features are usually implemented in the Java compiler without any
impact on bytecode or JVM. These can be easily handled by Android’s D8 tool and can be used in
Android projects without a problem.</p><p>Example:</p><pre><code class="java hljs"> public void sayIt() {
var message = "I am a Java 10 inferred type running on Android";
System.out.println(message);
}
</code></pre><p>You just need to tell Gradle that the project is targeting new Java versions. This can be
configured in <span class="pre-inline">build.gradle.kts</span>:</p><pre><code class="kotlin hljs"> android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
</code></pre><p>When <span class="pre-inline">compileOptions</span> are not defined, the defaults come into play.
Up until AGP 4.1, the default Java compatibility level was set to a very ancient Java 6. Since
AGP 4.2, it has been bumped to (only slightly less ancient) Java 8.</p><h2>Can we use new Java APIs?</h2><p>Regrettably, Java library APIs are a completely different thing.</p><p>Java 8 APIs are available starting with Android 8 (API level 26). Some Java 9 APIs (like
<a href="https://developer.android.com/reference/java/util/List#of%28%29">List.of()</a>) are
available starting with Android 11 (API level 30). These APIs might also be available on older
Android versions through
<a href="https://developer.android.com/studio/write/java8-support#library-desugaring">Java APIs
desugaring</a>.</p><p>Hopefully, every future Android version will adopt more of these new Java APIs and make them
available for use on older Android versions via desugaring.</p><h2>Can we use the latest version - Java 15?</h2><p>We can use JDK 15 for running Gradle builds as it supports the latest Java version
<a href="https://docs.gradle.org/6.7/release-notes.html#support-for-java-15">since Gradle 6.7</a>.
</p><p>Unfortunately, we cannot use Java 15 language features in our code. In fact, we cannot use Java
14, 13 and 12 language features either, as the highest supported <span class="pre-inline">sourceCompatibility</span>
level is still Java 11. However, the limitation of R8 not being able to parse the latest Java
version class files <a href="https://issuetracker.google.com/issues/141587937#comment19">was resolved</a>
at the beginning of December 2020, so we can hope for Java 15 support arriving soon.</p><h2>How does this affect Kotlin projects?</h2><p>Not much. Kotlin compiler and toolchain are not affected by JDK used for Gradle build nor the
Java compatibility level set for a project.</p><p>However, when you use JDK 11 for build and Java 11 as a source compatibility level for Java
compiler, it is reasonable to use the same level as Kotlin target for JVM. This allows Kotlin to
generate code optimized for newer Java language versions:</p><pre><code class="kotlin hljs"> kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
</code></pre><p>When <span class="pre-inline">kotlinOptions</span> are not defined, the default
<span class="pre-inline">jvmTarget</span> is again set to a very ancient Java 6. Please define
your <span class="pre-inline">kotlinOptions</span>!</p><h2>The bottom line</h2><p>Better late than never, Java 11 has just arrived in the Android world. It won’t much change your
day to day work of writing Java code. It may not change your work of writing Kotlin code at all.
Nevertheless, it <em>is</em> a big thing for the ecosystem which promises easier upgrades in the
future, that will in turn allow for the depreciation of lots of outdated stuff. Both, the
Android team at Google and the Kotlin team at JetBrains may finally drop support for Java 6 and
Java 8 and focus on more contemporary language versions. That is something we would all profit
from.</p><p>
<br>
</p><p>Pavel Švéda<br></p><p>Twitter: <a href="https://twitter.com/xsveda">@xsveda</a><br><br></p>
<br> | | #android;#java;#kotlin;#gradle | | |
Architecture tests with ArchUnit, pt. 1: Reasons | | https://mobileit.cz/Blog/Pages/arch-unit-1.aspx | Architecture tests with ArchUnit, pt. 1: Reasons | <p>Good architecture is essential for a codebase to enjoy a long and happy life (the other crucial
ingredient is running automated unit tests, but that’s a different blog post). Nowadays there are many
sensible options, including our favorite for mobile apps, Clean arch, but no matter which one
you choose, you’ll need to document and enforce it somehow.</p><p>The former is traditionally accomplished by some form of oral history passed from one developer
to another (sometimes augmented by blurry photos of frantically scribbled whiteboard diagrams),
while the latter is sporadically checked during code reviews (if there’s time—which there
isn’t—and you remember all the rules yourself—which you don’t).</p><p>Or maybe you even have a set of gradually outdated wiki pages and fancy UML models created in
some expensive enterprise tool. Or complicated but incomplete rules written for rather arcane
static analysis frameworks. Or any other form of checks and docs, which are usually hard to
change, detached from the actual code and difficult and rather expensive to maintain.</p><p>But fret not! There is a new architectural sheriff in this JVM town of ours and he’s going to
take care of all of this—say hello to your new best friend, <a href="https://www.archunit.org/">ArchUnit</a>!
</p><h2>What’s all the fuss about?</h2><p>ArchUnit is a library to, well, unit test your architecture. There are other tools to
<em>check</em> your architecture, but the “unit testing” part of ArchUnit is actually its killer
feature.</p><p>While “normal” unit tests should describe behavior (not structure!) of the system under test,
ArchUnit cleverly leverages JVM and existing unit test frameworks to let you document
<em>and</em> check your architecture in a form of runnable unit tests, executable in your
current unit test environment (because you already have a strong suite of unit tests, right?).
Why exactly is this such a welcome improvement?</p><p>Well, it all boils down to the fundamental benefits of all unit tests: Because unit tests are
code, they are a precise, up-to-date, unambiguous, executable specification of the system. Docs
can be outdated and misleading, but unit tests either compile or don’t; they either pass or not.
Imagine opening a project you don’t know anything about, running its unit tests and seeing
this:</p>
<img alt="ArchUnit test results in Android Studio" src="/Blog/PublishingImages/Articles/arch-unit-1-01.png" data-themekey="#" />
<p>Suddenly the whole onboarding situation looks much brighter, doesn’t it?</p><h2>Show me the code</h2><p>Enough talk, let’s get down to business! If your test framework of choice is JUnit 4, put this
in your <span class="pre-inline">build.gradle.kts</span>:</p><pre><code class="kotlin hljs">dependencies {
testImplementation("com.tngtech.archunit:archunit-junit4:0.14.1")
}
</code></pre><p>There are artifacts for other test frameworks as well, just refer to the <a href="https://www.archunit.org/userguide/html/000_Index.html#_installation">docs</a>. Be
careful not to use older versions as this version contains important fixes for multi-module
projects containing Android libraries in a CI environment.</p><p>Now we can write our first architecture test:</p><pre><code class="kotlin hljs">@RunWith(ArchUnitRunner::class)
@AnalyzeClasses(packages = ["com.example.myapp"])
internal class UiLayerTest {
@ArchTest
val `view model subclasses should have correct name` =
classes().that().areAssignableTo(ViewModel::class.java)
.should().haveSimpleNameEndingWith("ViewModel")
}
</code></pre><p>And just like that, you now have one small naming convention documented and automatically
verified across your whole project. The API does a great job at being self-explanatory and we’ll
get into the details later, but let’s quickly recap what we have here:</p><p><span class="pre-inline">@AnalyzeClasses</span> annotation is one of the ways to specify what to
check. Here, we simply want to test all code in
the <span class="pre-inline">com.example.myapp</span> package and its subpackages. ArchUnit imports
and checks Java bytecode (not source files), which is why it works with Kotlin (or any other JVM
language), although it’s itself a pure Java library—another example of Kotlin’s stellar
interoperability with Java. <em>Where</em> ArchUnit actually gets this bytecode is a slightly
more complicated question, but that’s not important right now.</p><p>Anyway, we annotate our test cases with <span class="pre-inline">@ArchTest</span> and for the
shortest syntax, we use properties instead of functions. As with other unit tests, it’s a good
idea to leverage Kotlin’s escaped property names for more readable test outputs.</p><p>And then finally for the main course: ArchUnit has a comprehensive, very expressive and really
rather beautiful fluent API for specifying the predicates and their expected outcomes. It’s not
Java reflection and being a pure Java library, ArchUnit doesn’t have constructs for
Kotlin-exclusive language elements, but it’s still more than powerful enough.</p><h2>Test the tests</h2><p>Now run the test. Most projects probably stick to this naming convention, so the result bar in
your favorite IDE might be green already. But wait! How do we know that the tests actually
work?</p><p>Although they may appear a bit strange, ArchUnit tests are still unit tests and we should treat them as
such. That means we should follow the famous red-green-refactor cycle, albeit modified, because
you absolutely need to see the test fail and it must fail for the correct reason. This is the
only time when you actually test your tests!</p><p>What does this mean for ArchUnit tests? The difference from normal TDD for our specific test case
is that we cannot simply write the test first and watch it fail, because if there are no view
models in the project yet, the test will pass. So we need to cheat a little and break the
architecture on purpose, manually, by creating a temporary class violating the naming convention
in the main source set. Then we run the test, watch it fail, delete the class and watch the test
go green (the refactoring part isn’t really applicable here).</p><p>This looks like extra work and granted, it can be a bit tedious, but the red part of the cycle
simply cannot be skipped, ever. There is a myriad of logical and technical errors that can
result in the test being wrong or not executed at all and this is your only chance to catch
them. There’s nothing worse than a dead lump of code giving you a false sense of security.</p><p>And there’s one more thing to borrow from TDD playbook: Perhaps you are doing a code review or
approving pull request and you discover some construction violating a rule that you haven’t
thought of before. What to do with that? As with all new bugs, don’t rush fixing the bug! The
first thing you should do is write a test exposing the bug (the red part of the cycle)—that
means writing an ArchUnit rule which will fail with the offending code. Only after that, make the
test green. This way, you’ll slowly make your test suite more precise, with the added bonus that
future regressions will be prevented as well.</p><h2>Be careful what you test for</h2><p>We’ll take a look at all ArchUnit’s fluent API constructs in a future post, but there’s an
important detail we need to discuss before that.</p><p>Basically all simple ArchUnit rules follow the form <span class="pre-inline">(no) LANGUAGE_ELEMENT that PREDICATE should (not) CONDITION</span>.
From a mathematical point of view, these rules are <em>implications</em>.</p><p>An implication looks like this:</p>
<img alt="Venn diagram of an implication" src="/Blog/PublishingImages/Articles/arch-unit-1-02.png" data-themekey="#" />
<p>For our example test above (and many other tests that you’ll write), it means that the test will
pass for <em>all</em> these variants:</p><ul><li>class is not assignable to <span class="pre-inline">ViewModel::class</span> and does not
have a simple name ending with <span class="pre-inline">ViewModel</span> (that’s OK)
</li><li>class is assignable to <span class="pre-inline">ViewModel::class</span> and has a simple
name ending with <span class="pre-inline">ViewModel</span> (that’s also OK)
</li><li>class is not assignable to <span class="pre-inline">ViewModel::class</span> and has a simple
name ending with <span class="pre-inline">ViewModel</span> (the criss-crossed part of the
diagram; we don’t really want to allow this)
</li></ul><p>It seems that what we really want is an equivalence:</p>
<img alt="Venn diagram of an equivalence" src="/Blog/PublishingImages/Articles/arch-unit-1-03.png" data-themekey="#" />
<p>Although ArchUnit doesn’t (yet?) have API elements to specify equivalences, they are fairly
simple to create: Because A ↔ B is the same as (A → B) AND (B → A), we just need to add another
test to our suite:</p><pre><code class="kotlin hljs">@ArchTest
val `classes named ViewModel should have correct super class` =
classes().that().haveSimpleNameEndingWith("ViewModel")
.should().beAssignableTo(ViewModel::class.java)
</code></pre><p>This way, the offending case which the first test didn’t catch (class name ends with
<span class="pre-inline">ViewModel</span>, but it is not assignable to
<span class="pre-inline">ViewModel.java</span>) is prevented.</p><h2>Best thing since sliced bread</h2><p>I don’t want to use the word game-changer, but I just did. Since we started adding ArchUnit tests
to our projects, we have seen significant improvements in developer productivity and the health of our
codebases. Compared to similar solutions, ArchUnit’s simple integration, ease of use and
expressive powers are unmatched.</p><p>We’ve only scratched the surface of what’s possible, so <a href="/Blog/Pages/arch-unit-2.aspx">next
time</a>, we’ll dive into ArchUnit APIs to discover some nifty architecture testing goodness!
</p>
| | #architecture;#jvm;#tdd;#android | | |
Architecture tests with ArchUnit, pt. 2: Rules | | https://mobileit.cz/Blog/Pages/arch-unit-2.aspx | Architecture tests with ArchUnit, pt. 2: Rules | <p>In the <a href="/Blog/Pages/arch-unit-1.aspx">first part</a> of this series, we’ve had a glimpse
of an architecture test written with the almighty ArchUnit, but of course there’s much more!
Although ArchUnit’s API looks like Java Reflection API, it also contains powerful constructs to
describe dependencies between code or predefined rules for testing popular architecture styles.
Let’s see what we’ve got to play with!</p><h2>First things first</h2><p>ArchUnit rules follow the pattern
<span class="pre-inline">(no) LANGUAGE_ELEMENT that PREDICATE should (not) CONDITION</span>.
So what language elements can we use?</p><p>All tests begin with static methods in <span class="pre-inline">ArchRuleDefinition</span> class
(but please import the class to make the rules more readable).</p><p>We can start with <span class="pre-inline">classes</span> or <span class="pre-inline">constructors</span>
which are pretty self-explanatory. We also have <span class="pre-inline">theClass</span> if you
want to be brief and specific. If possible, always use the overload that takes <span class="pre-inline">Class<*></span> argument instead of the overload that takes
String to make your tests resistant to future refactorings; the same goes for other methods with
these argument choices.</p><p>Next, we have <span class="pre-inline">fields</span>, <span class="pre-inline">methods</span> and
<span class="pre-inline">members</span>. When testing Kotlin code, be extra careful with <span class="pre-inline">fields</span> because Kotlin properties are <em>not</em> Java
fields. Remember that ArchUnit checks compiled bytecode and every Kotlin property is actually
compiled to getter method by prepending the <span class="pre-inline">get</span> prefix, setter
method by prepending the <span class="pre-inline">set</span> prefix (only for <span class="pre-inline">var</span> properties) and private field with the same name as the
property name, but <em>only for properties with backing fields</em>. When testing Kotlin
properties, it may sometimes be safer to test their generated getters or setters. Anyway, these
subtle details show the importance of watching your test fail.</p><p>We also have a slightly mysterious <span class="pre-inline">codeUnits</span> method—it means
simply anything that can access other code (including methods, constructors, initializer blocks,
static field assignments etc.).</p><p>All methods mentioned above also have their negated variants. Now what can we do with all
this?</p><h2>Packages, packages everywhere</h2><p>Consistent packaging is one of the most important things to get right in the project. We strongly
prefer packaging by features first, then by layers. This concept sometimes goes by the name of
“screaming architecture”: For example, when you open an Android project and you see top level
packages such as <span class="pre-inline">map</span>, <span class="pre-inline">plannedtrips</span>, <span class="pre-inline">routeplanning</span>,
<span class="pre-inline">speedlimits</span>, <span class="pre-inline">tolls</span>, <span class="pre-inline">vehicles</span> or <span class="pre-inline">voiceguidance</span>,
you’ll get a pretty good idea about what the app is really about. But if instead you are looking at
packages such as <span class="pre-inline">activities</span>, <span class="pre-inline">fragments</span>, <span class="pre-inline">services</span>, <span class="pre-inline">di</span>, <span class="pre-inline">data</span>, <span class="pre-inline">apis</span>, etc., it won’t tell you much about the
application (every Android app will contain at least some of those things).</p><p>ArchUnit can enforce correct package structure, prevent deadly cyclic dependencies and much more.
Let’s see a few examples (the actual packages mentioned are not important, use what is
convenient for your project):</p><pre><code class="kotlin hljs">@ArchTest
val `every class should reside in one of the specified packages` =
classes().should().resideInAnyPackage(
"..di",
"..ui",
"..presentation",
"..domain",
"..data"
)
</code></pre><p>The two dots mean “any number of packages including zero”, so this test says that every class
must exist in one of these predefined leaf packages.</p><p>This test however doesn’t say anything about the package structure <em>above</em> the leaves, so
if you want to be more strict, you can write this, for example:
</p><pre><code class="kotlin hljs">@ArchTest
val `every class should reside in one of the specified packages` =
classes().should().resideInAnyPackage(
"com.example.myapp.*.di",
"com.example.myapp.*.ui",
"com.example.myapp.*.presentation",
"com.example.myapp.*.domain",
"com.example.myapp.*.data"
)
</code></pre><p>The star matches any sequence of characters excluding the dot (for our sample packaging, in its
place there would be a feature name), but you can also use <span class="pre-inline">**</span> which
matches any sequence of characters <em>including</em> the dot. Together with the two dot
notation, you can express pretty much any package structure conceivable (see the Javadoc for
<span class="pre-inline">PackageMatcher</span> class).</p><h2>Building the walls</h2><p>One popular architectural style is to divide the code into layers with different levels. We can
define layer level simply as the code’s distance from inputs/outputs—so things like UI, DB or
REST clients are pretty low-level, whereas business logic and models are on the opposite side
and the application logic sits somewhere in the middle.</p><p>In this case, it’s a good idea to isolate higher-level layers from external dependencies such as
platform SDK or other invasive frameworks and libraries, since higher levels should be more
stable and independent of the implementation details in lower layers. ArchUnit can help us with
that:</p><pre><code class="kotlin hljs">@ArchTest
val `higher-level classes should not depend on the framework` =
noClasses().that().resideInAnyPackage("..presentation..", "..domain..")
.should().dependOnClassesThat().resideInAnyPackage(
"android..",
"androidx..",
"com.google.android..",
"com.google.firebase.."
/* and more */
)
</code></pre><p>Only a few lines and those pesky imports have no way of creeping in your pristine (and now even
fairly platform-independent!) code.</p><h2>Piece(s) of cake</h2><p>Speaking of layers, we should not only handle their dependencies on the 3rd party code, but of
course also the direct dependencies between them. Although we can use the constructs mentioned
above, ArchUnit has another trick up to its sleeve when it comes to layered architectures.</p><p>Suppose we have defined these layers and their <em>code</em> dependencies:</p>
<img alt="Example layer structure" src="/Blog/PublishingImages/Articles/arch-unit-2-01.png" data-themekey="#" />
<p>This is just an example, but let’s say that the domain layer is the most high-level, so it must
not depend on anything else; presentation and data layers can depend on stuff from domain, UI
can see view models in presentation layer (but view models must not know anything about UI) and
DI sees all to be able to inject anything (and ideally, no other layer should see DI layer,
because classes should not know anything about how they are injected; alas this is not always
technically possible).</p><p>Whatever your actual layers are, the most important thing is that all dependencies go in one
direction only, from lower level layers to higher level layers (this is the basic idea of Clean
architecture). ArchUnit can encode these rules in one succinct test:</p><pre><code class="kotlin hljs">@ArchTest
val `layers should have correct dependencies between them` =
layeredArchitecture().withOptionalLayers(true)
.layer(DOMAIN).definedBy("..domain")
.layer(PRESENTATION).definedBy("..presentation")
.layer(UI).definedBy("..ui")
.layer(DATA).definedBy("..data")
.layer(DI).definedBy("..di")
.whereLayer(DOMAIN).mayOnlyBeAccessedByLayers(DI, PRESENTATION, DATA)
.whereLayer(PRESENTATION).mayOnlyBeAccessedByLayers(DI, UI)
.whereLayer(UI).mayOnlyBeAccessedByLayers(DI)
.whereLayer(DATA).mayOnlyBeAccessedByLayers(DI)
.whereLayer(DI).mayNotBeAccessedByAnyLayer()
</code></pre><p>How does it work? <span class="pre-inline">layeredArchitecture()</span> is a static method in the
<span class="pre-inline">Architectures</span> class (again, please import it). First we need to
actually define our layers: <span class="pre-inline">layer</span> declares the layer (the
argument is simply any descriptive String constant) and <span class="pre-inline">definedBy</span> specifies a package by which the layer is, well,
defined (you can use package notation which we’ve seen before; you can also use a more general
predicate). Without <span class="pre-inline">withOptionalLayers(true)</span>
call, ArchUnit will require that all layers exist, which in a multi-module project might not
necessarily be true (some modules might for example contain only domain stuff).</p><p>This rather short test will have an enormous impact on your codebase—correctly managed
dependencies are what prevents your project from becoming a giant mess of spaghetti code.</p><h2>Inner beauty</h2><p>We’ve sorted the layers and packages, but what about their content? Take for example the domain
layer: Continuing our rather simplified example, we want only <span class="pre-inline">UseCase</span> classes and <span class="pre-inline">Repository</span>
interfaces in there. Furthermore, we want for these classes to follow certain name conventions
and to extend correct base classes.</p><p>We can express all these requirements by the following set of ArchUnit tests:</p><pre><code class="kotlin hljs">@ArchTest
val `domain layer should contain only specified classes` =
classes().that().resideInAPackage("..domain..")
.should().haveSimpleNameEndingWith("UseCase")
.andShould().beTopLevelClasses()
.orShould().haveSimpleNameEndingWith("Repository")
.andShould().beInterfaces()
@ArchTest
val `classes named UseCase should extend correct base class` =
classes().that().haveSimpleNameEndingWith("UseCase")
.should().beAssignableTo(UseCase::class.java)
@ArchTest
val `use case subclasses should have correct name` =
classes().that().areAssignableTo(UseCase::class.java)
.should().haveSimpleNameEndingWith("UseCase")
</code></pre><p>And as a bonus example for Android fans, you can, of course, be even more specific:</p><pre><code class="kotlin hljs">@ArchTest
val `no one should ever name fields like this anymore ;)` =
noFields().should().haveNameMatching("m[A-Z]+.*")
</code></pre><h2>Endless power</h2><p>We’ve seen only a smart part of the ArchUnit API, but there’s almost nothing that ArchUnit tests
cannot handle. You can examine all Java constructs and their wildest combinations (but always be
aware of Kotlin-Java interoperability details and test your tests), go explore!</p><p>Next time, we’ll take a look at some advanced features
and configuration options.</p> | | #architecture;#jvm;#tdd;#android | | |
Architecture tests with ArchUnit, pt. 3: Advanced stuff | | https://mobileit.cz/Blog/Pages/arch-unit-3.aspx | Architecture tests with ArchUnit, pt. 3: Advanced stuff | <p>ArchUnit has many tricks up to its sleeve. We’ve <a href="/Blog/Pages/arch-unit-2.aspx">already
seen</a> how to check package structure, language elements such as classes, fields, and methods, and
how to make sure your layered architecture is sound. But there’s more! Let’s take a look at some
advanced concepts.</p><h2>Slicing and dicing</h2><p>As we’ve seen in the previous part of this series, ArchUnit makes it easy to test layers
and their relationships. However, dividing your codebase only horizontally isn’t enough—in all
but very tiny projects, you’d end up with huge layers containing too many unrelated classes.
Thus we need to divide our code vertically as well, usually by user-facing features and
project-specific or general-purpose libraries. Those features or libraries are often compilation
units (e.g., Gradle modules), but that doesn’t need to concern us at this moment.</p>
<img alt="Package and module structure" src="/Blog/PublishingImages/Articles/arch-unit-3-01.png" data-themekey="#" />
<p>So, if we have defined several vertical slices of our codebase, we would like to test their
relationships as well. Horizontal layer tests work <em>across</em> all slices, so they won’t
help us in this case, but ArchUnit has us covered with its high-level slices API:</p><pre><code class="kotlin hljs">@ArchTest
val `feature slices should not depend on each other` =
slices().matching("com.example.myapp.feature.(*)..")
.should().notDependOnEachOther()
</code></pre><p>This is a good rule to have, as you usually want your features to be isolated from each other.
How does it work?</p><p>First, we define the matcher which slices the codebase vertically: It takes package notation
which we’ve seen in the previous rules. The matcher group denoted by parentheses specifies the
actual slicing point as well as the slice’s name shown in error messages.</p><p>In this case, code units residing in the following example packages <em>and</em> their
subpackages would constitute separate slices: <span class="pre-inline">com.example.myapp.feature.login</span>,
<span class="pre-inline">com.example.myapp.feature.map</span> or <span class="pre-inline">com.example.myapp.feature.navigation</span>.
</p>
<img alt="Feature slices" src="/Blog/PublishingImages/Articles/arch-unit-3-02.png" data-themekey="#" />
<p>In contrast, writing <span class="pre-inline">matching("com.example.myapp.feature.(**)")</span>, then
<span class="pre-inline">com.example.myapp.feature.login.model</span> and <span class="pre-inline">com.example.myapp.feature.login.data</span> would constitute
<em>different</em> slices.</p>
<img alt="Layer slices" src="/Blog/PublishingImages/Articles/arch-unit-3-03.png" data-themekey="#" />
<p>Be careful, as this distinction might be rather subtle!</p><p>As always when practicing TDD, you need to see your test fail for the right reason—in
this case that means creating a temporary file that intentionally breaks the test and deleting it
afterwards.</p><p>The rest of the rule is simple: After the usual <span class="pre-inline">should()</span>
operator, we have only two options: <span class="pre-inline">notDependOnEachOther()</span> tests
that, well, no slice depends on any other (unlike the layer dependency tests, these tests are
bi-directional), whereas <span class="pre-inline">beFreeOfCycles()</span>
allows dependencies between the slices, but only in one direction at most.</p><p>Generally speaking, it may be a good idea to run the <span class="pre-inline">beFreeOfCycles()</span> test on every slice (using one of the two test
variants mentioned above) in your codebase, whereas some types of slices (typically
libraries, but not features) may be permitted to depend on each other in one direction.
</p><p>But what if your codebase isn’t structured in such a convenient way? For example, there might be
no middle <span class="pre-inline">feature</span> package distinguishing features from
libraries, or worse, the package structure may be completely inconsistent.</p><p>For such cases, ArchUnit contains handy <span class="pre-inline">SliceAssignment</span> interface which
you can use to assign slices to classes in a completely arbitrary way:</p><pre><code class="kotlin hljs">@ArchTest
private val features = object : SliceAssignment {
override fun getIdentifierOf(javaClass: JavaClass) = when {
javaClass.packageName.startsWith("com.example.myapp.login") -> SliceIdentifier.of("feature-login")
javaClass.name.contains("map") -> SliceIdentifier.of("feature-map")
/* ... whatever you need ... */
else -> SliceIdentifier.ignore()
override fun getDescription() = "this will be added to the error message"
}
</code></pre><p>Strings given to <span class="pre-inline">SliceIdentifier</span> are arbitrary constants
identifying the slice and are also shown in error messages.</p><p>There is an important difference in what you write in that <span class="pre-inline">else</span>
branch: If you return <span class="pre-inline">SliceIdentifier.of("remaining")</span>, then all
classes not matching the previous cases will be assigned to the <span class="pre-inline">"remaining"</span>
slice (which means they will be tested against other slices), whereas if you return <span class="pre-inline">SliceIdentifier.ignore()</span>, those classes won’t participate in
the test at all (both options have their uses, but be careful not to confuse them).</p><p>We can then use our slice assignment like this:</p><pre><code class="kotlin hljs">slices().assignedFrom(features).should().notDependOnEachOther()</code></pre><h2>Why be in when you could be out?</h2><p>As we’ve learned, ArchUnit runs its tests on compiled bytecode. But where do these classes come
from?</p><p>There is more than one way to specify that, but probably the most succinct is to use this
annotation:</p><pre><code class="kotlin hljs">@RunWith(ArchUnitRunner::class)
@AnalyzeClasses(packages = ["com.example.myapp"])
internal class MyArchTest
</code></pre><p>Besides using String literals, we can specify packages with Classes or, if that’s not enough,
completely customize the sources using ArchUnit’s <span class="pre-inline">LocationProvider</span>. In every case, please note that ArchUnit
looks for packages within the current classpath and <em>all</em> classes must be imported
for ArchUnit to be able to work correctly—if you import class <span class="pre-inline">X</span>,
you need to import all its dependencies as well, transitively, otherwise ArchUnit will have only
a limited amount of information to work with and the tests might yield false positives, giving you
a false sense of security.</p><p>Now we have all the class locations that ArchUnit needs, but there are situations when we don’t
necessarily need to test against all of the classes in there. We can filter the classes with
<span class="pre-inline">importOptions</span>:</p><pre><code class="kotlin hljs">@RunWith(ArchUnitRunner::class)
@AnalyzeClasses(
packages = ["com.example.myapp"],
importOptions = [
DoNotIncludeArchives::class,
DoNotIncludeTests::class,
DoNotIncludeAndroidGeneratedClasses::class
]
)
internal class MyArchTest
</code></pre><p>ArchUnit comes with a couple of handy predefined import options, such as the first two, or we can
write our own, which is simple enough:</p><pre><code class="kotlin hljs">internal class DoNotIncludeAndroidGeneratedFiles : ImportOption {
companion object {
private val pattern = Pattern.compile(".*/BuildConfig\\.class|.*/R(\\\$.*)?\\.class|.*Binding\\.class")
}
override fun includes(location: Location) = location.matches(pattern)
}
</code></pre><p>This import option rejects Android <span class="pre-inline">BuildConfig</span>, <span class="pre-inline">R</span>, and <span class="pre-inline">Binding</span> classes. The location
argument passed here is platform-independent, so you don’t have to worry about path separators
and such things.</p><p>But what if we need to be more granular? For example, sometimes we might need to ignore certain
classes on a per-test basis, basically adding ad-hoc exceptions to our pristine rules, because,
you know, the real world happens. This is a slippery slope, so don’t forget to document such
situations, but it’s simple enough to do by adding <span class="pre-inline">or</span> clause to
the rule:</p><pre><code class="kotlin hljs">@ArchTest
val `domain layer should contain only specified classes` =
classes().that().resideInAPackage("..domain..")
.should().haveSimpleNameEndingWith("Repository").andShould().beInterfaces()
.orShould().haveSimpleNameEndingWith("Controller").andShould().beInterfaces()
.orShould().haveSimpleNameEndingWith("UseCase")
.orShould().be(Data::class.java)
.because("only repositories, controllers and use cases are permitted in domain and Data is special wrapper for results from those classes")
</code></pre><p>Be specific as possible with your exceptions as you don’t want them to accidentally match more
language elements than intended—here, using class literal instead of String is safe and future
refactoring-proof.</p><p><span class="pre-inline">Because</span> clause allows you to add more detail to the default error
message generated from the test case name. There is also <span class="pre-inline">`as`</span>
clause (ArchUnit being written in Java, it accidentally overloads Kotlin’s keyword, so don’t
forget to escape it or create an alias extension function with a Kotlin-friendly name) that allows you to completely override the error message.</p><h2>Godspeed</h2><p>Because ArchUnit does a lot under the hood (bytecode examination and cycle checks tend to be
expensive, among other things), the tests themselves, although written using unit test
infrastructure, seldom have the <em>speed</em> of normal unit tests. The speed penalty
can be
quite severe, so organize your test suites in a way that ArchUnit tests don’t slow you down when
developing your code using the classic TDD cycle of red-green-refactor, which has to be always
blazingly fast.</p><p>That said, all ArchUnit’s features working together allow us to express our architecture in a
clean, succinct, and <em>executable</em> way, which is invaluable in larger projects. Consistency
and
clarity are virtues that may very well make the difference between a codebase that is easy to
learn and maintain and one that becomes a big steaming pile of unreadable mess.</p> | | #architecture;#jvm;#tdd;#android | | |
Architecture tests with ArchUnit, pt. 4: Extensions & Kotlin support | | https://mobileit.cz/Blog/Pages/arch-unit-4.aspx | Architecture tests with ArchUnit, pt. 4: Extensions & Kotlin support | <p>ArchUnit is <a href="/Blog/Pages/arch-unit-3.aspx">immensely capable on its own</a> and that's a great merit on its own, but it doesn’t stop there—ArchUnit’s power can be augmented by adding
custom matchers, language elements, and even whole new concepts. In this post, we’ll look at how
we can achieve that and then we’ll see if we can leverage these capabilities to support even
Kotlin-exclusive language elements in ArchUnit tests (spoiler alert: yes, we can!). Ready?</p><h2>Shiny new things</h2><p>As we’ve mentioned several times, ArchUnit rules look like this:</p><pre><code class="kotlin hljs">(no) LANGUAGE_ELEMENT that PREDICATE should (not) CONDITION</code></pre><p>As it turns out, by thoroughly following the open/closed principle, ArchUnit allows us to supply our
own language elements, predicates and conditions. These can be simple aggregations of existing
built-in predicates or conditions to facilitate reuse, or we can invent entirely new
domain-specific concepts to utilize in our architecture tests. So how is it done?</p><p>To create a custom language element, predicate or condition, we need to extend
<span class="pre-inline">AbstractClassesTransformer</span>, <span class="pre-inline">DescribedPredicate</span>,
or <span class="pre-inline">ArchCondition</span> respectively. Each abstract base class takes
one type argument—the language element it operates on (ArchUnit provides for example <span class="pre-inline">JavaClass</span>, <span class="pre-inline">JavaMember</span>, <span class="pre-inline">JavaField</span> or <span class="pre-inline">JavaCodeUnit</span> and
we can even create our own; these are reflection-like models read from compiled bytecode). They
also have one constructor argument, a String that is appended to the rule description in error
messages.</p><p>The simplest one to implement is <span class="pre-inline">DescribedPredicate</span>—we need to
override its <span class="pre-inline">apply</span> method which takes the examined language
element and returns Boolean:</p><pre><code class="kotlin hljs">val myPredicate = object : DescribedPredicate("rule description") {
override fun apply(input: JavaClass): Boolean = // …
}</code></pre><p><span class="pre-inline">ArchCondition</span> is slightly more involved, as its <span class="pre-inline">check</span> function takes the language element as well. In addition, it also takes <span class="pre-inline">ConditionEvents</span> collection, which is used to
return the result of the evaluation, as this function doesn’t directly return anything:
</p><pre><code class="kotlin hljs">val myCondition = object : ArchCondition("condition description") {
override fun check(item: JavaClass, events: ConditionEvents) {
if (item.doesNotSatisfyMyCondition()) {
events.add(SimpleConditionEvent.violated(item, "violation description"))
}
}
}</code></pre><p><span class="pre-inline">AbstractClassesTransformer</span> has a <span class="pre-inline">doTransform</span>
method which takes a collection of <span class="pre-inline">JavaClass</span>es and
transforms it to another collection. Elements of the output collection can be <span class="pre-inline">JavaClass</span>es as well, different built-in types or even custom
classes. The transformation may comprise any number of operations including mapping or
filtering:</p><pre><code class="kotlin hljs">val myTransformer = object : AbstractClassesTransformer("items description") {
override fun doTransform(collection: JavaClasses): Iterable =
collection.filter { /* ... */ }.map { /* ... */ }
}</code></pre><p>Anyway, we can use our custom transformers, predicates, and conditions like this:</p><pre><code class="kotlin hljs">all(encryptedRepositories)
.that(handleGdprData)
.should(useSufficientlyStrongCrypthographicAlgorithms)</code></pre><p>and they can, of course, be combined with the built-in ones.</p><p>Besides promoting reuse, custom transformers, predicates, and conditions are particularly good at
increasing the level of abstraction of your tests—it’s better to describe your system using its
domain language instead of low-level, opaque technical terms.</p><h2>Gimme some Kotlin lovin’</h2><p>As promised, now it’s the time to tackle the last thing we’d like to have in ArchUnit
tests—Kotlin support.</p><p>Because ArchUnit reads compiled bytecode and Kotlin has killer Java interoperability, we can get
pretty far out of the box, but we still can’t directly test for Kotlin stuff like sealed and
data classes, objects, typealiases, suspending functions etc. To find out how that could be
possible, we need to take a slight detour first.</p><p>When targeting JVM (or Android), Kotlin compiler outputs JVM bytecode. Adding custom bytecodes
for Kotlin-exclusive constructs is of course out of the question, so the compiler must resort to
clever tricks to convert Kotlin stuff to vanilla JVM bytecode. Now, there’s some wiggle room
(JVM bytecode allows some things that Java as a language doesn’t), but still, to achieve
Kotlin’s stellar level of Java interoperability, the compiler must mostly play by Java’s
rules.</p><p>To achieve that, for example, Kotlin compiler generates getters, setters and backing fields for
properties. It also creates encapsulating classes for top level functions and properties, adds new
functions to existing classes (to support data classes) or adds parameters to existing functions
(this is one of the tricks behind suspending functions).</p><p>As a result, when examining the bytecode alone, those Kotlin concepts effectively disappear. But
for Kotlin compiler to be able to compile Kotlin code against another <em>already compiled</em>
Kotlin code (and to see it as Kotlin code, not generic JVM code), this information must be
preserved somewhere.</p><p>Take for example this simple data class:</p><pre><code class="kotlin hljs">data class Money(val amount: BigDecimal, val currency: Currency)</code></pre><p>IntelliJ Idea/Android Studio lets us see bytecode generated from this Kotlin code, which in turn
can be (in most cases) decompiled to equivalent Java code. If we do that with the <span class="pre-inline">Money</span> class, we’ll see something like this:</p><pre><code class="java hljs">@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 1,
d1 = {"\u0000,\n\u0002\u0018\u0002\n\ /* rest omitted */ },
d2 = {"Lcom/example/myapp/Money;", "", "amount", "Ljava/math/BigDecimal;", "currency", "Ljava/util/Currency;", "(Ljava/math/BigDecimal;Ljava/util/Currency;)V", "getAmount", "()Ljava/math/BigDecimal;", "getCurrency", "()Ljava/util/Currency;", "component1", "component2", "copy", "equals", "", "other", "hashCode", "", "toString", "", "app-main"}
)
public final class Money {
@NotNull private final BigDecimal amount;
@NotNull private final Currency currency;
@NotNull public final BigDecimal getAmount() { return this.amount; }
@NotNull public final Currency getCurrency() { return this.currency; }
/* rest omitted */</code></pre><p>Bingo! The Java part is pretty straightforward, but it looks like that strange <span class="pre-inline">@Metadata</span> stuff
might be what we need. Indeed, the documentation for <span class="pre-inline">@Metadata</span>
says that “This annotation is present on any class file produced by the Kotlin compiler and is
read by the compiler and reflection.” Its arguments contain various interesting Kotlin-exclusive
bits and pieces related to the class and because it has runtime retention, it will be stored in
binary files, which means we can read them from our ArchUnit tests! If only we could make sense
of that gibberish inside the annotation…</p><h2>Metadata dissection</h2><p>It turns out that we can! There’s a small official library to do just that.</p><p>First, add JVM metadata library to your build script:</p><pre><code class="kotlin hljs">dependencies {
testImplementation("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0")
}</code></pre><p>Then, our plan of attack is this:</p><ol><li>The starting point is the input of our custom transformer, predicate, or condition, which in
this case will be ArchUnit’s <span class="pre-inline">JavaClass</span> object.
</li><li>ArchUnit can read annotations on the <span class="pre-inline">JavaClass</span> object, so we
examine if Kotlin’s <span class="pre-inline">@Metadata</span> annotation is present.
</li><li>If it is, we use the <a href="https://github.com/JetBrains/kotlin/blob/master/libraries/kotlinx-metadata/jvm/ReadMe.md">kotlinx-metadata</a>
library to read the actual metadata. (KotlinPoet has a <a href="https://square.github.io/kotlinpoet/kotlinpoet_metadata/">higher-level API</a>
based on
kotlinx-metadata, which presumably might be a little bit nicer to use; we’ll just use the
basic API here, as the end result will be the same in either case.)
</li><li>We expose the data in some easily digestible object so we can write simple and readable
assertions about it.
</li></ol><p>To make an already long story short, here is the first piece of the puzzle—transformation from
ArchUnit’s <span class="pre-inline">JavaClass</span> to kotlinx-metadata <span class="pre-inline">KmClass</span> model:</p><pre><code class="kotlin hljs">private fun JavaClass.toKmClass(): KmClass? = this
.takeIf { it.isAnnotatedWith(Metadata::class.java) }
?.getAnnotationOfType(Metadata::class.java)
?.let { metadata ->
KotlinClassMetadata.read(
KotlinClassHeader(
kind = metadata.kind,
metadataVersion = metadata.metadataVersion,
bytecodeVersion = metadata.bytecodeVersion,
data1 = metadata.data1,
data2 = metadata.data2,
extraString = metadata.extraString,
packageName = metadata.packageName,
extraInt = metadata.extraInt
)
)
}
?.let { (it as? KotlinClassMetadata.Class)?.toKmClass() }</code></pre><p>If a given <span class="pre-inline">JavaClass</span> is annotated with <span class="pre-inline">@Metadata</span>,
this extension reads the annotation and
converts it to a <span class="pre-inline">KotlinClassMetadata</span> object (mapping the
annotation attributes to the
corresponding properties of <span class="pre-inline">KotlinClassHeader</span> along the way).
</p><p><span class="pre-inline">KotlinClassMetadata</span> is a sealed class and its subclasses
represent various different kinds of classes generated by the Kotlin compiler. There are a few
of them, but to keep things simple we are interested only in “real” classes
(<span class="pre-inline">KotlinClassMetadata.Class</span>) from which we finally extract the
rich <span class="pre-inline">KmClass</span> model (and return
null in all other cases).</p><p>To make our life easier later, we also add this handy extension:</p><pre><code class="kotlin hljs">private fun JavaClass.isKotlinClassAndSatisfies(predicate: (KmClass) -> Boolean): Boolean =
this.toKmClass()?.let { predicate(it) } == true</code></pre><h2>Grand finale</h2><p>Now we can finally write our transformers, predicates, and conditions. Because they will be all
quite similar, let’s create factory methods for them first:</p><pre><code class="kotlin hljs">fun kotlinClassesTransformer(description: String, predicate: (KmClass) -> Boolean) =
object : AbstractClassesTransformer(description) {
override fun doTransform(collection: JavaClasses): Iterable =
collection.filter { it.isKotlinClassAndSatisfies(predicate) }
}</code></pre><pre><code class="kotlin hljs">fun kotlinDescribedPredicate(description: String, predicate: (KmClass) -> Boolean) =
object : DescribedPredicate(description) {
override fun apply(javaClass: JavaClass) =
javaClass.isKotlinClassAndSatisfies(predicate)
}</code></pre><pre><code class="kotlin hljs">fun kotlinArchCondition(
ruleDescription: String,
violationDescription: String,
predicate: (KmClass) -> Boolean
) = object : ArchCondition(ruleDescription) {
override fun check(javaClass: JavaClass, events: ConditionEvents) {
if (!javaClass.isKotlinClassAndSatisfies(predicate)) {
events.add(SimpleConditionEvent.violated(javaClass, "$javaClass $violationDescription"))
}
}
}</code></pre><p>And now, finally, we have everything ready to write things we can actually use in our ArchUnit
tests—for example:</p><pre><code class="kotlin hljs">val kotlinSealedClasses = kotlinClassesTransformer("Kotlin sealed classes") {
it.sealedSubclasses.isNotEmpty()
}</code></pre><pre><code class="kotlin hljs">val areKotlinDataClasses = kotlinDescribedPredicate("are Kotlin data classes") {
Flag.Class.IS_DATA(it.flags)
}</code></pre><pre><code class="kotlin hljs">val beKotlinObjects = kotlinArchCondition("be Kotlin objects", "is not Kotlin object") {
Flag.Class.IS_OBJECT(it.flags)
}</code></pre><p>The predicate lambdas operate on <span class="pre-inline">KmClass</span> instances. <span class="pre-inline">KmClass</span> is quite a low-level but powerful API
to examine <span class="pre-inline">@Metadata</span> annotation content. <span class="pre-inline">KmClass</span> has direct methods or properties for some
Kotlin constructs, while others can be derived from its flags. Sometimes it takes a little bit
of exploration, but all Kotlin-specific stuff is there. Or, for a higher-level API to do the
same, see <a href="https://square.github.io/kotlinpoet/kotlinpoet_metadata/">KotlinPoet
metadata</a>.</p><p>Now we can write tests such as:</p><pre><code class="kotlin hljs">all(kotlinSealedClasses)
.that(resideInAPackage("..presentation"))
.should(haveSimpleNameEndingWith("State"))</code></pre><pre><code class="kotlin hljs">classes()
.that(resideInAPackage("..model")).and(areKotlinDataClasses)
.should(implement(Entity::class.java))</code></pre><pre><code class="kotlin hljs">classes()
.that().areTopLevelClasses().and().areAssignableTo(UseCase::class.java)
.should(beKotlinObjects)</code></pre><p>So there you have it—support for Kotlin constructs in ArchUnit. The sky’s the limit, now your
codebase can be more robust than ever!
</p><h2>Conclusion</h2><p>Well, this was quite a journey! The benefits of having even a small suite of ArchUnit tests in
your project are immense—they prevent subtle, hard-to-catch bugs, act as the best possible
documentation of your architecture, save you time during code reviews and keep your codebase
clean, consistent, maintainable and healthy. They are easy to write, simple to integrate into
your CI/CD pipeline and extendable even beyond the original language. What’s not to like? Start
writing them now and reap the rewards for years to come!</p> | | #architecture;#jvm;#tdd;#android;#kotlin | | |
Questions to ask before choosing mobile app technology | | https://mobileit.cz/Blog/Pages/choosing-mobile-app-technology.aspx | Questions to ask before choosing mobile app technology | <p>Embarking on a new project is exciting. So many possibilities, so many choices! But you better
get them right from the start, otherwise, your project might suffer in the long run.</p><p>Choosing a platform to build your mobile app can be a daunting task. For some apps, a simple
responsive web or PWA will suffice, whereas for others only native solutions will do. And
there’s of course a range of popular cross-platform or hybrid technologies like Xamarin, React
Native, Flutter, or Kotlin Multiplatform, to name a few.</p><p>Evaluating all these alternatives is difficult. There are no universally right or wrong answers,
but to make the choice easier, we offer you a list of questions that, when answered, will help
you make the right choice.</p><h2>Lifespan</h2><ol><li><strong>What is the planned lifetime period of your app?</strong> Short-lived marketing or
event apps have different requirements than apps that need to live happily for years.
</li><li><strong>What is more important: Time to market, or sustainable development over
time?</strong> Sometimes quick’n’dirty solutions make perfect business sense, sometimes they
are poison.
</li><li><strong>Will the chosen technology still exist when your app approaches the end of its
life?</strong> Obsolete or abandoned technology will severely hinder your ability to support
and expand your app.
</li><li><strong>Will the technology be supported by its authors? Will it be supported on target
platforms?</strong> Open source technology can be theoretically maintained by anybody,
however, in practice, the majority of work often rests on a surprisingly small number of
individuals.
</li><li><strong>How will the technology evolve over time?</strong> There is a significant difference
between a technology that the authors primarily develop to serve their own needs (even if
it’s open-sourced), and a technology that is truly meant as a general-purpose tool.
</li><li><strong>Is there a risk of vendor lock-in?</strong> If the technology is currently free to
use, will it still be free in the future? What is the cost of moving to an alternative
solution?
</li></ol><h2>Runtime</h2><ol start="7"><li><strong>What runtime environment does the app need?</strong> The app may be compiled to
native code, it may need bridges, wrappers, interpreters, etc. Those can differ wildly in
various regards, sometimes by an order of magnitude.
</li><li><strong>How is the performance?</strong> Nobody wants sluggish, janky apps.</li><li><strong>Is it stable?</strong> Frequent crashes destroy an app's reputation quickly.</li><li><strong>How big are deployed artifacts? Do they need to be installed?</strong> A complicated
or slow installation process lowers the chances that users will even <em>launch</em> your
app, while every extra megabyte increases churn.
</li></ol><h2>UI</h2><ol start="11"><li><strong>Does the technology use native components, or does it draw its own? Can the user
tell the difference?</strong> Non-native components may look similar, but users are
surprisingly sensitive to even small inconsistencies.
</li><li><strong>Does it respect the look’n’feel of each platform?</strong> You don’t want your app
to look unintentionally alien on the target platform.
</li><li><strong>Are all platform-specific components available?</strong> Custom UI components often
demand a lot of work and if many are not available, your app can get very expensive, very
quickly.
</li><li><strong>How difficult is it to create custom components?</strong> Even if all platform
components are available, there will be times when you’ll need to create your own—and it
needs to be reasonably effective to do so.
</li><li><strong>How difficult is it to create animations?</strong> When done right, animations are a
crucial part of the UX, but implementing animations can sometimes be exceedingly difficult.
</li><li><strong>How are the components integrated with the target system?</strong> Appearances are
not everything—you also need to consider things like gestures, accessibility, support for
autocomplete, password managers, etc.
</li></ol><h2>Compatibility and interoperability</h2><ol start="17"><li><strong>What level of abstraction does the technology bring?</strong> Some try to completely
hide or unify the target platforms, some are very low-level. Both can be good, or bad.
</li><li><strong>Which system functionalities does it support directly?</strong> UI is not
everything—chances are your app will need to support at least some of the following things:
biometry, cryptography, navigation, animations, camera, maps, access to user’s contacts or
calendar, OCR, launcher widgets, mobile payment systems, AR/VR, 3D rendering, sensors,
various displays, wearables, car, TV, …
</li><li><strong>How difficult is it to access native APIs?</strong> Every abstraction is leaky.
There will come a time when you’ll need to interact with the underlying platform directly.
The difficulty to do so can vary greatly.
</li><li><strong>Are cutting-edge platform features available right away?</strong> Especially when
using bridges or wrappers, support for the latest features can be delayed.
</li><li><strong>What other platforms does the technology support?</strong> The ability to run your
app on other platforms can sometimes be very advantageous, just keep in mind that the extra
investment required can vary.
</li></ol><h2>Paradigm and architecture</h2><ol start="22"><li><strong>How steep is the learning curve?</strong> Your team needs to be up-and-running in a
reasonable amount of time.
</li><li><strong>How rigid is the technology?</strong> Some frameworks try to manage
everything—painting by the numbers can be simple and effective, but at the same time, it may
limit your ability to implement things for which the framework doesn’t have first-class
support. On the other hand, libraries may be more difficult to wire together, but they grant
you greater freedom.
</li><li><strong>How distant is the given paradigm from the default way of doing things?</strong>
Nonstandard or exotic approaches can steepen the learning curve significantly.
</li><li><strong>Is the technology modular? On what levels?</strong> Usually, you need the ability to
slice the app across various boundaries (e.g., features, layers), and at various levels
(e.g., code, compilation, deployment, etc.).
</li><li><strong>How does it scale?</strong> Nowadays, even mobile apps can easily grow to hundreds
of screens, and the app mustn’t crumble under that weight for both its developers and users.
</li></ol><h2>Tooling</h2><ol start="27"><li><strong>Is there an official IDE? What does it cost? Can it be extended with
plugins?</strong> Developer productivity is paramount, and the best tools pay for themselves
quickly.
</li><li><strong>Which build system does the technology use?</strong> There are many of them, but
they’re not all equally simple to use, fast, or extendable.
</li><li><strong>How is the CI/CD support?</strong> It needs to integrate smoothly with your CI/CD
system of choice.
</li><li><strong>What about testing, debugging, instrumentation, or profiling?</strong> Your
developers and QA people need to be able to quickly dissect your app to identify and fix
potential problems.
</li><li><strong>How mature and effective are the tools?</strong> Your developers should focus on
your app, they shouldn’t be fighting the tools.
</li><li><strong>Does the technology support hot reload, or dynamic feature modules?</strong> These
features usually greatly enhance developer productivity.
</li></ol><h2>Ecosystem</h2><ol start="33"><li><strong>Is the technology open source?</strong> There are countless advantages when it is.
</li><li><strong>What is the availability, quality, and scope of 3rd party libraries?</strong> The
ability to reuse existing, well-tested code can make or break projects.
</li><li><strong>Is the official documentation up-to-date, complete, and comprehensive?</strong>
While learning about particular technology by trial and error can be fun, it certainly isn’t
effective.
</li><li><strong>Do best practices exist?</strong> If there are many ways to do a thing, chances are
some of them will end up with your developers shooting themselves in the foot.
</li><li><strong>How accessible is community help? Are there blog posts, talks, or other learning
materials?</strong> Search StackOverflow, or try to find newsletters, YouTube channels,
podcasts, or conferences dedicated to the technology in question.
</li><li><strong>Are consultants available if needed?</strong> Some of them are even helpful.</li><li><strong>What is the overall community sentiment towards the technology?</strong> Dedicated
fans are a good sign, but be careful not to fall for marketing tricks.
</li><li><strong>Do other similar organizations have experience with the technology?</strong> Learn
from the successes and mistakes of others.
</li></ol><h2>Human resources</h2><ol start="41"><li><strong>What primary programming language does the technology rely on?</strong> It isn’t
enough that developers are able to <em>edit</em> source files to make the machine do
something—they need to be able to write idiomatic and expressive code that can be read by
human beings.
</li><li><strong>Do you already have suitable developers?</strong> Why change a whole team, when you
might already have a stable, well-coordinated one?
</li><li><strong>Will mobile developers be effective using the language?</strong> There could be
great friction when switching developers from one language to another, especially when the
new language is significantly different (e.g., statically vs. dynamically typed, compiled
vs. interpreted, etc.).
</li><li><strong>Will non-mobile developers be effective on mobile platforms?</strong> For example,
some technologies try to port web frameworks to mobile platforms, so it might look like a
good idea to assign web developers to the project—but the reality is not that simple.
</li><li><strong>What is the current market situation? What is the market profile of available
developers?</strong> You usually need a suitable mix of junior and senior developers, but
they might not be easy to find, or their cost might not be economically feasible.
</li></ol><h2>Existing codebase</h2><ol start="46"><li><strong>Do you already have some existing code?</strong> Rewriting from scratch is tempting,
but it isn’t always a good idea.
</li><li><strong>What have you invested in it so far?</strong> It may be very cheap to throw away, or
it may represent a major asset of your organization.
</li><li><strong>What is its value to your organization?</strong> It may earn or save you a ton of
money, or it may be a giant liability.
</li><li><strong>How big is the technical debt?</strong> The value of unmaintainable code is not
great, to put it mildly.
</li><li><strong>Can it be maintained and evolved?</strong> The software must be, well, soft. If
yours is rigid, again, its value is not that great.
</li><li><strong>Can it be transformed piece-by-piece?</strong> Some technologies allow gradual
migration, some are all-or-nothing propositions.
</li></ol><h2>Final questions</h2><p>Each app has different needs, and there will always be tradeoffs. In the end, you’ll need to
prioritize the various viewpoints implied by the aforementioned questions.</p><p>Which qualities are most important for your project? Which properties bring you opportunities?
Which increase risk?</p><p>When you put the alternatives into the right perspective, you certainly have a much better chance
at success. May your apps live long and prosper!</p> | | #project-management;#android;#iOS | | |
Jetpack Compose: What you need to know, pt. 1 | | https://mobileit.cz/Blog/Pages/compose-1.aspx | Jetpack Compose: What you need to know, pt. 1 | <p><a href="https://developer.android.com/jetpack/compose">Jetpack Compose</a> is coming sometime
this year. Although it is still under heavy development, given its significance, I think now is
the right time to look at what it brings to the table.</p><p>This isn’t a technical tutorial or introduction to Compose (there are many of these floating
around, but be careful, as many of them are already out of date), but rather a collection of
more or less random points, notes, and findings. Let’s find out if the hype is justified!</p><h2>Executive summary, but for developers</h2><p><strong>Compose is going to be one of the biggest changes Android development has ever
seen.</strong></p><p>Yes, perhaps even bigger than reactive programming, Kotlin, or coroutines. UI is a crucial part
of any application and a UI toolkit built on the mindset from the 2010s instead of the 1990s is
indeed a very welcome upgrade.</p><p>Also, because it relies on Kotlin-exclusive features, Compose is another nail into Java’s coffin
on Android.</p><p><strong>Making UIs is fun again!</strong></p><p>This is Compose’s equivalent of <span class="pre-inline">RecyclerView</span> <em>with different
item
types</em>:</p><pre><code class="kotlin hljs">LazyColumn {
items(rows) { row ->
when (row) {
is Title -> TitleRow(row.title)
is Item -> ItemRow(row.text)
}
}
}</code></pre><p>Of course, everything isn’t that simple, but Compose really excels at its main goal—creating
sophisticated and highly reusable custom components and complex layouts in a simple, effective,
and safe manner.</p><p><strong>The mental model is radically different from what we are used to in Android.</strong></p><p>Unidirectional UI toolkits were the rage with web folks some time ago, and now they’ve finally
arrived on mobile platforms.</p><p>The good news is that because we are late to the party, the paradigm has matured, and perhaps
Compose won’t repeat at least some of the mistakes that caught up with early implementations on
other platforms. The bad news is that the paradigm requires a significant mindset shift (say on
a scale of reactive programming)—but it’s for the better, I promise.</p><p><strong>Compose has a huge multiplatform potential.</strong></p><p>Compose comprises <a href="https://developer.android.com/jetpack/androidx/releases/compose">several
artifacts</a>, and only some of them are Android-specific.
JetBrains already work on <a href="https://www.jetbrains.com/lp/compose/">desktop port</a>, and
covering other platforms is certainly not impossible.</p><p>Building on a combination of existing platform-specific UI toolkits and Kotlin Multiplatform
features such as <span class="pre-inline">expect/actual</span> declarations, one can imagine a
distant future where a single UI
toolkit provides the holy grail of unified implementation, native performance, and
platform-specific look’n’feel.</p><h2>Creating UI</h2><p><strong>There are no XML layouts, no inflaters and no objects representing the UI
components.</strong></p><p>There are no setters to mutate the current UI state because there are no objects representing the
UI views (<span class="pre-inline">@Composable</span> function calls only <em>look</em> like
constructor calls, but don’t let that fool you), which means there cannot even be any internal
UI state (well, the last point isn’t entirely true, but we’ll get to that later). Then you must
think about states and events traveling through your UI tree in various directions and whatnot.
</p><p>If you’ve never experienced a unidirectional toolkit, it will feel alien, strange, and maybe even
ineffective, but the benefits are worth it.</p><p><strong>String, font, and drawable resources are staying.</strong></p><p>Compose doesn’t want to get rid of these and works with them just fine. However, only bitmap and
vector drawables make sense with Compose. Other types such as layer list drawables, state list
drawables, or shape drawables are superseded by more elegant solutions.</p><p><a href="https://developer.android.com/reference/kotlin/androidx/compose/ui/graphics/Color">Colors</a>
and <a href="https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Dp">dimensions</a>
should be defined entirely in Kotlin code if possible, but traditional resources still may be
used if needed.</p><p><strong>There are no resource qualifiers.</strong></p><p>Compose has the power of Kotlin at its disposal. If you need to provide alternative values
depending on the device configuration (or any other factor), simply add a condition to your
composable function—it’s an explicit and unambiguous way to specify the value you want.</p><p>And of course remember to keep your code DRY—if you find yourself repeating the same bit of logic
in many places, refactor.</p><p><strong>There are no themes and styles (sort of).</strong></p><p>Compose contains basic components that expose a number of parameters to change their look and
behavior. Because everything is written in Kotlin, these parameters are rich, and most
importantly, type-safe.</p><p>If you need to style a bunch of components in the same way, you simply <a href="https://developer.android.com/jetpack/compose/themes#component-styles">wrap</a> the
original composable function call with your own composable, setting the parameters you need to
change (or exposing new ones), and use this in your code.</p><p>Simple, efficient (because there is virtually no penalty for nested calls), and without hidden
surprises.</p><p><strong>Compose comes with Material Design implementation out of the box.</strong></p><p>Although there are no themes or styles as such, there is a way to create and use application-wide
themes.</p><p>Compose comes with <a href="https://developer.android.com/jetpack/compose/themes#app-wide">Material
Design implementation</a>. Just wrap your root composable with <a href="https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#materialtheme">MaterialTheme</a>,
customize colors, shapes, or typography to fit your brand, and you’re good to go. You can have
different <span class="pre-inline">MaterialTheme</span> wrappers for different parts of your UI,
effectively replacing theme overlays from the legacy system.</p><p>Often this is all you’ll ever need, but if your design system is more sophisticated or simply
won’t fit the predefined Material Design attributes, you can <a href="https://developer.android.com/jetpack/compose/themes#custom-design">implement your
own</a> from scratch. However, this is quite difficult and requires advanced knowledge of
Compose to get it right.</p><p>See <a href="https://adambennett.dev/2020/12/migrating-your-design-system-to-jetpack-compose-part-1/">this
blog series</a> for valuable insights on custom design systems in Compose and <a href="https://cashapp.github.io/2020-12-02/crouching-theme-hidden-di">this post</a> for a
comparison of different theming approaches.</p><p><strong>We can’t completely get rid of the legacy theme system (yet).</strong></p><p>Compose theming reaches only the parts of the UI that are managed by Compose. We might still need
to set a legacy theme for our activities (to change window’s background, status bar, and
navigation bar colors, etc.), or to style View-based components that don't have Compose
counterparts.</p><p><strong>Don’t expect component or feature parity with legacy View-base components or Material
Design
specs any time soon.</strong></p><p>It’s the old story all over again: Writing a new UI toolkit from scratch means that there is
going to be a long period in which a number of components (or at least their features) won’t be
officially available.</p><p>For example, Compose’s <a href="https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#textfield">TextField</a>
doesn’t have the same features (and API) that <a href="https://developer.android.com/reference/com/google/android/material/textfield/TextInputLayout">TextInputLayout</a>
has, and both of these implementations aren’t 100 % aligned with the <a href="https://material.io/components/text-fields">Material Design spec</a>.</p><p>This situation may be slightly annoying, but at least with Compose, it’s significantly easier to
write custom components yourself.</p><p><strong>Finally, an animation system so simple that you’ll actually use it.</strong></p><p>Animating many things is as simple as wrapping the respective value in a <a href="https://developer.android.com/reference/kotlin/androidx/compose/animation/package-summary#animate%28kotlin.Float%2c%20androidx.compose.animation.core.AnimationSpec%2c%20kotlin.Float%2c%20kotlin.Function1%29">function</a>
call, and for more complex animations, Compose superbly leverages the power of Kotlin.</p><p>Done right, animations are a great way to enhance user experience. With Compose animation APIs,
their implementation is at last effective and fun.</p><h2>Internals</h2><p><strong>Composable functions are like a new language feature.</strong></p><p>Technically, <span class="pre-inline">@Composable</span> is an annotation, but you need to think
about it more like a keyword (suspend is a good analogy, more on that later). This “soft
keyword” radically changes generated code, and you need to have at least a basic idea of <a href="https://medium.com/androiddevelopers/under-the-hood-of-jetpack-compose-part-2-of-2-37b2c20c6cdd">what
goes on under the hood</a>, otherwise, it’s very well possible to shoot yourself in the foot
even with innocent-looking composable functions.</p><p><strong>The knowledge of the internals is important for creating performant UIs.</strong></p><p>Compose compiler does a lot in this regard (like positional memoization, and fine-grained
recomposition), but there are situations when the developer has to provide optimization clues,
or declare and then actually honor contracts that the compiler cannot infer on its own (such as
marking data classes as truly immutable).</p><p>However, I expect the compiler to become smarter in the future, alleviating the need for many of
these constructs.</p><h2>States</h2><p><strong>Compose UIs are declarative, but not truly stateless.</strong></p><p>UIs in Compose are declared by constructing deeply nested trees of composable functions where <a href="https://developer.android.com/jetpack/compose/state">states flows down and events
up</a>. At the root of the tree, there is a comprehensive, “master” state coming from some
external source (the best candidate for this task is the good old view model). When the state
changes, parts of the UI affected by the change are re-rendered automatically.</p><p>In theory, we want the UI to be <em>perfectly</em> stateless. The root state should be completely
externalized and should contain <em>everything</em> that must be set on the screen. That would
mean not
just obvious things like text field strings, checkbox states, and so on, but also, for example,
<em>all</em> styling attributes for all the views, internal animation states including clock,
current
animated values, etc.</p><p>In practice, this would be too cumbersome (argument lists would grow unacceptably large and
“interesting” arguments like user inputs would get mixed up with purely technical ones like
animation states), so besides explicit state that is passed via composable function arguments,
Compose has several other ways to provide data down the component tree.</p><p><strong>Composable functions can have their own internal state.</strong></p><p>Yes, function can have state encapsulated in it that survives between its invocations. This is a
pragmatic decision that simplifies its signature and enables some optimizations, and is
especially handy for animations and other things that don’t need to be changed and/or observed
from outside.</p><p><strong>Ambients are like service locators for data passed through the UI tree.</strong></p><p>
<a href="https://developer.android.com/reference/kotlin/androidx/compose/runtime/Ambient">Ambient</a>
holds a kind of global variable defined in the tree node somewhere up in the hierarchy,
statically accessible to nodes below it. If this rings an alarm bell in your head, you’re
right—statically accessed global variables create invisible coupling and other problems.</p><p>However, this is a trade-off that is often worth it. Ambients are best suited for values that we
need to set or change explicitly but don’t want to explicitly <em>pass</em> through the tree.
Theme attributes and properties are a prime example of such things. </p><p><strong>State management is now more important than ever.</strong></p><p>So we have (at least) 3 ways to store and manipulate state in Compose, and they can even be
combined along the way. The question of which method to use for which part of the state becomes
essential. Sometimes, the answer can be difficult, and choosing the wrong one can lead to all
kinds of messy problems.</p><p>Also, especially for larger screens, both the structure and the content of the state object is
crucial.</p><h2>Until next time</h2><p>Well, that concludes part 1. In the second and final part of this series, we’ll look at the
ecosystem, performance, stability, and even the magic that makes Compose possible. Take care and
stay tuned!</p> | | #android;#jetpack;#compose;#ui | | |
Jetpack Compose: What you need to know, pt. 2 | | https://mobileit.cz/Blog/Pages/compose-2.aspx | Jetpack Compose: What you need to know, pt. 2 | <p>This is the second and final part of the Jetpack Compose series that combines curious excitement
with a healthy dose of cautious skepticism. Let’s go!</p><h2>Ecosystem</h2><p><strong>Official documentation doesn’t cover enough.</strong></p><p>That’s understandable in this phase of development, but it absolutely needs to be significantly
expanded before Compose hits 1.0.</p><p>On top of that, Google is once again getting into the bad habits of 1) mistaking developer
marketing for advocacy and 2) scattering useful bits of information between <a href="https://developer.android.com/jetpack/compose/documentation">official
docs</a>, KDoc, semi-official <a href="https://medium.com/androiddevelopers/understanding-jetpack-compose-part-1-of-2-ca316fe39050">blogs</a>,
<a href="https://github.com/android/compose-samples">code samples</a>, or other sources with
unknown relevance.
Although these can be useful, they’re difficult to find and are not usually kept up-to-date.
</p><p><strong>Interoperability is good.</strong></p><p>We can use <a href="https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary#AndroidView%28kotlin.Function1%2c%20androidx.compose.ui.Modifier%2c%20kotlin.Function1%29">legacy
Views</a> in our Compose hierarchy and composables as <a href="https://developer.android.com/reference/kotlin/androidx/compose/ui/platform/ComposeView">parts</a>
of View-based UIs. It
works, we can migrate our UIs gradually. This feature is also important in the long term, as I
wouldn’t expect a Compose version of WebView or MapView written from scratch any time soon, if
ever.</p><p>Compose also plays nicely with other libraries—it integrates well with Jetpack <a href="https://developer.android.com/jetpack/compose/interop#viewmodel">ViewModel</a>,
<a href="https://developer.android.com/jetpack/compose/navigation">Navigation</a>, or <a href="https://developer.android.com/jetpack/compose/interop#streams">reactive
streams</a> (LiveData, RxJava, or Kotlin Flow—<a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/">StateFlow</a>
is especially well
suited for the role of a stream of states coming from the view model to the root composable).
Popular 3rd party libraries such as <a href="https://github.com/InsertKoinIO/koin/#androidx">Koin</a>
also have support for Compose.</p><p>Compose also gives us additional options. Its simplicity allows for much. For example, it is very
well possible to completely get rid of fragments and/or Jetpack Navigation (although in this
case, I think one vital piece of the puzzle is still missing—our DI frameworks need the ability
to create scopes tied to composable functions), but of course you don’t have to. Choose what’s
best for your app.</p><p>All in all, the future of the Compose ecosystem certainly looks bright.</p><p><strong>Tooling is a work in progress, but the fundamentals are already done.</strong></p><p>Compose alphas basically require <a href="https://developer.android.com/studio/preview">canary
builds of Android studio</a>, which are expected to be a
little bit unstable and buggy. Nevertheless, specifically for Compose, the Android tooling team
has already added custom syntax and error highlighting for composable functions, a bunch of live
templates, editor intentions, inspections, file templates, and even color previews in the gutter
(Compose has its own color type).</p><p>Compose also supports <a href="https://developer.android.com/jetpack/compose/preview">layout
previews</a> in the IDE, but these are more cumbersome than their XML counterparts. A true hot
reload doesn’t seem to be possible at the moment.</p><p>The IDE also sometimes struggles when a larger file with lots of deeply nested composable
functions is opened in the editor. That said, the tooling won’t hinder your progress in a
significant way.</p><p><strong>UI testing is perhaps more complicated than it was with the legacy toolkit.</strong></p><p>In Compose, there are no objects with properties in the traditional sense, so to facilitate UI
tests, Compose (mis)uses its accessibility framework to expose information to the tests.
</p><p>To be honest, it all feels a little bit hacky, but at least we have support for running the tests
on JUnit 4 platform (with the help of a custom rule), <a href="https://developer.android.com/jetpack/compose/testing#testing-apis">Espresso-like
APIs</a> for selecting nodes and asserting things on them, and a helper function to print
the UI tree to the console.</p><p>The situation is thus fairly similar to the legacy toolkit, and so is my advice: Mind the <a href="https://martinfowler.com/articles/practical-test-pyramid.html">test pyramid</a>, don’t
rely too much on UI tests, and structure your app in such a way that the majority of the code
can be tested by simple unit tests executed on the JVM.</p><h2>Performance and stability</h2><p><strong>Build speeds can be surprising.</strong></p><p>In a good way! One would think that adding an additional compiler to the build pipeline would
slow things down (and on its own, it would), but Compose replaces the legacy XML layout system,
which has its own performance penalties (parsing XMLs, compiling them as resources, etc.).
</p><p>It turns out, even now when Compose is still in a very early stage of development, the build time
of a project written with Compose is at least comparable to the legacy UI toolkit version—and it
might be even faster, as measured <a href="https://medium.com/androiddevelopers/jetpack-compose-before-and-after-8b43ba0b7d4f">here</a>.
</p><p><strong>Runtime performance is a mixed bag.</strong></p><p>UIs made with Compose can be laggy sometimes, but this is totally expected since we are still in
alpha. Further optimizations are promised down the line, and because Compose doesn’t come with
the burden of <a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/view/View.java">tens
of thousands of LOC</a> full of compatibility hacks and workarounds in each
component, I hope someday Compose will actually be faster than the legacy toolkit.</p><p><strong>It crashes (it’s an alpha, I know).</strong></p><p>In my experience, Compose crashes both at compile time (the compiler plugin) and at runtime
(usually because of a corruption of Compose’s internal data structure called “slot table”,
especially when animations are involved). When it does crash, it leaves behind a very, very long
stack trace that is full of synthetic methods, and which is usually also totally unhelpful.
</p><p>We definitely need special debugging facilities for Compose (similar to what coroutines have),
and yes, I know, the majority of these bugs will be ironed out before 1.0. The thing is, Compose
simply must be reliable and trustworthy at runtime because we are not used to hard crashes from
our UI toolkit—for many teams, that would be an adoption blocker.
</p><h2>Expectations</h2><p><strong>Compose is meant to be the primary UI toolkit on Android.</strong></p><p>Several Googlers confirmed that if nothing catastrophic happens, this is the plan. Of course, it
will take years, and as always, it won’t be smooth sailing all the way, but Google and JetBrains
are investing heavily in Compose.</p><p><strong>Compose is no silver bullet.</strong></p><p>Yes, Compose in many ways simplifies UI implementation and alleviates a significant amount of
painful points of the legacy UI toolkit.</p><p>At the same time, it’s still possible to repeat some horrible old mistakes regarding Android’s
lifecycle (after all, your root composable must still live in some activity, fragment, or view),
make a huge untestable and unmaintainable mess eerily similar to the situation when the whole
application is written in one single Activity, or even invent completely new and deadly
mistakes.</p><p>Compose is <em>not</em> an architecture. Compose is just a UI framework and as such it must be
isolated behind strict borders.
</p><p><strong>Best practices need to emerge.</strong></p><p>Compose is architecture-agnostic. It is well suited to clean architecture with MVVM, but that
certainly isn’t the only possible approach, as it’s evident from the <a href="https://github.com/android/compose-samples">official samples repo</a>.
However, in the past, certain ideas proved themselves better than others, and we should think
very carefully about those lessons and our current choices.</p><p>Just because these are official samples by Google (or by anyone else for that matter), that
doesn’t mean you should copy them blindly. We are all new to this thing and as a community, we
need to explore the possibilities before we arrive at a set of reasonable, reliable, and
tried-and-proven best practices.</p><p>Just because we can do something doesn’t mean we should.</p><p><strong>There are a lot of open questions.</strong></p><p>The aforementioned official samples showcase a variety of approaches, but in my book, some are a
little bit arguable or plainly wrong. For example, ask yourself:
</p><p>How should the state be transformed while passed through the tree, if ever? How should internal
and external states be handled? How smart should the composable functions be? Should a view
model be available to any composable function directly? And what about repositories? Should
composable functions have their own DI mechanism? Should composable functions know about
navigation? And data formatting, or localization? Should they handle the process death
themselves? The list goes on.</p><p><strong>Should you use it in production?</strong></p><p>Well, it entirely depends on your project. There are several important factors to consider:</p><ul><li>Being still in alpha, the APIs will change, sometimes significantly. Can you afford to
rewrite big parts of your UI, perhaps several times?
</li><li>There are features missing. This situation will get better over time, but what you need now
matters the most.
</li><li>Runtime stability might be an issue. You can work around some things, but there’s no denying
that Compose right now is less stable than the legacy toolkit.
</li><li>What is the lifespan of your application? If you’re starting an app from scratch next week,
with plans to release v1.0 in 2022 and support it for 5 years, then Compose might be a smart
bet. Another good use might be for proof of concept apps or prototypes. But should you
rewrite all your existing apps in Compose right now? Probably not.
</li></ul><p>As always with new technology, all these questions lead us to these: Are you an early adopter?
Can you afford to be?</p><h2>Under the hood</h2><p><strong>Compose is very cutting edge (and in certain aspects quite similar to how coroutines
work).</strong></p><p>In an ideal world, no matter how deeply composable functions were nested and how complex they
were, we could call them all on each and every frame (that’s 16 milliseconds on 60 FPS displays,
but faster displays are becoming more prevalent). However, hardware limitations of real world
devices make that infeasible, so Compose has to resort to some very intricate optimizations. At
the same time, Compose needs to maintain an illusion of simple nested function calls for us
developers.</p><p>Together, these two requirements result in a technical solution that’s as radical as it’s
powerful—changing language semantics with a custom Kotlin compiler plugin.</p><p><strong>Compose compiler and runtime are actually very interesting, general-purpose
tools.</strong></p><p>Kotlin functions annotated with @Composable behave very differently to normal ones (as it’s the
case with suspending functions). This is possible thanks to the <a href="https://kotlinlang.org/docs/reference/whatsnew14.html#unified-backends-and-extensibility">IR
code</a> being generated for them by the compiler (Compose uses the Kotlin IR compiler
backend, which itself is in alpha).</p><p>Compose compiler tracks input argument changes, inner states, and other stuff in an internal data
structure called <em>slot table</em>, with the intention to execute only the necessary
composable
functions when the need arises (in fact, composable functions can be executed in any order, in
parallel, or even not at all).</p><p>As it turns out, there are other use cases when this is very useful—composing and rendering UI
trees is just one of them. Compose compiler and runtime can be used for <a href="https://jakewharton.com/a-jetpack-compose-by-any-other-name/">any programming
task</a>
where working efficiently with tree data structures is important.</p><p><strong>Compose is the first big sneak peek at Kotlin’s exciting future regarding compiler
plugins.</strong></p><p>Kotlin compiler plugins are still very experimental, with the API being unstable and mostly
undocumented (if you’re interested in the details, read <a href="https://bnorm.medium.com/writing-your-second-kotlin-compiler-plugin-part-1-project-setup-7b05c7d93f6c">this
blog series</a> before it becomes
obsolete), but eventually the technology will mature—and when it does, something very
interesting will happen: Kotlin will become a language with more or less stable, fixed <em>syntax</em>,
and vastly changeable, explicitly pluggable <em>behavior</em>.</p><p>Just look at what we have at our disposal even now, when the technology is in its infancy: There
is Compose, of course (with a <a href="https://www.jetbrains.com/lp/compose/">desktop port</a>
in the works), a plugin to <a href="https://kotlinlang.org/docs/reference/compiler-plugins.html#all-open-compiler-plugin">make
classes open</a> to play
nice with certain frameworks or tests, <a href="https://developer.android.com/kotlin/parcelize">Parcelable
generator</a> for Android, or <a href="https://github.com/cashapp/exhaustive">exhaustive
when for
statements</a>, with <a href="https://github.com/Foso/Cabret-Log">more plugins</a> coming in
the future.</p><p>Last but not least, I think that the possibility to modify the language with external,
independent plugins will lower the pressure on language designers, reducing the risk of bloating
the language—when part of the community demands some controversial feature, why not test-drive
it in the form of a compiler plugin first?</p><h2>Final words</h2><p>Well, there you have it—I hope this series helped you to create an image of Compose in your head
that is a little bit sharper than the one you had before. Compose is certainly going to be an
exciting ride!</p> | | #android;#jetpack;#compose;#ui | | |
So you want to create a design system, pt. 1: Can designers and developers ever be friends? | | https://mobileit.cz/Blog/Pages/design-system-1.aspx | So you want to create a design system, pt. 1: Can designers and developers ever be friends? | <p>Design systems are all the rage these days. Done right, they make both UI design and
implementation much easier and, more importantly, cheaper, while providing users with a
consistent and pleasant UX.</p><p>Let's look at them from a mobile development perspective - how do you create a design system that
is usable, efficient, and even removes the pain points of interaction between designers and
developers?</p><h2>What exactly is a design system?</h2><p>A design system is a comprehensive framework for the design and implementation of digital
products. It consists of reusable styles, components, standards, and guidelines that are used
together to build any number of applications that are both coherent and allow for evolution.</p><p>A design system consists of two parts, described by this highly scientific equation:</p><p style="text-align:center;"><em>Design system = Design × System</em></p><p>The <em>design</em> part is all about how your product looks, behaves, and speaks to the user -
in other words, the actual look'n'feel, perhaps developed in more detail and with more depth
than usual. This is probably the easier part because historically our industry had quite a lot
of experience with this kind of thing.</p><p>The <em>system</em> part is not to be underestimated because it is important for consistency,
quick and cheap design and implementation, and the ability to freely shape the UX in the future.
Achieving that level of flexibility is difficult, but it is also where design systems bring the
most value.</p><p>Since the system part must be robust enough to allow for different designs to be created and then
evolved in time while supporting the implementation, we arrive at the second fundamental
equation:</p><p style="text-align:center;"><em>Design < System</em></p><p>That's not to say that design is less important or less valuable, of course not! The point is
that a situation where you have a great design but a bad system is much worse than the other way
around, especially in terms of implementation effort and overall flexibility. </p><p>Designers are better at, well, designing, while developers are probably a little bit better at
creating systems. Let's look at how both sides can help each other and make their jobs
easier.</p><p>I was lucky enough to recently implement a large-scale design system used in dozens of
applications. Based on this experience, before we get to the nitty-gritty details of design
systems specs and implementations, let me offer some initial tips for both designers and
developers.</p><h2>Dear designers</h2><ul><li>You absolutely need to have first-hand experience with <em>all</em> the platforms you will
be
designing for. You need to immerse yourself in the platform, soak up the feeling of using it
on a daily basis, and get to know its iconic apps.<br>
If your main phone is an iPhone, you need to get an Android device too (and vice versa) -
yes, I know, the culture shock is considerable in both cases, but this is your job after
all. It doesn’t matter if you're going to design for cars or smart fridges or fighter jets
or IoT-enabled dog houses, you need to physically spend some time with those things.
</li><li>Be aware that different platforms have wildly different UI idioms, widget sets, features,
and development costs, not forgetting different and sometimes very interesting bugs! UI
programming is actually <em>very</em> hard (that’s an actual fact, not me complaining, I
swear!) and what is free on one platform can be very expensive or almost impossible to do on
another.<br>Even worse, there is usually no obvious, predictable relationship between what
is simple and what is complex to implement. Even a small change in design from your
perspective can cause a massive increase in the implementation effort. Be prepared to kill
your darlings or at least postpone them until the next major version.
</li></ul>
<img alt="Two different buttons" src="/Blog/PublishingImages/Articles/design-system-1-01.png" data-themekey="#" />
<p style="text-align:center;"><em>On Android, one of these buttons is actually much more
expensive to implement than the other.</em>
</p><ul><li>If you don’t design it, someone else will - and that someone is likely to be someone <em>different</em>
each time, so not only will some things in your app be out of your control, but they'll also
look a little different every time.<br>
There is no other option but to simply design, in sufficient detail, <em>everything</em>
your design system will need.
</li><li>Sooner or later your design will be transformed into code, in the best case 1:1. There is a
lot of UI code in most applications and it is complex, expensive, and also very hard to
test. How consistent and systematic your specs and designs are <em>directly and
significantly</em> affects the cost of development. Consistency doesn't just help
developers, it also helps you as well when designing real applications.
</li><li>It is often prohibitively expensive to achieve a pixel-perfect implementation. I know it's
hard, but sometimes achieving a <em>good enough</em> result is the sensible thing to do.
Because the exact reasons for this are usually highly complicated, technical, and thus
boring and uninteresting, you might need to trust your developers more or less blindly in
this regard.
</li></ul>
<img alt="Text rendered twice" src="/Blog/PublishingImages/Articles/design-system-1-02.png" data-themekey="#" /><br>
<p style="text-align:center;"><em>Same font size, same letter spacing - but the design tool
and OS render it slightly differently anyway. And let's not start with inconsistencies
across OS versions...</em></p><ul><li>Unless you are designing a special kind of application such as games or VR, please respect
the platform and therefore its users. The goal is to strike the right balance between the
platform itself, your customer's brand, and your creativity.<br>For example, people don't
compare the iOS and Android versions of your app to each other - what they do is compare
your app to other apps on their device.
</li><li>Modern design tools are great, but they still have a lot of limitations that may not be
obvious from your point of view - things like layout composition, exporting assets to
different formats, or code generation look good at first glance, but in reality are often
not completely usable. Please don't fall blindly for the marketing of the manufacturers of
these tools and discuss this with your developers.
</li></ul>
<img alt="Bad UI source code" src="/Blog/PublishingImages/Articles/design-system-1-03.png" data-themekey="#" /><br>
<p style="text-align:center;"><em>I won't name a specific tool here, but someone can commit
this garbage to source control only over my dead cold body.</em></p><ul><li>Since you are the guardians of the source of truth of your design system, you must have a
mechanism in place to communicate changes and updates to your design system.
</li><li>Please talk to your developers often and be prepared to meet them halfway. Create great
things and don’t forget to have fun in the process!
</li></ul><h2>Dear developers</h2><ul><li>Because no one sees or understands the technical details as you do, it is your
responsibility to communicate potential problems and difficulties in a simple, clear, and
timely manner. Not only that, you need to be able to offer alternative solutions or
trade-offs based on different criteria.
</li><li>In a sense, you're also responsible for the delivery of the whole thing, so you have to be
able to schedule your work on the whole application from a technical perspective so you
don't end up with two perfect screens and 30 broken ones.
</li><li>If you think something is missing from the design system, please let the designers know!
Don't jump straight into implementation - let the designers do their job. Let them design
the thing so it's consistent with the rest of the design system, and let them specify it so
it's reusable.
</li><li>You need to have a perfect overview of the UI of your platform, both from a development and
user perspective. These APIs are huge in width and depth and contain many bugs and
incompatibilities, but it's your responsibility to know most of them.
</li></ul>
<img alt="Android View class reference" src="/Blog/PublishingImages/Articles/design-system-1-04.png" data-themekey="#" /><br>
<p style="text-align:center;"><em>54 subclasses is overly optimistic. And the whole thing
is over 30.000 LOC long!</em></p><ul><li>As I said, there is a lot of UI code in a normal application and it is complex, expensive,
and very hard to test, but also highly reusable. For these very reasons, you must
<em>not</em> treat it as a second-class code! The usual principles of clean code apply here
too - buzzwords like DRY, KISS, YAGNI, or separation of concerns are actually pretty good
ideas. Hardcoding and duplicating things is a road to maintenance hell.
</li><li>Since your implementation will ideally follow the designers' specifications and designs very
closely in the code, the quality of input from the designers has a high chance of directly
affecting the quality of your code.<br>If necessary, politely but firmly demand cleanliness
and consistency in design system specifications, which, after all, must always be the only
source of truth in the design system.<br>And since the implementation follows the design
system specifications, it is also your responsibility to keep the implementation in sync
with the design system over time.
</li><li>It helps if you have a basic understanding of design principles, typography, color theory,
etc. You don't have to do landscape painting in your spare time, but being able to talk to
designers at least partially in the language of their domain is important. A light form of
OCD that allows you to quickly spot misaligned elements or inconsistent colors comes in
handy too.
</li></ul>
<img alt="OCD test" src="/Blog/PublishingImages/Articles/design-system-1-05.png" data-themekey="#" /><br>
<div style="text-align:center;"><em>One of these things is not like the other.<br><br></em></div><ul><li>Please talk to your designers often and be prepared to meet them halfway. Create great
things and don’t forget to have fun in the process!
</li></ul><h2>A successful cooperation</h2><p>The above points help designers and developers to be on the same page when working together,
which is key for a successful outcome. Next time, we'll get our hands dirty with specific
elements of design systems such as colours, typography, layouts, and motion, bringing the
<em>designs</em> and <em>systems</em> together in a scalable and maintainable way.</p> | | #design-system;#ui;#ux;#development;#android;#iOS | | |
So you want to create a design system, pt. 2: Colors | | https://mobileit.cz/Blog/Pages/design-system-2.aspx | So you want to create a design system, pt. 2: Colors | <p>Color is probably the most distinctive element of any design, and also the most important
expression of brand identity (at least until <a href="https://material.io/blog/announcing-material-you">Material You</a> completely
reverses this relationship, but it remains to be seen how it will be adopted). So how do we
approach color when designing and implementing a design system so that our solution is usable,
versatile, and scalable?</p><h2>Color me curious</h2><p>Besides conveying the brand and evoking emotions, colors have several other roles in current
applications, including:</p><ul><li>highlighting different application states such as errors, warnings, success, or info
messages
</li><li>ensuring usability, legibility, and accessibility of the application under all conditions
</li><li>providing different themes from which the user (or system) can choose according to
environmental conditions or personal preferences
</li></ul><p>Regarding the last point, users nowadays expect support for at least light and dark themes. Often
this is more than just an aesthetic choice - for example, a car navigation app that dazzles
drivers at night with large areas of bright colors can be downright dangerous.</p><p>And while the app supports switching between themes, it doesn't have to stop at just these two
basic ones, for example:</p><ul><li>Is accessibility extremely important to your app? Add a specially designed high-contrast or
colorblind-friendly theme.
</li><li>Does the app owner currently run a major promotion, have an anniversary, or celebrate some
significant event? Make it known with a special temporary theme.
</li><li>Do you want to differentiate products or make it clear that the customer bought a premium
version of the app or service? Add a special, more luxurious-looking theme.
</li></ul>
<img alt="Various application themes" src="/Blog/PublishingImages/Articles/design-system-2-01.png" data-themekey="#" />
<p>Theme support is a feature that is unique in that it makes both users and the marketing
department happy. But how to construct it so that both designers and developers can actually
work with it and be productive?</p><h2>Layers of indirection</h2><p>Let's start with what is definitely not suitable: Hardcoding the colors in the design tool and
therefore in the code.</p>
<img alt="Do not hardcode colors" src="/Blog/PublishingImages/Articles/design-system-2-02.png" data-themekey="#" />
<p>There are obvious drawbacks to this method, including the inability to change colors globally in
a controlled manner (no, “find & replace” isn’t really a good idea in this case), and the need
to copy and edit all designs for each additional theme we want to add (for designers), or
cluttering the code with repetitive conditions (for developers). It also often leads to
inconsistencies and it’s extremely error-prone - did you notice the mistake in the picture
above?</p><p>Unfortunately, we still occasionally encounter this approach because many design tools will
happily automagically present all the colors used, even if they are hardcoded, creating the
illusion that the colors are under control and well-specified. They aren’t. Don’t do this.</p><p>So how to get rid of hardcoded colors? The first step is to hide them behind named constants and
reuse these constants in all places.</p>
<img alt="Do not use color constants alone" src="/Blog/PublishingImages/Articles/design-system-2-03.png" data-themekey="#" />
<p>This is definitely better - the colors can be changed globally in one place, but the problem
arises when supporting multiple themes. The naive solution is to override each constant with a
different value in every theme. This works as long as the colors in the different themes change
1:1. But consider the following situation:</p>
<img alt="Do not override color constants per theme" src="/Blog/PublishingImages/Articles/design-system-2-05.png" data-themekey="#" />
<p>Since it is usually not advisable to use large areas of prominent colors in a dark theme,
although the toolbar and button in a light theme are the same color, the toolbar should be more
subdued in a dark theme. This breaks the idea of overriding the colors 1:1 in different
themes because where one theme uses a single color, another theme needs more colors.</p><p>The solution to this situation is the so-called (and only slightly ironic) <a href="https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering">fundamental
theorem of software engineering</a>:</p><p style="text-align:center;"><em>“We can solve any problem by introducing an extra level of
indirection.”</em></p><p>In this case, that means <em>another</em> layer of named color constants. I kid you not - please
stay with me, it’ll be worth it.</p><h2>The solution</h2><p>We achieve our goals, i.e. the ability to easily and safely change colors globally, and support
any number of themes, by following these steps:</p><ol><li><strong>Define a set of semantic colors.</strong> These are colors named and applied based
on their purpose in the design. Their names must not express specific colors or shades, but
<em>roles</em>. For example, Google’s Material Design defines the following semantic colors:
<img alt="Semantic colors of Material Design" src="/Blog/PublishingImages/Articles/design-system-2-06.png" data-themekey="#" />
These names are a good starting point, but of course, you can define your own, based on your
needs. What's important is that semantic colors don't have concrete values by themselves,
they are placeholders or proxies that only resolve to specific colors when applied within a
specific theme, meaning one semantic color will probably have a different actual value in
each theme.
</li><li><strong>Define a set of literal colors.</strong> These constants literally represent the
individual colors of your chosen color palette. They are common to all themes, so there are
usually more of them than semantic colors. Unlike semantic colors, they are named purely on
the basis of their appearance. For example, an earlier version of Material Design defined
the following shades:
<img alt="Old Material Design literal colors" src="/Blog/PublishingImages/Articles/design-system-2-07.png" data-themekey="#" />
Recently it has become a common practice to distinguish colors with different lightness
using a number where 1000 is 0% lightness (i.e. black) and 0 is 100% lightness (white), but
of course you can devise your own system.
</li><li>Follow this rule in both design <em>and</em> code (no exceptions!):<br><strong>Semantic
colors must be used exclusively and everywhere. Literal colors (or even hard-coded
constants) must <em>never</em> be used directly.</strong><br>
This means that the use of colors in design and implementation must have the possibility of
being completely specified in the form of "wireframes" like this:
<img alt="Design wireframe specified with semantic colors" src="/Blog/PublishingImages/Articles/design-system-2-08.png" data-themekey="#" />
</li><li><strong>Finally, map semantic colors to concrete literals <em>per theme</em>.</strong>
This step ultimately produces a specific theme from the design specification, which is in
itself <em>independent</em> of a particular theme. Based on our previous example, the final
result will look like this:
<img alt="Themes resolved from semantic colors mapped to color literals" src="/Blog/PublishingImages/Articles/design-system-2-09.png" data-themekey="#" />
For example, toolbar background color is <em>specified</em> as <span class="pre-inline">Primary</span>,
which in <span class="pre-inline">Light</span> theme is <em>mapped</em> to <span class="pre-inline">Purple700</span> literal color, but in <span class="pre-inline">Dark</span>
theme it resolves to <span class="pre-inline">Purple900</span>. The most important thing is
that <span class="pre-inline">Purple900</span> or <span class="pre-inline">Purple700</span>
literal colors <em>aren't</em> mentioned in the design specification, only in theme
definition.
</li></ol><p>It's just a little extra work, but the benefits are enormous. We have successfully decoupled the
<em>definition</em> of the colors from the <em>actual</em> colors used in various themes.
</p><h2>Make it work for you</h2><p>There are usually questions that often arise or choices that need to be made when implementing
this system. Here are some tips based on our experience:</p><ul><li><strong>Don't go overboard with the number of semantic colors.</strong> It's tempting to
define a separate semantic color for every part of each UI element (e.g., <span class="pre-inline">ButtonBackground</span>,
<span class="pre-inline">SwitchTrack</span>, <span class="pre-inline">ProgressIndicatorCircle</span>),
which has the theoretical advantage that you can then
change them independently, but it also makes it much harder to navigate the design and
implementation. The ideal amount of semantic colors is one where one can hold more or less
all of them in one's head at once.
Try to find a minimum set of sufficiently high-level names that will cover 90% of
situations. You can always add more names later.
</li><li><strong>Naming is hard.</strong> Since semantic colors form the basis of the vocabulary used
across the team and also appear everywhere in the code, it's a good idea to spend some time
looking for the most appropriate names. If some of the chosen names turn out to be not that
fitting, don't be afraid to refactor them. It's unpleasant, but living with inappropriate
names for a long time is worse.
</li><li><strong>Never mix literal and semantic names.</strong> For example, a set of semantic colors
containing <span class="pre-inline">Orange</span>, <span class="pre-inline">OrangeVariant</span>, <span class="pre-inline">Secondary</span>,
<span class="pre-inline">Background</span>, <span class="pre-inline">Disabled</span>, etc.
isn’t
going to work well, even if the main color of your brand is orange and everyone knows it.
Even so, create a purely semantic name for such a color, like <span class="pre-inline">Brand</span> or <span class="pre-inline">Primary</span>.
</li><li><strong>If you need multiple versions of a semantic color, never distinguish them with
adjectives expressing properties of literal colors</strong> such as <span class="pre-inline">BrandLight</span>, <span class="pre-inline">BrandDark</span>,
etc., because what is darker in one theme may be lighter in another and vice versa. Instead,
use adjectives expressing purpose or hierarchy, such as <span class="pre-inline">BrandPrimary</span>,
<span class="pre-inline">BrandAccent</span>, or even
<span class="pre-inline">BrandVariant</span> (but if you have <span class="pre-inline">Variant1</span>
thru <span class="pre-inline">Variant8</span>, you have, of course, a problem as
well).
</li><li><strong>For each semantic color that can serve as a background color, define the
corresponding semantic color for the content that can appear on that background.</strong>
It's a good idea for these colors to contain the preposition <span class="pre-inline">on</span> or the word <span class="pre-inline">content</span> in
the
name, like <span class="pre-inline">OnPrimary</span> or <span class="pre-inline">SurfaceContent</span>.
Avoid the word <span class="pre-inline">text</span> (e.g., <span class="pre-inline">SurfaceText</span>),
as this
color will often be applied to other elements such as icons or illustrations, and try to
avoid the word <span class="pre-inline">foreground</span> because sometimes the use of
background and foreground colors can
be visually inverted:
<img alt="Two components with inverted colors" src="/Blog/PublishingImages/Articles/design-system-2-10.png" data-themekey="#" />
</li><li><strong>The use of the alpha channel in literal colors is a difficult topic.</strong>
Generally speaking, the colors that will be used as backgrounds should be 100% opaque to
avoid unexpected combinations when several of them are layered on top of each other (unless
this effect is intentional). Content colors, on the other hand, can theoretically contain an
alpha channel (useful, for example, for defining global secondary or disabled content colors
that work on different backgrounds), but in this case, it is necessary to verify that the
given color <em>with its alpha value</em> works with any background.<br>Another question is
alpha channel support in your design tool and code - is the alpha value an integral part of
the color, or can we combine separate predefined colors and separate predefined alpha
values?
</li><li><strong>If your design tools don't directly support semantic colors or multiple themes at
the same time, work around that.</strong> Tools come and go (or, in rare cases, are
upgraded), but your design system and the code that implements it represents much more value
and must last longer. Don’t be a slave to a particular tool.
</li><li><strong>All text should be legible and meet accessibility standards</strong> (icons on the
other hand don’t
need to do that, but it’s generally a good idea for them to be compliant as well) - see <a href="https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html">The
Web Content Accessibility Guidelines (WCAG 2.0)</a>, and use automated tools that check
for accessibility violations.
</li></ul><h2>Design is never finished</h2><p>...so it's important that your design system is able to evolve sustainably. This way of defining
color, although certainly not the simplest, allows for exactly that. We'll look at other
fundamental elements of design systems and how to handle them next time.</p> | | #design-system;#ui;#ux;#development;#android;#iOS | | |