31天Kotlin
# 31DaysOfKotlin
— Week 1 Recap
The moreKotlin
code we write, the more we love it!Kotlin’s
modern language features together with Android KTX made our Android code more concise, clear and pleasant. We (@FMuntenescu and @objcode) started the #31DaysOfKotlin series as a way of sharing some of our favorite Kotlin
andAndroid KTX
features and hopefully get more of you to like it as much as we do.
![week2of4.png](https://img.haomeiwen.com/i1742298/e876542e2f186a03.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
In the first 7 days of Kotlin we focused on the basics.
Day 1: Elvis operator
Handling nulls in style? Check out the elvis operator ?:
, to cut your “null-erplate”. It’s just a small bit of syntax sugar to replace nulls with a default value or even return! Docs: Elvis operator.
val name: String = person.name ?: “unknown”
val age = person.age ?: return
Day 2: String templates
Formatting Strings? Refer to variables and expressions in string literals by putting $
in front of the variable name. Evaluate expressions using ${expression}
. Docs: string templates.
val language = "Kotlin"
// “Kotlin has 6 characters”
val text = "$language has ${language.length} characters"
Day 3: Destructuring declarations
Now with prisms? Android KTX uses destructuring to assign the component values of a color. You can use destructuring in your classes, or extend existing classes to add destructuring. Docs: destructuring declarations.
// now with prisms
val (red, green, blue) = color
// destructuring for squares
val (left, top, right, bottom) = rect
// or more pointedly
val (x, y) = point
Day 4: When expressions
A switch statement with superpowers? Kotlin’s when
expression can match on just about anything. Literal values, enums, ranges of numbers. You can even call arbitrary functions! Docs: when
class Train(val cargo: Number?) {
override fun toString(): String {
return when (cargo) {
null, 0 -> "empty"
1 -> "tiny"
in 2..10 -> "small"
is Int -> "big inty"
else -> "$cargo"
}
}
}
Day 5: For loops, range expressions and destructuring
For loops get superpowers when used with two other Kotlin features: range expressions and destructuring. Docs: ranges, destructuring.
// iterating in the range 1 to 100
for(i in 1..100) {…}
// iterating backwards, in the range 100 to 1
for(i in 100 downTo 1){…}
// iterating over an array, getting every other element
val array = arrayOf(“a”, “b”, “x”)
for(i in 1 until array.size step 2 ){…}
// iterating over an array with the item index and destructuring
for((index, element) in array.withIndex()) {…}
// iterating over a map
val map = mapOf(1 to “one”, 2 to “two”)
for( (key, value) in map){…}
Day 6: Properties
In Kotlin, classes can have mutable and read-only properties, with getters and setters generated by default. You can also implement custom ones if required. Docs: properties.
class User {
// properties
val id: String = "" // immutable. just getter
var name: String = "" // default getter and setter
var surname: String = "" // custom getter, default setter
get() = surname.toUpperCase() // custom getter declaration
var email: String = "" // default getter, custom setter
set(value) { // custom setter declaration
// “value” = name of the setter parameter
// “field” = property’s backing field; generated
if(isEmailValid(value)) field = value
}
}
Day 7: Data classes and equality
Creating classes with one role: to hold data? Mark them as “data” classes. The default implementation of equals()
is generated (so are hashCode()
, toString()
, and copy()
) and checks for structural equality. Docs: data classes, equality
data class User(
val name: String,
val email: String,
val address: Address,
…
)
public class UserListDiffCallback: DiffUtil.Callback() {
override fun areContentsTheSame(
oldItemPosition: Int,
newItemPosition: Int
): Boolean {
// use the generated equals method
return newUserList[newItemPosition] ==
oldUserList[oldItemPosition])
}
This week focused on the basics: removing null errors, simplifying loops and conditions, improving getters and setters, and removing boilerplate. Next week we’ll dive into more Kotlin features!
![week3of4.png](https://img.haomeiwen.com/i1742298/8426d35ca6659b15.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
In the second we we continued to explore Kotlin — going deeper into topics like sealed classes and inline.
Day 8: Visibility
In Kotlin, everything is public by default! Well, almost. Kotlin has a rich set of visibility modifiers you can use as well: private
, protected
, internal
. Each of them reduces the visibility in a different way. Docs: visibility modifiers
// public by default
val isVisible = true
// only in the same file
private val isHidden = true
// internal to compilation ‘module’
internal val almostVisible = true
class Foo {
// public by default
val isVisible = true
// visible to my subclasses
protected val isInheritable = true
// only in the same class
private val isHidden = true
}
Day 9: Default arguments
Is the number of method overloads getting out of hand? Specify default parameter values in functions. Make the code even more readable with named parameters. Docs: default arguments
// parameters with default values
class BulletPointSpan(
private val bulletRadius: Float = DEFAULT_BULLET_RADIUS,
private val gapWidth: Int = DEFAULT_GAP_WIDTH,
private val color: Int = Color.BLACK
) {…}
// using only default values
val bulletPointSpan = BulletPointSpan()
// passing a value for the first argument, others default
val bulletPointSpan2 = BulletPointSpan(
resources.getDimension(R.dimen.radius))
// using a named parameter for the last argument, others default
val bulletPointSpan3 = BulletPointSpan(color = Color.RED)
Day 10: Sealed classes
Kotlin sealed
classes let you easily handle error data. When combined with LiveData
you can use one LiveData
to represent both the success path and the error path. Way better than using two variables. Docs: sealed classes
sealed class NetworkResult
data class Success(val result: String): NetworkResult()
data class Failure(val error: Error): NetworkResult()
// one observer for success and failure
viewModel.data.observe(this, Observer<NetworkResult> { data ->
data ?: return@Observer // skip nulls
when(data) {
is Success -> showResult(data.result) // smart cast to Success
is Failure -> showError(data.error) // smart cast to Failure
}
})
You can also use sealed classes in a RecyclerView
adapter. They’re a perfect fit for ViewHolders
— with a clean set of types to dispatch explicitly to each holder. Used as an expression, the compiler will error if all types aren’t matched.
// use Sealed classes as ViewHolders in a RecyclerViewAdapter
override fun onBindViewHolder(
holder: SealedAdapterViewHolder?, position: Int) {
when (holder) { // compiler enforces handling all types
is HeaderHolder -> {
holder.displayHeader(items[position]) // smart cast here
}
is DetailsHolder -> {
holder.displayDetails(items[position]) // smart cast here
}
}
}
Going further with RecyclerViews
, if we have a lot of callbacks from a RecyclerView
item, like this one with detail clicks, shares, and delete actions, we can use sealed classes. One callback taking one sealed class can handle all the things!
sealed class DetailItemClickEvent
data class DetailBodyClick(val section: Int): DetailItemClickEvent()
data class ShareClick(val platform: String): DetailItemClickEvent()
data class DeleteClick(val confirmed: Boolean):
DetailItemClickEvent()
class MyHandler : DetailItemClickInterface {
override fun onDetailClicked(item: DetailItemClickEvent) {
when (item) { // compiler enforces handling all types
is DetailBodyClick -> expandBody(item.section)
is ShareClick -> shareOn(item.platform)
is DeleteClick -> {
if (item.confirmed) doDelete() else confirmDetele()
}
}
}
}
Day 11: Lazy
It’s good to be lazy! Defer the cost of expensive property initialization until they’re actually needed, by using lazy
. The computed value is then saved and used for any future calls. Docs: lazy
val preference: String by lazy {
sharedPreferences.getString(PREFERENCE_KEY)
}
Day 12: Lateinit
In Android, onCreate
or other callbacks initialize objects. In Kotlin non-null vals must be initialized. What to do? Enter lateinit
. It’s a promise: initialize me later! Use it to pinky-swear it will eventually be null safe. Docs: lateinit
class MyActivity : AppCompatActivity() {
// non-null, but not initalized
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
// …
// initialized here
recyclerView = findViewById(R.id.recycler_view)
}
}
Day 13: Require and check
Are your function arguments valid? Check before using them, with require
. If they’re not valid an IllegalArgumentException
is thrown. Docs: require
fun setName(name: String) {
// calling setName(“”) throws IllegalArgumentException
require(name.isNotEmpty()) { “Invalid name” }
// …
}
Is the state of your enclosing class correct? Use check
to verify. It will throw an IllegalStateException
if the value checked is false
. Docs: check
fun User.logOut(){
// When not authenticated, throws IllegalStateException
check(isAuthenticated()) { “User $email is not authenticated” }
isAuthenticated = false
}
Day 14: Inline
Can’t wait to use lambdas to make new APIs? Get in line. Kotlin lets you specify a function as inline
— which means calls will be replaced with the function body. Breathe and make lambda-based APIs with zero overhead. Docs: inline functions
// define an inline function that takes a function argument
inline fun onlyIf(check: Boolean, operation: () -> Unit) {
if (check) {
operation()
}
}
// call it like this
onlyIf(shouldPrint) { // call: pass operation as a lambda
println(“Hello, Kotlin”)
}
// which will be inlined to this
if (shouldPrint) { // execution: no need to create lambda
println(“Hello, Kotlin”)
}
This week went deeper into Kotlin features: visibility, default arguments, sealed classes, lazy, lateinit, require and check, and the really powerful inline. Next week we’ll dive into more Kotlin features and start exploring Android KTX.
week3of4.png
Week 3 of Kotlin was split between Kotlin functionalities and different ways of making your Android code sweeter with Android KTX.
Day 15: Operator overloading
Write Kotlin (time * 2)
faster with operator overloading. Objects like Path
, Range
or SpannableStrings
naturally allow for operations like addition or subtraction. With Kotlin, you can implement your own operators. Docs: operator overloading, Android KTX usage example.
// Definition
/** Adds a span to the entire text. */
inline operator fun Spannable.plusAssign(span: Any) =
setSpan(span, 0, length, SPAN_INCLUSIVE_EXCLUSIVE)
// Use it like this
val spannable = “Eureka!!!!”.toSpannable()
spannable += StyleSpan(BOLD) // Make the text bold with +=
spannable += UnderlineSpan() // Make the text underline with +=
Day 16: Top level functions and parameters
Utility methods for a class? Add them to the top level of the source file. In Java, they are compiled as static methods of that class. Docs: basic syntax.
// Define a top-level function that creates a DataBinding Adapter for a RecyclerView
@BindingAdapter(“userItems”)
fun userItems(recyclerView: RecyclerView, list: List<User>?){
//update the RecyclerView with the new list
…
}
class UsersFragment: Fragment{...}
Are you defining static constants for your class? Make them top-level properties. They will be compiled to a field and static accessor(s).
// Define a top-level property for Room database
private const val DATABASE_NAME = “MyDatabase.db”
private fun makeDatabase(context: Context): MyDatabase {
return Room.databaseBuilder(
context,
MyDatabase::class.java,
DATABASE_NAME
).build()
}
Day 17: Iterating types without an iterator
Iterators in interesting places? Android KTX adds iterators to ViewGroup
and SparseArray
. To define iterator extensions use the operator
keyword. Foreach loops will use the extensions! Docs: for loops, Android KTX usage example.
// Example from Android KTX
for(view in viewGroup) { }
for(key in sparseArray) {}
// Your project
operator Waterfall.iterator() {
// add an iterator to a waterfall class
}
for(items in myClass) {} // Now waterfall has iterations!
Day 18: Easy Content Values
Combine the power of ContentValues
with the brevity of Kotlin. Use the Android KTX ContentValues
creator and just pass a Pair<StringKey, Value>
. Android KTX implementation.
val contentValues = contentValuesOf(
“KEY_INT” to 1,
“KEY_LONG” to 2L,
“KEY_BOOLEAN” to true,
“KEY_NULL” to null
)
Day 19: DSLs
Specifically terrific? Domain specific languages can be made by using type safe builders. They make for clean APIs; and you can build them yourself too with the help of features like extension lambdas and type safe builders.
html {
head {
title {+”This is Kotlin!” }
}
body {
h1 {+”A DSL in Kotlin!”}
p {+”It’s rather”
b {+”bold.” }
+”don’t you think?”
}
}
}
Spek is a testing library built as a Kotlin DSL. Instead of using @Annotations
, Spek provides a typesafe way to declare your test code without relying on reflection magic.
@RunWith(JUnitPlatform::class)
class MyTest : Spek({
val subject = Subject()
given("it ’ s on fire") {
subject.lightAFire()
it("should be burning") {
assertTrue(subject.isBurning())
}
it("should not be cold") {
assertFalse(subject.isCold())
}
}
})
Another DSL for Kotlin on Android is Anko. Anko lets you build Android views using declarative code.
frameLayout {
button("Light a fire") {
onClick {
lightAFire()
}
}
Day 20: Easy Bundle
Bundle up, and get ready for the concise bundle creator in Android KTX. No more calls to putString
, putInt
, or any of their 20 friends. One call will make you a new Bundle
, and it’ll even handle Arrays!
val bundle = bundleOf(
"KEY_INT " to 1,
"KEY_LONG" to 2L,
"KEY_BOOLEAN" to true,
"KEY_NULL" to null
"KEY_ARRAY" to arrayOf(1, 2)
)
Day 21: Cleaning up postDelayed
Lambdas are sweet. With last parameter call syntax, you can cleanup callbacks, Callable
, and Runnable
. For example, Android KTX sweetens postDelayed
with a small wrapper.
// Android KTX API
fun Handler.postDelayed(
delay: Int,
token: Any? = null,
action: () -> Unit)
// Call it like this — no need for a Runnable in your code
handler.postDelayed(50) {
// pass a lambda to postDelayed
}
This week focused on a few basic Kotlin features like operators overloading, top level functions and parameters and iterators, we talked about an advanced feature: domain specific languages (DSLs) and showed how you can write more concise code when using Android KTX for content values, bundles and callbacks.
week4of4.png
Week 4 looked at more language basics then dove into some ways Android KTX makes your code more concise and readable!
Day 22: Calling Kotlin from the Java Programming Language
Using Kotlin and Java in the same project? Do you have classes with top-level functions or properties? By default, the compiler generates the class name as YourFileKt
. Change it by annotating the file with @file:JvmName
. Docs: package level functions
// File: ShapesGenerator.kt
package com.shapes
fun generateSquare(): Square {…}
fun generateTriangle(): Triangle {…}
// Java usage:
Square square = ShapesGeneratorKt.generateSquare();
// File: ShapesGenerator.kt
@file:JvmName(“ShapesGenerator”)
package com.shapes
fun generateSquare(): Square {…}
fun generateTriangle(): Triangle {…}
// Java usage:
Square square = ShapesGenerator.generateSquare();
Day 23: Reified
To make the concept of reified concrete an example is in order: Context.systemService()
in Android KTX uses reified to pass a “real” type via generics. No more passing classes to getSystemService
! Docs: reified type parameters
Android KTX: Context.systemService()
// the old way
val alarmManager =
context.getSystemService(AlarmManager::class.java)
// the reified way
val alarmManager: AlarmManager = context.systemService()
// the magic from Android KTX… with “real” type T
inline fun <reified T> Context.systemService() =
getSystemService(T::class.java)
Day 24: Delegates
Delegate your work to another class with by
. Favor composition over inheritance with class delegation and reuse property accessor logic with delegator properties. Docs: delegation and delegated properties
class MyAnimatingView : View( /* … */ ) {
// delegated property.
// Uses the getter and setter defined in InvalidateDelegate
var foregroundX by InvalidateDelegate(0f)
}
// A View Delegate which invalidates
// View.postInvalidateOnAnimation when set.
class InvalidateDelegate<T : Any>(var value: T) {
operator fun getValue(thisRef: View,
property: KProperty<*>) = value
operator fun setValue(thisRef: View,
property: KProperty<*>, value: T) {
this.value = value
thisRef.postInvalidateOnAnimation()
}
}
Day 25: Extension functions
No more Util classes! Extend the functionality of a class by using extension functions
. Put the name of the class you’re extending before the name of the method you’re adding. Doc: extension functions
Extension functions:
- Are not member functions
- Do not modify the original class in any way
- Are resolved an compile time by static type information
- Are compiled to static functions
- Don’t do polymorphism
Example: String.toUri()
// Extend String with toUri
inline fun String.toUri(): Uri = Uri.parse(this)
// And call it on any String!
val myUri = “www.developer.android.com".toUri()
Day 26: Drawable.toBitmap() easy conversions
If you ever converted a Drawable
to a Bitmap
then you know how much boilerplate you need.
Android KTX has a great set of functions to make your code more concise when working with classes from the graphics package. Docs: [graphics](https://github.com/android/android-ktx/tree/master/src/main/java/androidx/core/graphics
// get a drawable from resources
val myDrawable = ContextCompat.getDrawable(context, R.drawable.icon)
// convert the drawable to a bitmap
val bitmap = myDrawable.toBitmap()
Day 27: Sequences, lazy, and generators
Sequences are lists that never existed. A Sequence
is a cousin of Iterator
, lazily generating one value at a time. This really matters when using map
and filter
— they’ll create Sequences instead of copying the list for every step! Docs: sequences
val sequence = List(50) { it * 5 }.asSequence()
sequence.map { it * 2 } // lazy (iterate 1 element)
.filter { it % 3 == 0 } // lazy (iterate 1 element)
.map { it + 1 } // lazy (iterate 1 element)
.toList() // eager (make a new list)
You can make sequences from a list or by specifying a next function. If you never terminate a sequence, it can be infinitely long without running out of memory. With coroutines in Kotlin you can also use generators! Docs: generators
// make a sequence from a list
list.asSequence().filter { it == “lazy” }
// or, reach infinity functionally
val natural = generateSequence(1) { it + 1 }
// or, cooperate with coroutines
val zeros = buildSequence {
while (true) {
yield (0)
}
}
Day 28: Easier spans
Powerful but hard to use — that’s how the text styling Spans API feels.
Android KTX adds extension functions for some of the most common spans and makes the API easier to use. Android KTX: spannable string builder
val string = buildSpannedString {
append(“no styling text”)
bold {
append(“bold”)
italic { append(“bold and italic”) }
}
inSpans(RelativeSizeSpan(2f), QuoteSpan()){
append(“double sized quote text”)
}
}
Day 29: Parcelize
Love the speed of Parcelable
, but don’t like writing all that code? Say hello to @Parcelize
. Spec: Parcelize
@Parcelize
data class User(val name: String, val occupation: Work): Parcelable
// build.gradle
androidExtensions {
//Enable experimental Kotlin features
// in gradle to enable Parcelize
experimental = true
}
Day 30: updatePadding extensions
Extending existing APIs with default arguments usually makes everyone happy. Android KTX lets you set the padding on one side of a view using default parameters. A one line function that saves so much code! Android KTX: View.updatePadding
view.updatePadding(left = newPadding)
view.updatePadding(top = newPadding)
view.updatePadding(right = newPadding)
view.updatePadding(bottom = newPadding)
view.updatePadding(top = newPadding, bottom = newPadding)
Day 31: Scoping out run
, let
, with
, apply
Let’s run with some standard Kotlin functions! Short and powerful, run
, let
, with
, and apply
all have a receiver (this
), may have an argument (it
) and may have a return value. See the differences
run
val string = “a”
val result = string.run {
// this = “a”
// it = not available
1 // Block return value
// result = 1
}
let
val string = “a”
val result = string.let {
// this = this@MyClass
// it = “a”
2 // Block return value
// result = 2
}
with
val string = “a”
val result = with(string) {
// this = “a”
// it = not available
3 // Block return value
// result = 3
}
apply
val string = “a”
val result = string.apply {
// this = “a”
// it = not available
4 // Block return value unused
// result = “a”
}
This week covered some more language features, such as interop, refied, and sequences, then we moved on to Android KTX showing off some of the ways it helps you write concise and readable code. To finish off the series we covered the powerful Kotlin scope functions.