(This page has no text content)
Programming Android with Kotlin Achieving Structured Concurrency with Coroutines Pierre-Olivier Laurence and Amanda Hinchman- Dominguez, with G. Blake Meike and Mike Dunn
Programming Android with Kotlin by Pierre-Olivier Laurence and Amanda Hinchman-Dominguez, with G. Blake Meike and Mike Dunn Copyright © 2022 Pierre-Olivier Laurence and Amanda Hinchman- Dominguez. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://oreilly.com). For more information, contact our corporate/institutional sales department: 800-998-9938 or corporate@oreilly.com. Acquisition Editor: Suzanne McQuade Development Editor: Jeff Bleiel Production Editor: Beth Kelly Copyeditor: Piper Editorial Consulting, LLC Proofreader: Audrey Doyle Indexer: WordCo Indexing Services, Inc. Interior Designer: David Futato Cover Designer: Karen Montgomery Illustrator: Kate Dullea December 2021: First Edition
Revision History for the First Edition 2021-12-03: First Release See http://oreilly.com/catalog/errata.csp?isbn=9781492063001 for release details. The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Programming Android with Kotlin, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc. The views expressed in this work are those of the authors, and do not represent the publisher’s views. While the publisher and the authors have used good faith efforts to ensure that the information and instructions contained in this work are accurate, the publisher and the authors disclaim all responsibility for errors or omissions, including without limitation responsibility for damages resulting from the use of or reliance on this work. Use of the information and instructions contained in this work is at your own risk. If any code samples or other technology this work contains or describes is subject to open source licenses or the intellectual property rights of others, it is your responsibility to ensure that your use thereof complies with such licenses and/or rights. 978-1-492-06300-1 [LSI]
Preface JetBrains created Kotlin for two reasons: there was no language that filled all the gaps in Android development using (legacy) Java libraries, and a new language would allow Android development to set trends, rather than just follow them. In February 2015, Kotlin 1.0 was officially announced. Kotlin is concise, safe, pragmatic, and focused on interoperability with Java code. It can be used everywhere Java is used today: for server-side development, Android apps, desktop or portable clients, IoT device programming, and much, much more. Kotlin gained popularity among Android developers quite rapidly, and Google’s decision to adopt Kotlin as the official language of Android development resulted in skyrocketing interest in the language. According to the Android Developers website, more than 60% of professional Android developers currently use Kotlin. The learning curve in Android is rather steep: admittedly, it’s hard to learn and harder to master. Part of the Android developer “upbringing,” for many, is to be exposed over time to unintended interactions between the Android operating system and the application. This book intends to bring those kinds of exposures to readers in depth and up close by examining such problems in Android. We’ll talk not only about Kotlin and Java, but also about the concurrency problems that arise when using Android and how Kotlin is able to solve them. We will sometimes compare Kotlin to Java when we believe doing so provides better insight (especially since most readers are expected to have a Java background). We can demonstrate, with working examples, how to bridge that gap, and how the underlying concepts of most Kotlin operations are more similar to the Java equivalent than not. The tasks will be organized by topic to provide software engineers with a structured decomposition of that mass of information, and they will show how to make an application robust and maintainable.
Additionally, users familiar with Java—including Android developers—will find their learning curve dramatically flatten when we present each of the common tasks in both Java and Kotlin. Where appropriate, we’ll discuss the difference and the pitfalls of one or both, but we hope to provide bite-size and easily digestible examples of a task that will “just work,” and enable the reader to consume and adapt to the modern paradigm, as well as become aware of the significance of the updated code immediately and instinctively. While Kotlin is fully interoperable with Java, other Java application development (server-side programming, desktop clients, middleware, etc.) has not caught on to the extent that Android has. This is largely due to the maintainer of Android (Google) strongly “encouraging” its users to make the change. Users are regularly migrating to Kotlin, but even more still fall back to Java for mission-critical work. Our hope is that this book will serve as the lifeline an Android developer needs to feel safe in committing to the advantages and simplicity that Kotlin represents. Who Should Read This Book Any of the over six million Android engineers. We believe that virtually every Android engineer could benefit from this book. While a small percentage will be fluent in Kotlin, even they will likely learn something from the information we’ll present. But realistically, we’re targeting the very large majority who haven’t made the transition to Kotlin. This book is also for those who have dipped a toe in but not gained the same level of familiarity with Kotlin that they may have accrued in Java-centric Android development: Scenario 1 A reader is proficient in Java, heard of this new Kotlin language, and wants to try it out. So they read some online tutorial and start using it and it works great. Soon they realize that this isn’t just a new syntax. The idioms aren’t the same (e.g., functional programming, coroutines) and a whole new way of developing is now possible. But they lack guidelines, structure. For them, this book is a perfect fit.
Scenario 2 A reader is part of a small team of Java developers. They have discussions about whether they should start including Kotlin in their project. Even if Kotlin is said to be 100% interoperable with Java, some colleagues argue that introducing another language will add complexity to the project. Others suggest it might limit the number of colleagues who will be able to work on the project because of the need to master two languages. The reader could use this book to convince their colleagues, if they can show that the benefits will outweigh the costs. Scenario 3 An experienced Android developer may have played around with Kotlin or written a feature in it, but still falls back to the home base of Java when things need to get done. This was the scenario we found ourselves in when realizing the book we’re pitching now would have made our lives much easier. This is also the state we see most commonly around us—many Android devs have touched Kotlin, and many feel like they understand enough to write it when necessary, but they are either unaware, or simply unconvinced, of the significance of data classes, immutable properties, and structured concurrency. We think this book will turn a curious person into a committed evangelist. Why We Wrote This Book There are plenty of books that show how Android works, how Kotlin works, or how concurrency works. Kotlin is becoming wildly popular with Android development for its easy adoption and cleaner syntax, but Kotlin offers Android much more than that: it offers new ways to solve concurrency problems in Android. We wrote this book to provide a unique and specific intersectionality of these topics in great depth. Both Android and Kotlin are rapidly changing, separately and together. Trying to keep up with all the changes can be difficult.
We view this book as a valuable checkpoint in history: showing where Android came from, where it is now, and how it will continue to evolve with Kotlin as the language matures. Navigating This Book Sometimes we include code snippets as screenshots instead of regular atlas code formatting. This is particularly useful with coroutines and flows, as suspension points are clearly identifiable. We also get type hints from the IDE. Chapter 1, “Kotlin Essentials” and Chapter 2, “The Kotlin Collections Framework” cover major notable transitions made with Android in Kotlin. While the information in these chapters is enough to give you a good grounding in Kotlin, further chapters will take a deeper dive into more complex/advanced features. Users familiar with Java or similar syntactic structures will find the translation surprisingly natural. Chapter 3, “Android Fundamentals” and Chapter 4, “Concurrency in Android” will provide you with a foundation in the Android system in relation to memory and threading. As in any other operating system, concurrency is hard to achieve. Chapter 5, “Thread Safety” through Chapter 11, “Performance Considerations with Android Profiling Tools” examine common issues surrounding memory and threading, while indicating how the Android framework has evolved over time to grant developers more control around them. In tandem, these chapters show how Kotlin’s extensions and language features can help developers write better applications faster. Chapter 12, “Trimming Down Resource Consumption with Performance Optimizations” explores the use of powerful Android developer tools to examine performance and memory-related analytics under the hood—to be able to see things you never really knew about. This book will provide engineers with professionally developed and curated implementations of the most common tasks seen in native Android development. Many tasks will
consist of a real-world problem, followed by the corresponding solution in both Java and Kotlin. When further explanation is required, the solutions will follow a snappy compare-and-contrast model with a focus on brevity and natural language. Conventions Used in This Book The following typographical conventions are used in this book: Italic Indicates new terms, URLs, email addresses, filenames, and file extensions. Constant width Used for program listings, as well as within paragraphs to refer to program elements such as variable or function names, databases, data types, environment variables, statements, and keywords. Constant width bold Shows commands or other text that should be typed literally by the user. Constant width italic Shows text that should be replaced with user-supplied values or by values determined by context. TIP This element signifies a tip or suggestion. NOTE This element signifies a general note.
WARNING This element indicates a warning or caution. Using Code Examples Supplemental material (code examples, exercises, etc.) is available for download at https://github.com/ProgrammingAndroidWithKotlin. If you have a technical question or a problem using the code examples, please send email to bookquestions@oreilly.com. This book is here to help you get your job done. In general, if example code is offered with this book, you may use it in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing examples from O’Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product’s documentation does require permission. We appreciate, but generally do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: “Programming Android with Kotlin by Pierre-Olivier Laurence, Amanda Hinchman-Dominguez, G. Blake Meike, and Mike Dunn (O’Reilly). Copyright 2022 Pierre-Olivier Laurence and Amanda Hinchman- Dominguez, 978-1-492-06300-1.” If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at permissions@oreilly.com. O’Reilly Online Learning
NOTE For more than 40 years, O’Reilly Media has provided technology and business training, knowledge, and insight to help companies succeed. Our unique network of experts and innovators share their knowledge and expertise through books, articles, and our online learning platform. O’Reilly’s online learning platform gives you on-demand access to live training courses, in-depth learning paths, interactive coding environments, and a vast collection of text and video from O’Reilly and 200+ other publishers. For more information, visit http://oreilly.com. How to Contact Us Please address comments and questions concerning this book to the publisher: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 800-998-9938 (in the United States or Canada) 707-829-0515 (international or local) 707-829-0104 (fax) We have a web page for this book, where we list errata, examples, and any additional information. You can access this page at https://oreil.ly/pak. Email bookquestions@oreilly.com to comment or ask technical questions about this book.
For news and information about our books and courses, visit http://oreilly.com. Find us on Facebook: http://facebook.com/oreilly Follow us on Twitter: http://twitter.com/oreillymedia Watch us on YouTube: http://youtube.com/oreillymedia Acknowledgments This book has been greatly strengthened and improved thanks to our technical reviewers, Adnan Sozuan and Andrew Gibel. We give thanks to the folks at O’Reilly for helping to bring us together and giving us all the support we needed to bring this book to life, especially Jeff Bleiel and Zan McQuade. We thank Roman Elizarov and Jake Wharton for taking the time to speak with us about direction on evolution in concurrency in Kotlin and low-level optics in Android. We thank our friends, family, and colleagues for support. We thank the Kotlin community, and the individuals who have taken the time to read early drafts and give feedback. Lastly, we dedicate this book to Mike Dunn: coauthor, colleague, friend, and father. We miss him dearly and hope this book is something he’d be proud of.
Chapter 1. Kotlin Essentials Kotlin was created by the JetBrains team from St. Petersburg, Russia. JetBrains is perhaps best known for the IntelliJ Idea IDE, the basis for Android Studio. Kotlin is now used in a wide variety of environments across multiple operating systems. It has been nearly five years since Google announced support for Kotlin on Android. According to the Android Developers Blog, as of 2021, over 1.2 million apps in the Google Play store use Kotlin, including 80% of the top one thousand apps. If you’ve picked up this book, we are assuming that you are already an Android developer and that you are familiar with Java. Kotlin was designed to interoperate with Java. Even its name, taken from an island near St. Petersburg, is a sly allusion to Java, an island in Indonesia. Though Kotlin supports other platforms (iOS, WebAssembly, Kotlin/JS, etc.), a key to Kotlin’s broad use is its support for the Java virtual machine (JVM). Since Kotlin can be compiled to Java bytecode, it can run anywhere that a JVM runs. Much of the discussion in this chapter will compare Kotlin to Java. It’s important to understand, though, that Kotlin is not just warmed-over Java with some added bells and whistles. Kotlin is a new and different language with connections to Scala, Swift, and C# that are nearly as strong as its connection with Java. It has its own styles and its own idioms. While it is possible to think Java and write Kotlin, thinking in idiomatic Kotlin will reveal the full power of the language. We realize that there may be some Android developers who have been working with Kotlin for some time, and who have never written any Java at all. If this sounds like you, you may be able to skim this chapter and its review of the Kotlin language. However, even if you are fairly handy with the language, this may be a good chance to remind yourself of some of the details.
This chapter isn’t meant to be a full-fledged primer on Kotlin, so if you are completely new to Kotlin, we recommend the excellent Kotlin in Action. Instead, this chapter is a review of some Kotlin basics: the type system, variables, functions, and classes. Even if you are not a Kotlin language expert, it should provide enough of a foundation for you to understand the rest of the book. As with all statically typed languages, Kotlin’s type system is the meta language that Kotlin uses to describe itself. Because it is an essential aspect for discussing Kotlin, we’ll start by reviewing it. The Kotlin Type System Like Java, Kotlin is a statically typed language. The Kotlin compiler knows the type of every entity that a program manipulates. It can make deductions about those entities and, using those deductions, identify errors that will occur when code contradicts them. Type checking allows a compiler to catch and flag an entire large class of programming errors. This section highlights some of the most interesting features of Kotlin’s type system, including the Unit type, functional types, null safety, and generics. Primitive Types The most obvious difference between Java’s and Kotlin’s type systems is that Kotlin has no notion of a primitive type. Java has the types int, float, boolean, etc. These types are peculiar in that they do not inherit from Java’s base type, Object. For instance, the statement int n = null; is not legal Java. Neither is List<int> integers;. In order to mitigate this inconsistency, each Java primitive type has a boxed type equivalent. Integer, for instance, is the analog of int; Boolean of boolean; and so on. The distinction between primitive and boxed types has nearly vanished because, since Java 5, the Java compiler automatically converts between the boxed and unboxed types. It is now legal to say Integer i = 1. 1 2
Kotlin does not have primitive types cluttering up its type system. Its single base type, Any, analogous to Java’s Object, is the root of the entire Kotlin type hierarchy. NOTE Kotlin’s internal representation of simple types is not connected to its type system. The Kotlin compiler has sufficient information to represent, for instance, a 32-bit integer with as much efficiency as any other language. So, writing val i: Int = 1 might result in using a primitive type or a boxed type, depending on how the i variable is used in the code. Whenever possible, the Kotlin compiler will use primitive types. Null Safety A second major difference between Java and Kotlin is that nullability is part of Kotlin’s type system. A nullable type is distinguished from its nonnullable analog by the question mark at the end of its name; for example, String and String?, Person and Person?. The Kotlin compiler will allow the assignment of null to a nullable type: var name: String? = null. It will not, however, permit var name: String = null (because String is not a nullable type). Any is the root of the Kotlin type system, just like Object in Java. However, there’s a significant difference: Any is the base class for all nonnullable classes, while Any? is the base class for all nullable ones. This is the basis of null safety. In other words, it may be useful to think of Kotlin’s type system as two identical type trees: all nonnullable types are subtypes of Any and all nullable types are subtypes of Any?. Variables must be initialized. There is no default value for a variable. This code, for instance, will generate a compiler error: val name: String // error! Nonnullable types must be initialized! As described earlier, the Kotlin compiler makes deductions using type information. Often the compiler can figure out the type of an identifier from
information it already has. This process is called type inference. When the compiler can infer a type, there is no need for the developer to specify it. For instance, the assignment var name = "Jerry" is perfectly legal, despite the fact that the type of the variable name has not been specified. The compiler can infer that the variable name must be a String because it is assigned the value "Jerry" (which is a String). Inferred types can be surprising, though. This code will generate a compiler error: var name = "Jerry" name = null The compiler inferred the type String for the variable name, not the type String?. Because String is not a nullable type, attempting to assign null to it is illegal. It is important to note that a nullable type is not the same as its nonnullable counterpart. As makes sense, a nullable type behaves as the supertype of the related nonnullable type. This code, for instance, compiles with no problem because a String is a String?: val name = Jerry fun showNameLength(name: String?) { // Function accepts a nullable parameter // ... } showNameLength(name) On the other hand, the following code will not compile at all, because a String? is not a String: val name: String? = null fun showNameLength(name: String) { // This function only accepts non-nulls println(name.length) }
showNameLength(name) // error! Won't compile because "name" // can be null Simply changing the type of the parameter will not entirely fix the problem: val name: String? = null fun showNameLength(name: String?) { // This function now accepts nulls println(name.length) // error! } showNameLength(name) // Compiles This snippet fails with the error Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?. Kotlin requires that nullable variables be handled safely—in a way that cannot generate a null pointer exception. In order to make the code compile, it must correctly handle the case in which name is null: val name: String? = null fun showNameLength(name: String?) { println(if (name == null) 0 else name.length) // we will use an even nicer syntax shortly } Kotlin has special operators, ?. and ?:, that simplify working with nullable entities: val name: String? = null fun showNameLength(name: String?) { println(name?.length ?: 0) } In the preceding example, when name is not null, the value of name?.length is the same as the value of name.length. When name is null, however, the value of name?.length is null. The expression
does not throw a null pointer exception. Thus, the first operator in the previous example, the safe operator ?., is syntactically equivalent to: if (name == null) null else name.length The second operator, the elvis operator ?:, returns the left expression if it is non-null, or the right expression otherwise. Note that the expression on the right-hand side is evaluated only if the left expression is null. It is equivalent to: if (name?.length == null) 0 else name.length The Unit Type In Kotlin, everything has a value. Always. Once you understand this, it is not difficult to imagine that even a method that doesn’t specifically return anything has a default value. That default value is named Unit. Unit is the name of exactly one object, the value things have if they don’t have any other value. The type of the Unit object is, conveniently, named Unit. The whole concept of Unit can seem odd to Java developers who are used to a distinction between expressions—things that have a value—and statements—things that don’t. Java’s conditional is a great example of the distinction between a statement and an expression because it has one of each! In Java you can say: if (maybe) doThis() else doThat(); You cannot, however, say: int n = if (maybe) doThis() else doThat(); Statements, like the if statement, do not return a value. You cannot assign the value of an if statement to a variable, because if statements don’t
return anything. The same is true for loop statements, case statements, and so on. Java’s if statement, however, has an analog, the ternary expression. Since it is an expression, it returns a value and that value can be assigned. This is legal Java (provided both doThis and doThat return integers): int n = (maybe) ? doThis() : doThat(); In Kotlin, there is no need for two conditionals because if is an expression and returns a value. For example, this is perfectly legal: val n = if (maybe) doThis() else doThat() In Java, a method with void as the return type is like a statement. Actually, this is a bit of a misnomer because void isn’t a type. It is a reserved word in the Java language that indicates that the method does not return a value. When Java introduced generics, it introduced the type Void to fill the void (intended!). The two representations of “nothing,” the keyword and the type, however, are confusing and inconsistent: a function whose return type is Void must explicitly return null. Kotlin is much more consistent: all functions return a value and have a type. If the code for a function does not return a value explicitly, the function has the value Unit. Function Types Kotlin’s type system supports function types. For example, the following code defines a variable, func, whose value is a function, the lambda { x -> x.pow(2.0) }: val func: (Double) -> Double = { x -> x.pow(2.0) } Since func is a function that takes one Double type argument and returns a Double, it’s type is (Double) -> Double.
In the previous example, we specified the type of func explicitly. However, the Kotlin compiler can infer a lot about the type of the variable func from the value assigned to it. It knows the return type because it knows the type of pow. It doesn’t, however, have enough information to guess the type of the parameter x. If we supply that, though, we can omit the type specifier for the variable: val func = { x: Double -> x.pow(2.0)} NOTE Java’s type system cannot describe a function type—there is no way to talk about functions outside the context of the classes that contain them. In Java, to do something similar to the previous example, we would use the functional type Function, like this: Function<Double, Double> func = x -> Math.pow(x, 2.0); func.apply(256.0); The variable func has been assigned an anonymous instance of the type Function whose method apply is the given lambda. Thanks to function types, functions can receive other functions as parameters or return them as values. We call these higher-order functions. Consider a template for a Kotlin type: (A, B) -> C. It describes a function that takes two parameters, one of type A and one of type B (whatever types those may be), and returns a value of type C. Because Kotlin’s type language can describe functions, A, B, and C can all, themselves, be functions. If that sounds rather meta, it’s because it is. Let’s make it more concrete. For A in the template, let’s substitute (Double, Double) -> Int. That’s a function that takes two Doubles and returns an Int. For B, let’s just substitute a Double. So far, we have ((Double, Double) -> Int, Double) -> C.
Comments 0
Loading comments...
Reply to Comment
Edit Comment