mobile it

 

 

Underused Kotlin featureshttps://mobileit.cz/Blog/Pages/underused-kotlin-features.aspxUnderused Kotlin features<p>Kotlin is a modern and rapidly evolving language. Let's explore some nooks and crannies to see if there are any hidden gems.</p><h2>Value classes</h2><p>We often see domain models that look like this:</p><pre><code class="kotlin hljs">// DON'T DO THIS! data class CarCharger( val id: Long, val distance: Int, val power: Int, val latitude: Double, val longitude: Double, val note: String? = null /* ... */ )</code></pre><p>Unfortunately, this antipattern is so widespread that it has earned its own name - <a href="https://refactoring.guru/smells/primitive-obsession">primitive obsession</a>. If you think about it, it turns out that in the domain classes that we model based on real business entities, actually, very few things are <em>unbounded</em> Ints, Doubles, or Strings with totally arbitrary content.</p><p>The solution is to replace these primitives with proper, well-behaved types - wrapper classes that prohibit invalid values and invalid assignments:</p><pre><code class="kotlin hljs">// DON'T DO THIS! data class Latitude(val value: Double) { init { require(value in -90.0..90.0) { "Latitude must be in range [-90, 90], but was: $value" } } }</code></pre><p>We can use this class in the <span class="pre-inline">CarCharger</span> instead of the primitive type. This is much better in terms of safety and code expressiveness, but unfortunately it also often results in a noticeable performance hit, especially if the wrapped type is primitive. </p><p>But fret not! It turns out that thanks to Kotlin’s value classes, you can have your cake and eat it too! If you slightly modify the class declaration:</p><p></p><pre><code class="kotlin hljs">@JvmInline value class Latitude(val value: Double) { init { require(value in -90.0..90.0) { "Latitude must be in range [-90, 90], but was: $value" } } }</code></pre><p>the compiler (similarly to what happens with inline functions) will replace the class with the wrapped value at each call site. Thus, at compile-time, we have all the benefits of a separate type, but no overhead at runtime. Win-win! <a href="https://jakewharton.com/inline-classes-make-great-database-ids/">Inline classes also make great database IDs</a>. </p><p>Similar to data classes, <span class="pre-inline">equals</span> and <span class="pre-inline">hashcode</span> are automatically implemented for value classes based on the wrapped value (because value classes have no identity). Value classes can also have many features of standard classes, such as additional properties (without backing fields), or member functions, but there are also some restrictions - they cannot inherit from other classes (they can however implement interfaces), and they must be final. </p><p>Be sure to read the <a href="https://kotlinlang.org/docs/inline-classes.html">full documentation</a> and how value classes in Kotlin relate to <a href="https://github.com/Kotlin/KEEP/blob/master/notes/value-classes.md">Java’s upcoming Project Valhalla</a>.</p><p>With consistent use of value classes, your domain models (and other code, of course) can be significantly more secure and readable:</p><pre><code class="kotlin hljs">data class CarCharger( val id: CarChargerId, val distance: Kilometers, val power: Kilowatts, val coordinates: Coordinates, val note: String? = null /* ... */ )</code></pre><h2>Computed properties</h2><p>Computed properties are properties with custom getter and setter but without a backing field. They can be used to locally "overload" the assignment "operator".</p><p>For example, the currently popular reincarnation of the MVVM pattern involves a view model with a public, asynchronous, observable stream of UI states that are continuously rendered by the view. In Kotlin this state stream can be represented by <a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/">Flow</a>, or more appropriately by its subtype <a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow">StateFlow</a>, which has some properties more suitable for this situation:</p><pre><code class="kotlin hljs">interface ViewModel<S : Any> { val states: StateFlow<S> }</code></pre><p>Let's create an abstract base class that will serve as the basis for concrete implementations. In order for the view model to update states, it must internally hold a writable version of StateFlow:</p><pre><code class="kotlin hljs">abstract class AbstractViewModel<S : Any>(defaultState: S) : ViewModel<S> { protected val mutableStates = MutableStateFlow(defaultState) override val states = mutableStates.asStateFlow() }</code></pre><p>If a concrete view model subclass wants to emit a state update based on the previous state, it must do something like this:</p><pre><code class="kotlin hljs">class GreetingViewModel : AbstractViewModel<GreetingViewModel.State>(State()) { data class State( val greeting: String? = null /* other state fields */ ) fun onNameUpdated(name: String) { mutableStates.value = mutableStates.value.copy(greeting = "Hello, $name") } }</code></pre><p>This works, but the code isn't quite readable, and worse, the implementation details of the abstract view model (that it uses <span class="pre-inline">MutableStateFlow</span> internally) leak into the concrete view model - classes must be well encapsulated not only against the outside world but also against their subclasses! </p><p>Let's fix this by hiding the <span class="pre-inline">MutableStateFlow</span> in the base view model, and instead provide a better abstraction for subclasses:</p><pre><code class="kotlin hljs">abstract class AbstractViewModel<S : Any>(defaultState: S) : ViewModel<S> { private val mutableStates = MutableStateFlow(defaultState) override val states = mutableStates.asStateFlow() protected var state: S get() = mutableStates.value set(value) { mutableStates.value = value } }</code></pre><p>The function in the subclass that needs to update the state then can look like this:</p><pre><code class="kotlin hljs">fun onNameUpdated(name: String) { state = state.copy(greeting = "Hello, $name") }</code></pre><p>The subclass now has no idea how the states are implemented - from its point of view, it just writes and reads the state from a simple property, so if in the future the mechanism for emitting states needs to be changed (and this has happened several times during the development of Kotlin coroutines), individual subclasses will not be affected at all.</p><p> <em>Note: The above code is theoretically thread-unsafe, but depending on the context (view model running on the main thread) this may not be an issue.</em></p><h2>Pseudoconstructors</h2><p>In all non-trivial systems, it is important to abstract the object creation process. Although Kotlin must ultimately call a constructor <em>somewhere</em> to create a new instance, this doesn’t mean that all code should be <em>directly coupled</em> to these constructors - quite the opposite. A robust system is independent of how its objects are created, composed, and represented.</p><p>Many <a href="https://en.wikipedia.org/wiki/Creational_pattern">classic design patterns</a> were created for this purpose, and many of them are still valid with Kotlin, but thanks to the interplay of Kotlin features, we can implement some of them with a twist that improves the discoverability and readability of the resulting code.</p><p>When exploring an unfamiliar API, I would argue that the most intuitive way and the first choice to create an object based on its type is to call its constructor.</p><p>Creational patterns, however, are meant to abstract concrete constructors away, so in traditional languages we may instead see constructs such as:</p><pre><code class="kotlin hljs">Foo.createFoo() Foo.getInstance() Foo.INSTANCE FooFactory.create() Foo.Builder().build() FooBuilder.getInstance().build()</code></pre><p>There are many possibilities and combinations, and it can be challenging to keep them all in your head. Luckily, Kotlin can help us!</p><p>The first example is a basic factory. Let's say we have a point of interest interface called simply <span class="pre-inline">Poi</span>. There are many specific types of POIs with different properties and we need a factory to instantiate them from their serialized representation. </p><p>If our factory can be stateless, we can simply create a top-level function of the same name in Kotlin:</p><pre><code class="kotlin hljs">fun Poi(serialized: String): Poi</code></pre><p>The call site then (except for an import statement maybe) looks exactly the same as if we were calling the constructor.</p><p>Moreover, we can do things with top-level functions that we can't do with constructors - for example, we can have such functions in different modules with different visibility and parameters, for different purposes, in different layers, etc., while a constructor always has to live in its own class.</p><p>This way we can also create "extension constructors" for types we don't own, for example:</p><pre><code class="kotlin hljs">fun ByteArray(base64: String): ByteArray { /* ... */ }</code></pre><p>And if our factory function has default parameters, it can also replace simpler builders.</p><p>Coroutines library authors do something similar with Jobs. When you write</p><pre><code class="kotlin hljs">val job = Job()</code></pre><p>what you actually call is this function:</p><pre><code class="kotlin hljs">fun Job(parent: Job? = null): CompletableJob = JobImpl(parent)</code></pre><p>Here, a "constructor" of a known type actually returns its public subtype, implemented by a private subclass. This gives the library authors a great deal of flexibility for the future.</p><p>The second example is more complicated - let's say we've been tasked with creating a year view for a calendar application where workers in a factory can see their shift schedule and other necessary data. The UI looks something like this:</p> <img alt="Calendar UI" src="/Blog/PublishingImages/Articles/underused-kotlin-features-01.png" data-themekey="#" /> <p>and the domain model of one day is as follows:</p><pre><code class="kotlin hljs">data class Day( val date: LocalDateTime, val shift: Shift, val dayType: DayType, val workType: WorkType ) { enum class Shift { None, Day, Night } enum class DayType { Normal, Weekend, NationalHoliday } enum class WorkType { Normal, Inventory, Maintenance, Training, Vacation } }</code></pre><p>Since the calendar can display several years at once in this view, and there are many possible combinations in each cell, and there can be hundreds of cells on the screen at once, and the whole thing has to scroll smoothly both vertically and horizontally, it is not possible for performance reasons to implement individual cells as regular widgets with an image and a text field.</p><p>We need to optimize this UI so that the individual cells are bitmaps that we render directly to the screen. But there would still be hundreds of such bitmaps, and color bitmaps take up a surprising amount of memory surprisingly quickly.</p><p>The solution is to cache bitmaps that look the same, effectively making them <a href="https://en.wikipedia.org/wiki/Flyweight_pattern">flyweights</a>. This will save a significant amount of rendering time and memory.</p><p>In a classic design, we would create a <span class="pre-inline">BitmapFactory</span>, add some <span class="pre-inline">BitmapCache</span>, and somehow wire it all together. With Kotlin, we can do this:</p><pre><code class="kotlin hljs">class DayBitmap private constructor(val imageBytes: ByteArray) { /* other properties and methods */ companion object { private val cache = mutableMapOf<DayCacheKey, DayBitmap>() private fun Day.cacheKey(): DayCacheKey = ... private fun Day.render(): ByteArray = ... operator fun invoke(day: Day): DayBitmap = cache.getOrPut(day.cacheKey()) { DayBitmap(day.render()) } } }</code></pre><p> <span class="pre-inline">ImageBytes</span> are raw image data that can be directly rendered to the screen. <span class="pre-inline">Cache</span> is a "static" global private cache for unique rendered images of days, <span class="pre-inline">DayCacheKey</span> is a helper type serving as a key to this cache (<span class="pre-inline">Day</span> class cannot be used as a key because it contains a date that is unique for each day - so <span class="pre-inline">DayCacheKey</span> uses all the fields from Day <em>except</em> the date).</p><p>The main trick is however the <span class="pre-inline">invoke</span> operator added to the <span class="pre-inline">DayBitmap</span> companion object.</p><p>First of all, what happens inside: A cache key is created from the given day, and if we already have a <span class="pre-inline">DayBitmap</span> object saved in the cache for this key, we return it immediately. Otherwise, we create it on-demand using its private constructor (which no one else can call!), cache it, and return it immediately. This is the actual flyweight-style optimization. </p><p>But the greatest beauty of this approach is in the creation of <span class="pre-inline">DayBitmaps</span>. The long version of the call is this:</p><pre> <code class="kotlin hljs">// DON’T DO THIS! DayBitmap.Companion.invoke(day)</code></pre><p>But since we don't have to explicitly state a companion with an implicit name, and the <span class="pre-inline">invoke</span> operator just looks like parentheses in a function call, we can shorten the whole thing, and the call-site usage is then indistinguishable from a constructor call, for example</p><pre> <code class="kotlin hljs">val bitmaps = days.map { day -> DayBitmap(day) }</code></pre><p>but with the huge difference that this transformation is <em>internally</em> optimized!</p><h2>More than the sum of the parts</h2><p>The charm of Kotlin is often how its individual features can often be used together in somewhat unexpected ways. This was just a small sampling of the less frequent abilities Kotlin has to offer - we'll look at some more next time.</p>#kotlin;#development;#android
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.aspxSo 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
Questions to ask before choosing mobile app technologyhttps://mobileit.cz/Blog/Pages/choosing-mobile-app-technology.aspxQuestions 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
Android jumps on Java release trainhttps://mobileit.cz/Blog/Pages/android-java-release-train.aspxAndroid 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
Jetpack Compose: What you need to know, pt. 2https://mobileit.cz/Blog/Pages/compose-2.aspxJetpack 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
Our first year with Kotlin Multiplatformhttps://mobileit.cz/Blog/Pages/kotlin-multiplatform-first-year.aspxOur first year with Kotlin Multiplatform<p>Kotlin Multiplatform allows you to use the same programming language on mobile, desktop, web, backend, IoT devices, and more. There is a large number of possibilities and a steadily growing number of real-world applications.</p><p>Kotlin Multiplatform was introduced at the end of 2018 and many teams have started adopting it right away, first only on smaller parts of their projects, which later grew over time.</p><p>In the case of the Cleverlance mobile team, we took a different approach in adopting Multiplatform technology. From the beginning, we believed Kotlin Multiplatform was the right approach to share business and application logic between Android and iOS mobile platforms. We've been following cross-platform and multi-platform technologies for a long time, but prior to the advent of Kotlin Multiplatform, none had convinced us of their long-term sustainability in an app agency environment like ours. It's not just about the functionality of the technology itself. What is important is the maturity of the whole platform, the community around it, and last but not least the good availability of experts in the subject matter, as my colleague summarized in his article <a href="/Blog/Pages/choosing-mobile-app-technology.aspx">Questions to ask before choosing mobile app technology</a>.</p><h2>(Too) early days</h2><p>As with any new technology we consider for use in our production applications, we set up a small project with Kotlin Multiplatform and tried to implement elementary tasks like sharing pure Kotlin code between Android and iOS, sharing network services, database access, or offloading work to a background thread.</p><p>At the same time, we started testing the right application architecture and establishing which parts of the application are suitable to share between platforms and how to do it properly. We addressed questions about the structure and rules for building shared code API, whether it's possible to call Kotlin Coroutines from Swift code, etc. And last but not least, we tested the suitable structure of the project and created a build pipeline, at the end of which an Android and iOS app package will be created.</p><p>In the beginning, the work went rather slowly. We built a list of problems or unresolved issues that prevented us from using the technology in our production apps. However, Kotlin Multiplatform has evolved very dynamically and we have to really appreciate the response time of its authors when bugs or shortcomings reported by us were resolved and a new version released in a matter of weeks.</p><p>During 2020, our demo project was gradually becoming usable, the list of unresolved issues was getting shorter, and we were eagerly awaiting the stable release of Kotlin 1.4, which promised a lot of good things.</p><h2>Starting off</h2><p>This happened at the end of summer 2020 when the list of issues that would prevent the Kotlin Multiplatform from being used in production was down to the last two.</p><p>The first one concerned <a href="https://github.com/JetBrains/kotlin-native/issues/2423">how to optimally build Kotlin code as a framework</a> for iOS apps. Even today, a year later, this topic is still not completely resolved, but the solution in the form of the so-called <a href="https://github.com/JetBrains/kotlin-native/issues/2423#issuecomment-490576896">Umbrella module (or Integration module)</a> turned out to be functional and sufficient for our needs, without limiting our work in any way.</p><p>The second one is about <a href="https://github.com/Kotlin/kotlinx.coroutines/issues/462">the memory model in Kotlin/Native</a> (in our case for iOS), and the possibility of using a background thread for coroutines. This one doesn't have a final implementation yet either, but a final solution <a href="https://blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager-development-preview/">is on the way</a>. However, a temporary solution from JetBrains in the form of a special <a href="https://github.com/Kotlin/kotlinx.coroutines/blob/native-mt/kotlin-native-sharing.md">native-mt Coroutines</a> build has proven to be sufficient in not delaying the use of Kotlin Multiplatform any further.</p><h2>First app</h2><p>On 1st September we started working on a new project. It was a mobile application for the consumer/marketing apps sector, for Android and iOS. During the first week of the project, we worked in a team of 1 Android and 1 iOS engineer on the basics of the project structure and app architecture, but especially on the alignment of practices for creating interfaces between shared and platform code and between Kotlin and Swift. Both of us were already experienced in Kotlin Multiplatform, which was important especially on the part of the iOS developer, who was thus already familiar with Kotlin and well aware of the platform differences.</p><p>At Cleverlance, we are proponents of Clean Architecture and have applied its principles to native app development for years. In terms of structure and architecture, we didn't need to invent any new approach, we just adapted our habits and proven practices in a few places to share code between the two platforms more efficiently.</p><h2>Worlds collide</h2><p>In the next phase of the project we added one more Android and iOS engineer, but they hadn't worked with Kotlin Multiplatform yet, so it was interesting to see how quickly they would get comfortable with the new mindset.</p><p>Perhaps unsurprisingly, this was not a major problem for the Android engineer. Due to the almost identical architecture and familiar language, it was enough to get used to the slightly different project structure (especially the module structure, the separation of shared and platform code, etc.) and to the new technologies, e.g. for network communication or storage, where it is not possible to directly use the well-known platform libraries, but one has to reach for the multiplatform ones (although these often just cover those known platform technologies under a single multi-platform roof).</p><p>It was much more interesting to watch the progress of the iOS developer who had never written a single line of Kotlin and had never encountered the Android world or the structure of Gradle projects. But as he was no stranger to the architecture, even though up until this point he had only known it in similar iOS projects written in Swift. It turned out that those principles and practices we shared years before between Android and iOS developers are that crucial foundation, and the technologies or programming languages are just the tools by which they are implemented.</p><p>So at first, this iOS engineer worked mainly on the iOS part of the app, and only occasionally modified shared code written in Kotlin. But he very quickly started writing more of the shared code on his own, until he opened his first PR at the end of September, in which a small but complete feature was implemented in the shared code and the iOS platform implementation. I, as an Android developer, then only wrote the small platform part for Android, on which I spent about 2 hours instead of the 2 days that the feature would have taken me if I had written it all natively for Android.</p><p>And let me tell you, it feels nice and exalting. Then, when that same iOS developer single-handedly fixed a bug in the Android app for us a few weeks later, I realized that this method of development has a whole new dimension that we never dreamed of before.</p><p>The following weeks and months of the project weren't entirely without problems. We occasionally ran into inconsistencies between the nascent Kotlin Multiplatform libraries, especially between Kotlin Coroutines and Ktor. The iOS framework build system stumbled a few times, but none of these issues ever gave us even a hint of stopping the development, and what's more, problems of this type gradually subsided. Around December 2020, after a new version of Kotlin Coroutines 1.4 was released, fully in line with the principles of multiplatform development, these difficulties became completely marginal and we were able to concentrate fully on the app development.</p><h2>Crunching the numbers</h2><p>As the project entered its final phase just before its release into production, it was time to look at the numbers.</p><p>When I checked the last two projects that we created as standard separate native Android and iOS apps, I found that the amount of code (lines of code) required for the Android and iOS app was pretty similar. In fact, the iOS apps were slightly smaller, but I attribute that mostly to the chatty way UI definitions are done on Android, a thing that is changing dramatically with the advent of technologies like Jetpack Compose and Swift UI. Likewise, at the project level, it can be argued that a similar amount of time is required to implement apps for both platforms.</p><p>As for our first multiplatform project, it worked out as follows in terms of lines of code:</p> <img alt="Comparison of lines of code" src="/Blog/PublishingImages/Articles/kotlin-multiplatform-first-year-1.png" data-themekey="#" /> <p>If the effort to implement one native app is 100%, then with Kotlin Multiplatform almost 60% of the code can be shared, and only a little over 40% needs to be implemented twice, for both Android and iOS, meaning the effort to implement both apps is 140% instead of 200%, saving almost a third of total development costs. Here again, it turns out that the amount of code needed for finalization on both platforms is similar. It should be noted that we also count a non-negligible amount of unit tests that we write only once and share.</p><p>The actual breakdown of which parts of the code we share and which we don't is a topic for a separate post, but as a rough preview, I'd give the following chart:</p> <img alt="Amount of shared code per area" src="/Blog/PublishingImages/Articles/kotlin-multiplatform-first-year-2.png" data-themekey="#" /> <p>The user interface is a very platform-specific layer, but the presentation layer does not contain such differences, and the reasons we did not share it in the first project are more technical. On subsequent multiplatform projects, we have however focused more on this part and now we are able to share the presentation layer code at about 70%, which has a positive impact on the percentage of overall code shared in the project.</p><p>When we look at the amount of time spent on this project, we get this graph:</p> <img alt="Comparison of time spent per developer" src="/Blog/PublishingImages/Articles/kotlin-multiplatform-first-year-3.png" data-themekey="#" /> <p>However, the dominance of reported Android developer time is not due to Android apps being more demanding, but simply because Android developers had more time to spend on the project. In fact, one of the iOS developers was not always able to devote 100% of his time during the project, but this did not affect the speed of development for both apps, as his time was simply compensated by the Android developers. The same worked the other way around when, for example, both Android developers were on holiday at the same time. This is not to say that an Android and iOS developer is an equivalent entity in terms of project staffing, but definitely, the multiplatform development gives you a certain amount of flexibility in human resource planning.</p><h2>Unexpected perks</h2><p>At the end of this post, I'd like to mention a few interesting facts and side effects we noticed during the development:</p><ul><li>The project is not fundamentally more demanding in its setup than a standard single platform project. Creating the basis for a new project takes a similar amount of time, in the order of days. </li><li>It's very good if Android developers learn at least the basics of Swift and working in Xcode. They can better prepare the shared code API and make small adjustments atomically across both platforms and shared code. </li><li>For iOS developers, learning Android technologies and ecosystem often involves discovering better tools than they are used to, which motivates them in their endeavors. </li><li>The second usage of shared code works as a very careful code review, and for some teams, this will allow the standard code review done during pull requests to be either reduced or removed entirely, thus increasing the development momentum. </li><li>From a project and business management perspective, we are building just one application. The same is true when communicating with the backend, where both applications act as one, there are no differences in implementation on each platform and it greatly facilitates team communication. </li><li>Short-term planned, but also unplanned developer downtime does not affect team velocity.</li><li>On one of the following projects, we were able to work in a mode of 1.5 Android developers and 3.5 iOS developers, with the development of both apps progressing similarly. </li></ul><h2>Conclusion</h2><p>It's been more than a year since we started working on our first application using Kotlin Multiplatform, and as the text above indicates, it hasn't remained an isolated experiment.</p><p>We are currently using this technology on five brand-new application projects. Besides that, we are discussing the opportunities with several long-term customers to deploy this technology in existing projects.</p><p>Kotlin Multiplatform is maturing like wine and we look forward to bringing the mobile platforms even closer together.</p><p><br></p><p>Pavel Švéda<br></p><p>Twitter: <a href="https://twitter.com/xsveda">@xsveda</a><br><br></p>​<br>#kotlin;#android;#iOS;#multiplatform;#kmp;#kmm
So you want to create a design system, pt. 3: Typographyhttps://mobileit.cz/Blog/Pages/design-system-3.aspxSo you want to create a design system, pt. 3: Typography<p>Long gone are the days when apps could only use a single system font, bold and italic at most, and that was it. Typography is now a significant part of product identity, but how do you apply it systematically?</p><p>As with colors, the most important thing is to avoid hardcoding type-specific values in the design tool and in the code. This means that you need to define a set of text styles and use them consistently wherever text appears in your UI. </p><p>Both major mobile platforms provide default text styles for different situations such as headings, subheadings, paragraphs, captions, or labels. However, these styles don't match across platforms, and it's also likely that your product needs won't fit neatly into these preset categories. In that case, rather than combining built-in and custom styles, it's easier to define your own styles for everything and ignore the built-in ones.</p><h2>Elements of style</h2><p>So what does the text style contain? To be on the safe side and avoid surprises caused by built-in components and their default values, you should always define at least the following properties:</p><ul><li><strong>Typeface:</strong> The font family you want to use. If appropriate on the platform, it is a good idea to specify a generic (or fall-back) font family too, such as serif, sans-serif, monospace, etc. </li><li><strong>Weight:</strong> Modern font families have a much wider range of weights than just regular and bold, not to mention variable fonts. The weight is usually expressed as a number or a name. Here is a table of the most common values: <img alt="Table of font weights" src="/Blog/PublishingImages/Articles/design-system-3-01.png" data-themekey="#" /> </li><li><strong>Style:</strong> Normal or italics, that's more or less it.</li><li><strong>Width:</strong> The width of each letter. Font families with variable widths are not quite common. Examples include: <img alt="Table of font widths" src="/Blog/PublishingImages/Articles/design-system-3-02.png" data-themekey="#" /> </li><li><strong>Case:</strong> Uppercase, lowercase, or small caps.</li><li><strong>Text decoration:</strong> Overline, underline, or strikethrough text.</li><li><strong>Size:</strong> The height of the characters. This is where it starts to get tricky, see implementation details in the next section. </li><li><strong>Letter spacing (tracking):</strong> The space between the characters. Zero is the default value specified in the font family, but it is often advisable to use a slightly higher positive value to improve readability (especially for paragraph styles with smaller text size), or a slightly negative value to better visually balance large headings. </li><li><strong>Line height:</strong> Vertical space between text lines, measured from baseline to baseline. Baseline is the invisible line on which each character sits, not including downstrokes (like in lowercase letters <em>p</em> or <em>g</em>).<br>As with letter spacing, each font family has a default value that may be adjusted for readability. </li><li><strong>Paragraph spacing:</strong> Vertical space between paragraphs.</li><li><strong>Paragraph alignment:</strong> Left, right, center, or block. Be careful with block alignment, as the legibility and visual quality of the resulting typesetting depends a lot on the quality of the algorithm used (which usually cannot be changed), including hyphenation algorithms for different languages. </li><li><strong>Text direction:</strong> If your application supports languages that are written from right to left, you often need to adjust layouts as well, and consistently use layout terms that are independent of text direction, such as start and end instead of left and right. </li><li><strong>Color:</strong> Should color be directly part of the text style specification? A slightly tricky question, both options have their pros and cons. However, specifying a default color probably won't do any harm, so I’d generally recommend including color in text style specification. </li></ul><h2>Practical type system</h2><p>So what text styles does a typical application need?</p><p>First of all, it is a good idea to distinguish between text styles for a text that stands on its own, as “top-level” content (headings, paragraphs, labels, captions, notes, etc.), and text styles for components that happen to contain text (buttons, menus, toolbars, input fields, tabs, etc.).</p><p>Some very well-known design systems don't distinguish between those usages (or, on the contrary, mix those together), but this is unfortunate—it often happens that in such systems a change of paragraph style unintentionally results in a change of the text style in some component like button or input field, which is something you usually don't want.</p><h2>Content text styles</h2><p>As with colors, it's a good idea to hide content text styles behind semantic names. The choice is completely yours, but usually, you will need at least several levels of headings and subheadings, one or two styles for regular text in paragraphs, accompanying styles such as captions or notes, and maybe even some styles for things like list items, etc.</p><p>If your app's domain is so specific that it's worth creating styles for concrete elements (e.g. cart items in an e-shop app, or waypoints in a navigation app), then definitely do so, even if those styles are visually very similar or even the same as the general-purpose styles. It's important to be able to change text styles that have common semantics (which means they change together, for the same reason), not just a coincidentally common look. </p> <img alt="Content text styles example" src="/Blog/PublishingImages/Articles/design-system-3-03.png" data-themekey="#" /> <h2>Component text styles</h2><p>What about component text styles? Most importantly, they should be considered private implementation details of the components, meaning they mustn’t be used in other components or stand-alone text.</p><p>They can only be reused between a group of tightly knit components, e.g., it’s fine to have a common text style for primary, secondary, outlined, or text buttons, but it’s a bad idea to share this style with unrelated components like tabs or chips—chances are, some of these components will change independently (at a different time, or for a different reason, or both), causing problems in unrelated places.</p><p>Beware—many platforms support some kind of inheritance for text styles, meaning you can derive a new style from an existing one by adding or overriding properties. Although this feature looks appealing because it can save implementation effort, when used incorrectly it leads to unwanted coupling, similar to the reuse of styles in unrelated components.</p><p>Never misuse inheritance as a tool to share implementation. Inheritance only works when it creates an “is-a” relationship—e.g., a secondary button certainly is a kind of button, but a tab is probably not a kind of button, and thus its text style should be kept separate.</p> <img alt="Component text styles example" src="/Blog/PublishingImages/Articles/design-system-3-04.png" data-themekey="#" /> <h2>Technical difficulties</h2><p>You may often encounter some complications during text style specification and implementation:</p><ul><li>Size units are a minefield. There are a large number of units and each platform uses its own specific ones. Sometimes the platform may even use different units for different things, which can be further complicated when the platform must support displays with different physical resolutions.<br>The system design specification needs to state the values using the units appropriate for each platform, or at least provide a conversion formula. </li><li>Be careful, not all fonts provide all the weights or styles. Some platforms then try to interpolate the weight when asked for a value that the font does not contain, and the result is usually obviously fake, and visually pretty bad; the same can happen with italics. <img alt="Real and fake italics and bold" src="/Blog/PublishingImages/Articles/design-system-3-05.png" data-themekey="#" /> </li><li>Since the text in the application can come from a variety of sources, and some have built-in formatting (e.g. when displaying HTML, Markdown, etc. with bold and italics applied), the formatting may interfere with the specified weight or text style. In this case, you need to either remove the formatting first, or specify what bold and italics actually mean for each text style. </li><li>You need to be absolutely sure that the font family you choose contains all the characters from all the languages your application supports. It's not just a problem of seeing embarrassing "tofu" blocks in place of missing glyphs, but also of rendering diacritics correctly. <img alt="Missing and invalid glyphs" src="/Blog/PublishingImages/Articles/design-system-3-06.png" data-themekey="#" /> </li><li>Especially if your application uses multiple font families, each with multiple weights and styles, pay attention to size of the font files you are packaging with the application. Higher download sizes increase customer churn. </li></ul><p>Other problems you may encounter include:</p><ul><li><a href="http://www.ravi.io/language-word-lengths">Languages vary greatly in word length.</a> You have to take this into account especially when designing layouts, because what fits on one line in one language may need two or more lines in another. You have to define what should happen in that case, e.g., words are replaced, ellipsized, hyphenated, text size is reduced… </li><li>Especially if you are designing mobile apps, you necessarily need to see all the text on an actual phone screen. Checking on a computer or laptop display is not enough, because it may give you a distorted impression of the size and, above all, the readability of the text.<br>Also remember that many people still use mobile devices with lower physical resolution, which has a big impact on readability, especially the text is small. It may often be appropriate to use other font families that render better on such devices. <img alt="Displays with different resolutions" src="/Blog/PublishingImages/Articles/design-system-3-07.png" data-themekey="#" /> </li></ul><h2>Headline2</h2><p>Typography plays a crucial aesthetic and practical role in a design system. At the same time, it is important to create a robust yet flexible set of text styles to support different functions of text in digital products.</p><p>Next time we'll look at one more important part of design systems—the grid.</p>#design-system;#ui;#ux;#development;#android;#iOS
So you want to create a design system, pt. 4: Layouts & dimensionshttps://mobileit.cz/Blog/Pages/design-system-4.aspxSo you want to create a design system, pt. 4: Layouts & dimensions<p>People today can choose between an incredible number of mobile devices that vary enormously in display size and resolution. How can you ensure your app looks and handles great on all of them? Let’s talk about dimensions, positions, and layouts, and how they fit into design systems!</p><h2>Absolute nonsense</h2><p>Some very popular design tools do this:</p> <img alt="Sample mobile screen with hardcoded dimensions" src="/Blog/PublishingImages/Articles/design-system-4-01.png" data-themekey="#" /> <p>It all looks nice and thoroughly specified, doesn't it? Wrong! Dimensions specified in absolute values have long been unusable for several reasons:</p><ul><li>Mobile device displays vary widely in terms of physical pixels.</li><li>Mobile devices also vary widely in terms of absolute physical display size—many phones are now approaching 7 inches, while at the other end of the spectrum devices around 5 inches are more than common. </li><li>The aspect ratios of displays also vary widely—today we can commonly see ratios such as 16:9, 18:9, 18.5:9, 19:9, 19.5:19, 20:9, 21:9, and this is by no means a complete list. </li><li>Since the advent of retina-like displays, physical pixels have become more or less irrelevant, and instead, we have to consider units independent of actual display density. </li><li>The amount of space your application gets on the display for rendering itself may vary depending on the presence, position, and size of system bars or display cutouts. </li><li>The operating system may also feature some sort of split-screen mode; and don’t get me started about foldables, large screen devices, and the like. </li></ul> <img alt="Different screen aspect ratios" src="/Blog/PublishingImages/Articles/design-system-4-02.png" data-themekey="#" /> <p>When designing screens for mobile devices, this means that you know literally <em>nothing</em> about the display, making absolute dimensions totally pointless (this also applies to platforms with a limited number of well-known models such as iOS—who knows what displays iPhones will have next year?). Hopefully, design tools will start to take this into account, but until that happens, we need to help ourselves in other ways.</p><h2>Units united</h2><p>So if physical pixels are no longer usable as a unit of dimension, what to use instead? Well, each platform has its own way of specifying dimensions independent of physical resolution, and unfortunately, you have to account for all of them in your design system.</p><p>For example, Android uses two units: scalable pixels (SP) for text sizes and density-independent pixels (DP) for everything else (actually, even that isn’t entirely true—as far as raster bitmaps are concerned, good old pixels are sometimes used as well, and Android letter-spacing units are totally weird). Moreover, SPs and DPs are converted to the same physical size by default, but this need not be the case if the user so chooses (for accessibility purposes).</p><p>Confused? This is perfectly understandable, but if the design system is to be usable, there is no choice but to learn how each platform handles dimensions, and then use those ways everywhere in the specs. The second best option is to use some abstract universal units and provide a conversion formula for each platform, but this way is more error-prone than specifying platform-native units that developers can use directly.</p><h2>Get into position</h2><p>Even when using platform-specific units, absolute positioning is still not feasible. So how do you adapt your layouts to all possible screen configurations?</p><p>The answer is relative and responsive positioning. Two things need to be specified for each component: Its dimensions and its relative position to some other component, its container, or screen. Together, these parameters form constraints that are used to unambiguously and responsively specify the entire layout, regardless of screen size, aspect ratio, etc.</p><p>Size constraints can be specified as:</p><ul><li>wrapping or “hugging” the content of the component</li><li>occupying all (or a proportion) of the available space</li><li>fixed values, but only in specific cases (e.g. icons)</li></ul><p>Some components may have additional options. For example, a component for displaying text can limit its height to the maximum number of lines displayed, or a component for displaying images can have a fixed aspect ratio.</p><p>It might be useful to add auxiliary parameters to these constraints, such as enforcing minimum or maximum dimensions, but then you must take care to prevent conflicts with other constraints.</p><p>The constraints for width and height are independent and can freely combine the above options, but only so that the resulting size is always unambiguous.</p><p>Relative positioning is quite simple: Each component must be anchored somehow in both horizontal and vertical directions to:</p><ul><li>another component</li><li>the container in which it is placed</li><li>the screen (this option must be handled with care)</li></ul> <img alt="Components and constraints" src="/Blog/PublishingImages/Articles/design-system-4-03.png" data-themekey="#" /> <p>Since the application has no control over how large a display will be available (and what its aspect ratio will be), nor how large the dynamic content will be (e.g. image size or text length), it is always necessary to specify a strategy for what to do if all the content does not fit. The basic options are:</p><ul><li>to make the component scrollable (remember that there is also a horizontal direction, but horizontal scrolling is usually much less intuitive) </li><li>limit the text by the number of characters or lines—in this case, it is necessary to also specify the indication of overflow (ellipsis, some kind of fade effect...) </li><li>crop the image to a fixed dimension or predefined aspect ratio (including the specification of whether to stretch or fit the image into the resulting container) </li></ul><h2>Everything’s not relative</h2><p>While the dimensions and positions of components should be specified as relative where possible for the design to be responsive, there are still situations where we need to use absolute values:</p><ul><li>spacings between components (or their margins)</li><li>container paddings</li><li>corner radii</li><li>dividers</li><li>elevation and/or z-index (depending on your platform, these may or may not be two different things) </li><li>small spacings used to visually align the parts encapsulated inside reusable components (e.g., space between icon and text inside a button) </li><li>parts of the typography specification (e.g., line-height; although in some cases these can be specified relatively as well) </li></ul><p>For the first five cases, it is absolutely essential (as in the case of <a href="/Blog/Pages/design-system-2.aspx">colours</a>) to introduce semantic dimension constants into the design system and then use them exclusively in all designs. You don't want to hardcode these values because it's easy to make a mistake when using them, and you also want to use them according to their purpose, not their value (which means there's no harm in having multiple semantic constants resolved to the same size).</p><p>So how to name these semantic constants? The first part is simple—it should always express the primary purpose (<span class="pre-inline">spacing</span>, <span class="pre-inline">padding</span>, <span class="pre-inline">cornerRadius</span>, <span class="pre-inline">elevation</span>, etc.), possibly combined with secondary usage (e.g., <span class="pre-inline">padding.screen</span>, <span class="pre-inline">padding.dialog</span>, etc.). In some cases, you’ll also need several size variations for a given purpose, so it's good to have a system for naming these variations as well, for example:</p><ul><li>size adjectives like <span class="pre-inline">tiny</span>, <span class="pre-inline">small</span>, <span class="pre-inline">normal</span>, <span class="pre-inline">medium</span>, <span class="pre-inline">large</span>, <span class="pre-inline">huge</span>—these work well, but don’t go overboard with quirky names like <span class="pre-inline">tiniest</span>, <span class="pre-inline">reallyReallyLarge</span>, <span class="pre-inline">humongous</span>; you need to be sure that the order from smallest to largest is always absolutely clear </li><li>T-shirt sizes like XS, S, M, L, XL, XXL—their order is unambiguous, but if you find out later that you need another value between, say, L and XL, you'll have a problem (this also applies to the first option to a certain degree) </li><li>clearly (this is important) dimensionless numbers without semantics like 100, 150, 200, 400, 1200—have the advantage of allowing you any number of values, you can always squeeze a new one between any two, and their order is unambiguous, but a problem will occur if this number is confused with the actual value, which is why I would recommend this only as a last resort (needless to say, the name of a constant must never contain its value) </li></ul><p>Putting it all together, your design system can define set of dimensions such as</p><pre> <code class="kotlin hljs">dimension.padding.screen.normal dimension.padding.screen.large dimension.padding.dialog.normal dimension.spacing.small dimension.spacing.normal dimension.spacing.large dimension.spacing.huge dimension.cornerRadius.normal dimension.cornerRadius.large dimension.elevation.none dimension.elevation.normal </code></pre><p>What actual values should these constants take? This is where it's also good to have a system so that your UI has a consistent rhythm. Different platforms usually have a grid defined as a multiple of some value (e.g., on Android it is 8 DP), and it's good to stick to that (so for example <span class="pre-inline">dimension.spacing.normal</span> might be 16 DP, <span class="pre-inline">dimension.spacing.large</span> 24 DP and so on) because your app always shares the screen with at least a part of the system UI, and ignoring the default grid might make your app feel subconsciously “wrong”.</p><p>And finally, what about the last two bullet points—dimensions used to visually tweak space inside encapsulated components, or in text styles? In this case (and only in this case!) I dare to say: hardcode them. Yes, I know, after all the talk about semantic constants, isolating values in one place and all that, this is unexpected, but I have two good reasons for that: </p><ol><li>These dimensions are implementation details of isolated, otherwise totally encapsulated components or text styles. They are completely hidden, not used externally anywhere, and not reused nor reusable. </li><li>Especially in the case of smaller components, these values are completely arbitrary, chosen based on visual appeal, and don't have to follow a predefined grid at all (e.g., if your button looks best with vertical padding of 3 DP, just use that value). </li></ol><h2>Putting it all together</h2><p>So let's apply the techniques we mentioned above to the example in the first figure:</p> <img alt="Sample mobile screens with dimension properties" src="/Blog/PublishingImages/Articles/design-system-4-04.png" data-themekey="#" /> <p>This is much better! Systematic and responsive, a UI specified this way is much easier to implement and use.</p><p>There are other things to think about:</p><ul><li>With interactive elements like buttons, the platform's minimum tap target size <em>in both dimensions</em> must be respected at all times. This includes not-so-obvious things like hyperlinks in paragraphs of text and the like. </li><li>If your app is running in fullscreen mode, you need to be <em>very</em> aware of and careful about display cutouts, the system UI overlaying your app, etc. </li><li>Modern phones are getting bigger and bigger, and if you use such a device single-handedly, you can't reach the whole screen with your fingers anymore. In this case, the screen is effectively divided into several zones, which differ substantially in how well they can be interacted with. Moreover, often the place that is the most prominent when visually scanning the screen (the top left corner) is also the hardest to reach physically, and vice versa. You have to take this very much into account and design a suitable compromise. And don’t forget about left-handed people! <img alt="Regions on phone screens reachable with a thumb" src="/Blog/PublishingImages/Articles/design-system-4-05.png" data-themekey="#" /> </li><li>Gesture implementation is another can of worms that we won't go into here. Even if your app doesn't support any custom gestures, current mobile platforms use system gestures that can interfere with your app. </li><li>Another major topic (for another blog post maybe) is support for foldable and large displays. This is where things like breakpoints, multi-panel screens, and the like come into play, and it's a whole different ballgame. </li></ul><h2>Design your system, systemize your designs</h2><p>This concludes our series on design systems. I hope it has helped to make the design and implementation of your mobile apps more consistent, easier, and more efficient, and who knows, maybe even a little more fun.</p>#design-system;#ui;#ux;#development;#android;#iOS
So you want to create a design system, pt. 2: Colorshttps://mobileit.cz/Blog/Pages/design-system-2.aspxSo 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
Architecture tests with ArchUnit, pt. 1: Reasonshttps://mobileit.cz/Blog/Pages/arch-unit-1.aspxArchitecture 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