0% found this document useful (0 votes)
80 views

Effectivekotlin 180828123804

This document provides several useful Kotlin tips and techniques: - Leverage Kotlin's type system by using custom data classes and sealed classes to create your own type systems for improved type safety and compiler checks. - Nothing is a subtype of every type and can be used to indicate functions that never return. - When expressions can be used to assign variables, providing flexibility. - Platform types help integrate Kotlin and Java by providing null safety for platform calls that may return null. - Extension properties allow defining properties on existing classes without modifying them. Custom type systems add value but also have runtime costs like allocation and indirection, so balance is important.

Uploaded by

name coin
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
80 views

Effectivekotlin 180828123804

This document provides several useful Kotlin tips and techniques: - Leverage Kotlin's type system by using custom data classes and sealed classes to create your own type systems for improved type safety and compiler checks. - Nothing is a subtype of every type and can be used to indicate functions that never return. - When expressions can be used to assign variables, providing flexibility. - Platform types help integrate Kotlin and Java by providing null safety for platform calls that may return null. - Extension properties allow defining properties on existing classes without modifying them. Custom type systems add value but also have runtime costs like allocation and indirection, so balance is important.

Uploaded by

name coin
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 192

Effective Kotlin

Useful Kotlin tips you might have missed


@AOrobator + @YousufHaque
Leverage Kotlin’s Type System
Kotlin Type Diamond

https://www.slideshare.net/compscicenter/kotlin-2016-mixing-java-and-kotlin
Nothing cannot be Instantiated

public class Nothing private constructor()


A function returning nothing will never return

public inline fun TODO(reason: String): Nothing =


throw NotImplementedError("An operation is not implemented: $reason")
A function returning nothing will never return

public inline fun TODO(reason: String): Nothing =


throw NotImplementedError("An operation is not implemented: $reason")
Nothing is a Subtype of Everything

val integer: Int = 3

val myTodo: Int = if (true) integer else TODO("not implemented")


Nothing is a Subtype of Everything

val integer: Int = 3

val myTodo: Int = if (true) integer else TODO("not implemented")


Platform Types

val myUpperCaseString: String = nonNullString.toUpperCase()


Platform Types

val myCompilerError: String = nullableString.toUpperCase()


Platform Types

val mySafeAccess: String? = nullableString?.toUpperCase()


Platform Types
// Java Declaration
public class SomeClass {
public static String getNullString() {
return null;
}
}
Platform Types
// Java Declaration
public class SomeClass {
public static String getNullString() {
return null;
}
}

val myPlatformString: String! = SomeClass.getNullString()


Platform Types
// Java Declaration
public class SomeClass {
public static String getNullString() {
return null;
}
}

val myPlatformString: String! = SomeClass.getNullString()


Platform Types
// Java Declaration
public class SomeClass {
public static String getNullString() {
return null;
}
}

val myPlatformString: String! = SomeClass.getNullString()

// Runtime exception
val myNullPointerException: String = myPlatformString.toUpperCase()
Platform Types
// Java Declaration
public class SomeClass {
public static String getNullString() {
return null;
}
}

val myPlatformString: String? = SomeClass.getNullString()

// Compiler error!
val myUnsafeCall: String = myPlatformString.toUpperCase()
Platform Types
// Java Declaration
public class SomeClass {
public static String getNullString() {
return null;
}
}

val myPlatformString: String? = SomeClass.getNullString()

// Compile time safety!


val myNullableString: String? = myPlatformString?.toUpperCase()
Platform Types
// Java Declaration
public class SomeClass {
@Nullable
public static String getNullString() {
return null;
}
}

val myAnnotatedString: String? = SomeClass.getNullString()

val myNullableString: String? = myAnnotatedString?.toUpperCase()


Create your own type systems
Create your own type systems
interface PlaylistRepository {
fun addSongToPlaylist(songId: Long, playlistId: Long)
}
Create your own type systems
interface PlaylistRepository {
fun addSongToPlaylist(songId: Long, playlistId: Long)
}
Create your own type systems
interface PlaylistRepository {
fun addSongToPlaylist(songId: Long, playlistId: Long)
}

val playlist: Playlist = ...


val song: Song = ...

// This is a bug, but the compiler allows it!


playlistRepository.addSongToPlaylist(
songId = playlist.id,
playlistId = song.id
)
Custom Type Systems
Custom Type Systems
data class SongId(val id: Long)
data class PlaylistId(val id: Long)
Custom Type Systems
data class SongId(val id: Long)
data class PlaylistId(val id: Long)

interface PlaylistRepository {
fun addSongToPlaylist(songId: SongId, playlistId: PlaylistId)
}
Compile time safety
data class SongId(val id: Long)
data class PlaylistId(val id: Long)

interface PlaylistRepository {
fun addSongToPlaylist(songId: SongId, playlistId: PlaylistId)
}

// Compiler doesn’t like this. Red squiggles everywhere. Success!


playlistRepository.addSongToPlaylist(
songId = PlaylistId(42L),
playlistId = SongId(9001L)
)
Extension Properties
Extension Properties
data class SongId(val id: Long)
val Song.typedId: SongId = SongId(this.id) // Create extension property

data class PlaylistId(val id: Long)


val Playlist.typedId: PlaylistId = PlaylistId(this.id)
Extension Properties
data class SongId(val id: Long)
val Song.typedId: SongId = SongId(this.id) // Create extension property

data class PlaylistId(val id: Long)


val Playlist.typedId: PlaylistId = PlaylistId(this.id)

val song: Song = ...


val playlist: Playlist = ...

playlistRepository.addSongToPlaylist(song.typedId, playlist.typedId)
Custom type system: Sealed Classes
Custom type system: Sealed Classes
sealed class SongId
data class ValidSongId(val id: Long) : SongId()
object InvalidSongId : SongId()

val Song.typedId: SongId = if (this.id == -1L) InvalidSongId else SongId(this.id)


Custom type system: Sealed Classes
sealed class SongId
data class ValidSongId(val id: Long) : SongId()
object InvalidSongId : SongId()

val Song.typedId: SongId = if (this.id == -1L) InvalidSongId else SongId(this.id)


Custom type system: Sealed Classes
sealed class SongId
data class ValidSongId(val id: Long) : SongId()
object InvalidSongId : SongId()

val Song.typedId: SongId = if (this.id == -1L) InvalidSongId else SongId(this.id)


Custom type system: Sealed Classes
sealed class SongId
data class ValidSongId(val id: Long) : SongId()
object InvalidSongId : SongId()

val Song.typedId: SongId = if (this.id == -1L) InvalidSongId else SongId(this.id)


Custom type system: Sealed Classes
sealed class SongId
data class ValidSongId(val id: Long) : SongId()
object InvalidSongId : SongId()

val Song.typedId: SongId = if (this.id == -1L) InvalidSongId else SongId(this.id)


Custom type system: Sealed Classes
sealed class SongId
data class ValidSongId(val id: Long) : SongId()
object InvalidSongId : SongId()

val Song.typedId: SongId = if (this.id == -1L) InvalidSongId else SongId(this.id)


Custom type system: Sealed Classes
sealed class SongId
data class ValidSongId(val id: Long) : SongId()
object InvalidSongId : SongId()

val Song.typedId: SongId = if (this.id == -1L) InvalidSongId else SongId(this.id)


Custom type system: Sealed Classes
sealed class SongId
data class ValidSongId(val id: Long) : SongId()
object InvalidSongId : SongId()

val Song.typedId: SongId = if (this.id == -1L) InvalidSongId else SongId(this.id)

class RealmSongRepository: SongRepository {


override fun getSongById(songId: SongId) : Song? = when (songId) {
is ValidSongId -> getSongFromRealm(songId.id) // smart cast to ValidSongId
InvalidSongId -> null
// No else statement required, compiler knows we’ve covered all cases
}
}
Custom type system: Sealed Classes
sealed class SongId
data class ValidSongId(val id: Long) : SongId()
object InvalidSongId : SongId()

val Song.typedId: SongId = if (this.id == -1L) InvalidSongId else SongId(this.id)

class RealmSongRepository: SongRepository {


override fun getSongById(songId: SongId) : Song? = when (songId) {
is ValidSongId -> getSongFromRealm(songId.id) // smart cast to ValidSongId
InvalidSongId -> null
// No else statement required, compiler knows we’ve covered all cases
}
}
Custom type system: Sealed Classes
sealed class SongId
data class ValidSongId(val id: Long) : SongId()
object InvalidSongId : SongId()

val Song.typedId: SongId = if (this.id == -1L) InvalidSongId else SongId(this.id)

class RealmSongRepository: SongRepository {


override fun getSongById(songId: SongId) : Song? = when (songId) {
is ValidSongId -> getSongFromRealm(songId.id) // smart cast to ValidSongId
InvalidSongId -> null
// No else statement required, compiler knows we’ve covered all cases
}
}
Custom type system: Sealed Classes
sealed class SongId
data class ValidSongId(val id: Long) : SongId()
object InvalidSongId : SongId()

val Song.typedId: SongId = if (this.id == -1L) InvalidSongId else SongId(this.id)

class RealmSongRepository: SongRepository {


override fun getSongById(songId: SongId) : Song? = when (songId) {
is ValidSongId -> getSongFromRealm(songId.id) // smart cast to ValidSongId
InvalidSongId -> null
// No else statement required, compiler knows we’ve covered all cases
}
}
Custom type system: It Ain’t Free
Custom type system: It Ain’t Free
Runtime Downsides
● Allocation
● Indirection
Custom type system: It Ain’t Free
Runtime Downsides
● Allocation
● Indirection

Must strike balance of usage


Custom type system: It Ain’t Free
Runtime Downsides
● Allocation
● Indirection

Must strike balance of usage

Inline Classes - Coming in Kotlin 1.3


When statements as expressions
// RecyclerView view types
enum class ViewTypeEnum {
TYPE_1,
TYPE_2
}
When statements as expressions
// Can be used to assign variables
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutRes = when (ViewTypeEnum.values()[viewType]) {
VIEW_TYPE_1 -> R.layout.list_item_type1
VIEW_TYPE_2 -> R.layout.list_item_type2
}

val v: View = LayoutInflater


.from(parent.context)
.inflate(layoutRes, parent, false)

return ViewHolder(v)
}
When statements as expressions
// Can be used to assign variables
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutRes = when (ViewTypeEnum.values()[viewType]) {
VIEW_TYPE_1 -> R.layout.list_item_type1
VIEW_TYPE_2 -> R.layout.list_item_type2
}

val v: View = LayoutInflater


.from(parent.context)
.inflate(layoutRes, parent, false)

return ViewHolder(v)
}
// RecyclerView view types
enum class ViewTypeEnum {
TYPE_1,
TYPE_2,
TYPE_3 // Added view type
}
When statements as expressions
// Can be used to assign variables
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutRes = when (ViewTypeEnum.values()[viewType]) {
VIEW_TYPE_1 -> R.layout.list_item_type1
VIEW_TYPE_2 -> R.layout.list_item_type2
}

val v: View = LayoutInflater


.from(parent.context)
.inflate(layoutRes, parent, false)

return ViewHolder(v)
}
When statements as expressions
// Can be used to assign variables
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutRes = when (ViewTypeEnum.values()[viewType]) {
VIEW_TYPE_1 -> R.layout.list_item_type1
VIEW_TYPE_2 -> R.layout.list_item_type2
}

val v: View = LayoutInflater


.from(parent.context)
.inflate(layoutRes, parent, false)

return ViewHolder(v)
}
When statements as expressions
// Not an expression
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (ViewTypeEnum.values()[position]) {
VIEW_TYPE_1 -> bindFirstViewType(holder, position)
VIEW_TYPE_2 -> bindSecondViewType(holder, position)
}
}
When statements as expressions
// Not an expression
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (ViewTypeEnum.values()[position]) {
VIEW_TYPE_1 -> bindFirstViewType(holder, position)
VIEW_TYPE_2 -> bindSecondViewType(holder, position)
}
}
// Expression
override fun onBindViewHolder(holder: ViewHolder, position: Int) =
when (ViewTypeEnum.values()[position]) {
VIEW_TYPE_1 -> bindFirstViewType(holder, position)
VIEW_TYPE_2 -> bindSecondViewType(holder, position)
}
Think Functionally
Lambda Expression Syntax

val plus3: (Int) -> Int = { x: Int -> x + 3 }


Lambda Expression Syntax

val plus3: (Int) -> Int = { x: Int -> x + 3 }


Lambda Expression Syntax

val plus3: (Int) -> Int = { x: Int -> x + 3 }


Lambda Expression Syntax

val plus3: (Int) -> Int = { x: Int -> x + 3 }


val multiply: (Int, Int) -> Int = { x, y -> x * y }
Lambda Expression Syntax

val plus3: (Int) -> Int = { x: Int -> x + 3 }


val multiply: (Int, Int) -> Int = { x, y -> x * y }

val six = plus3(3)


val sixty = multiply(6, 10)
Lambda with Receiver

val plus3: Int.() -> Int = { this + 3 }


Lambda with Receiver

val plus3: Int.() -> Int = { this + 3 }


Lambda with Receiver

val plus3: Int.() -> Int = { this + 3 }


val multiplyBy: Int.(Int) -> Int = { factor -> this * factor }
Lambda with Receiver

val plus3: Int.() -> Int = { this + 3 }


val multiplyBy: Int.(Int) -> Int = { factor -> this * factor }

val six = 3.plus3()


val sixty = six.multiplyBy(10)
val fifty = 2.plus3().multiplyBy(10)
Top Level and Local Functions
class SomeClass {
fun someMemberFunction() {

}
}
Top Level and Local Functions
class SomeClass {
fun someMemberFunction() {

fun someLocalFunction() {...}

}
}
Top Level and Local Functions
class SomeClass {
fun someMemberFunction() {

fun someLocalFunction() {...}

someLocalFunction()

}
}
Top Level and Local Functions
class SomeClass {
fun someMemberFunction() {

fun someLocalFunction() {...}

someLocalFunction()

}
}

fun someTopLevelFunction() {
// some implementation
}
Top Level and Local Functions
class SomeClass {
fun someMemberFunction() {

fun someLocalFunction() {...}

someLocalFunction()
someTopLevelFunction()
}
}

fun someTopLevelFunction() {
// some implementation
}
view.setOnClickListener { doSomething() }
Higher Order Functions are functions
that take and/or return other functions
val users: List<User> = listOf(User("Andrew"), User("Yousuf"))
val users: List<User> = listOf(User("Andrew"), User("Yousuf"))

val userNames: List<String> = users.map { it.name }


What is functional programming?
Pure functions
Immutable Data
Explicit and isolated side effects
Monads
- Kotlin’s Nulls
- List
- Observable
- Option
- Try
- Validated
Monads
- Kotlin’s Nulls
- List
- Observable
- Option
- Try
- Validated
Monads
- Kotlin’s Nulls
- List
- Observable
- Option
- Try
- Validated
Monads
- Kotlin’s Nulls
- List
- Observable
- Option
- Try
- Validated
Monads
- Kotlin’s Nulls
- List
- Observable
- Option
- Try
- Validated

Check out Arrow, a functional programming library: arrow-kt.io


Monads
- Kotlin’s Nulls
- List
- Observable
- Option
- Try
- Validated

Check out Arrow, a functional programming library: arrow-kt.io


Try Data Type Definition
sealed class Try<out A> {

data class Failure<out A>(val exception: Throwable) : Try<A>()

data class Success<out A>(val value: A) : Try<A>()

}
Instantiating a Try Instance
inline operator fun <A> invoke(func: () -> A): Try<A> =

try {

Success(func())

} catch (e: Throwable) {

Failure(e)

}
Instantiating a Try Instance
inline operator fun <A> invoke(func: () -> A): Try<A> =

try {

Success(func())

} catch (e: Throwable) {

Failure(e)

}
Instantiating a Try Instance
inline operator fun <A> invoke(func: () -> A): Try<A> =

try {

Success(func())

} catch (e: Throwable) {

Failure(e)

}
Kotlin Doesn’t have Checked Exceptions
@Throws(NotImplementedError::class)

fun myUnsafeOperation(): Int {

TODO("not implemented")

}
Kotlin Doesn’t have Checked Exceptions
@Throws(NotImplementedError::class)

fun myUnsafeOperation(): Int {

TODO("not implemented")

}
Try to the Rescue
@Throws(NotImplementedError::class)

fun myUnsafeOperation(): Int {

TODO("not implemented")

fun mySafeOperation(): Try<Int> {

return Try { TODO("not implemented") }

}
Semantic Function Signature
@Throws(NotImplementedError::class)

fun myUnsafeOperation(): Int {

TODO("not implemented")

fun mySafeOperation(): Try<Int> {

return Try { TODO("not implemented") }

}
Take Control of Your API
Take Control of Your API
// Default Java way
TestSubscriber<SomePojo> testSubscriber = new TestSubscriber<SomePojo>();
Take Control of Your API
// Default Java way
TestSubscriber<SomePojo> testSubscriber = new TestSubscriber<SomePojo>();

// Fire off networking event (could also be any async event)


repository.doNetworkRequest()
.subscribe(testSubscriber);
Take Control of Your API
// Default Java way
TestSubscriber<SomePojo> testSubscriber = new TestSubscriber<SomePojo>();

// Fire off networking event (could also be any async event)


repository.doNetworkRequest()
.subscribe(testSubscriber);

// Write a bunch of boilerplate


testSubscriber.awaitTerminalEvent(1, TimeUnit.SECONDS);
Take Control of Your API
// Default Java way
TestSubscriber<SomePojo> testSubscriber = new TestSubscriber<SomePojo>();

// Fire off networking event (could also be any async event)


repository.doNetworkRequest()
.subscribe(testSubscriber);

// Write a bunch of boilerplate


testSubscriber.awaitTerminalEvent(1, TimeUnit.SECONDS);
testSubscriber.assertComplete();
Take Control of Your API
// Default Java way
TestSubscriber<SomePojo> testSubscriber = new TestSubscriber<SomePojo>();

// Fire off networking event (could also be any async event)


repository.doNetworkRequest()
.subscribe(testSubscriber);

// Write a bunch of boilerplate


testSubscriber.awaitTerminalEvent(1, TimeUnit.SECONDS);
testSubscriber.assertComplete();
testSubscriber.assertValueCount(1);
Take Control of Your API
// Default Java way
TestSubscriber<SomePojo> testSubscriber = new TestSubscriber<SomePojo>();

// Fire off networking event (could also be any async event)


repository.doNetworkRequest()
.subscribe(testSubscriber);

// Write a bunch of boilerplate


testSubscriber.awaitTerminalEvent(1, TimeUnit.SECONDS);
testSubscriber.assertComplete();
testSubscriber.assertValueCount(1);
testSubscriber.assertValue(expectedValue);
Take Control of Your API
// Wrap assertions into single extension function
infix fun <T> TestSubscriber<T>.`has single value`(value: T?) {
awaitTerminalEvent(1, TimeUnit.SECONDS)
assertComplete()
assertNoErrors()
assertValueCount(1)
assertValue(value)
}
Take Control of Your API
// Wrap assertions into single extension function
infix fun <T> TestSubscriber<T>.`has single value`(value: T?) {
awaitTerminalEvent(1, TimeUnit.SECONDS)
assertComplete()
assertNoErrors()
assertValueCount(1)
assertValue(value)
}
Take Control of Your API
// Wrap assertions into single extension function
infix fun <T> TestSubscriber<T>.`has single value`(value: T?) {
awaitTerminalEvent(1, TimeUnit.SECONDS)
assertComplete()
assertNoErrors()
assertValueCount(1)
assertValue(value)
}

testSubscriber `has single value` expectedValue // single line!


Take Control of Your API
// Wrap assertions into single extension function
infix fun <T> TestSubscriber<T>.`has single value`(value: T?) {
awaitTerminalEvent(1, TimeUnit.SECONDS)
assertComplete()
assertNoErrors()
assertValueCount(1)
assertValue(value)
}

testSubscriber `has single value` expectedValue // single line!


Take Control of Your API
// Original verbose API
val recordedRequest = mockWebServer.takeRequest()
assertEquals(recordedRequest.getHeader("Authorization"), "Bearer SECURE_TOKEN")
assertEquals(recordedRequest.method, "POST")
assertEquals(recordedRequest.path, "/path/to/api?param1=foo")
...
Take Control of Your API
// Original verbose API
val recordedRequest = mockWebServer.takeRequest()
assertEquals(recordedRequest.getHeader("Authorization"), "Bearer SECURE_TOKEN")
assertEquals(recordedRequest.method, "POST")
assertEquals(recordedRequest.path, "/path/to/api?param1=foo")
...
Take Control of Your API
// Original verbose API
val recordedRequest = mockWebServer.takeRequest()
assertEquals(recordedRequest.getHeader("Authorization"), "Bearer SECURE_TOKEN")
assertEquals(recordedRequest.method, "POST")
assertEquals(recordedRequest.path, "/path/to/api?param1=foo")
...
Take Control of Your API
// Original verbose API
val recordedRequest = mockWebServer.takeRequest()
assertEquals(recordedRequest.getHeader("Authorization"), "Bearer SECURE_TOKEN")
assertEquals(recordedRequest.method, "POST")
assertEquals(recordedRequest.path, "/path/to/api?param1=foo")
...

// With a little Kotlin Magic


mockWebServer `received request` ExpectedRequest(
authorization = "Bearer SECURE_TOKEN",
method = POST,
contentType = JSON,
path = "/path/to/api?param1=foo",
body = """{ "bar": 42 }"""
)
Take Control of Your API
// Original verbose API
val recordedRequest = mockWebServer.takeRequest()
assertEquals(recordedRequest.getHeader("Authorization"), "Bearer SECURE_TOKEN")
assertEquals(recordedRequest.method, "POST")
assertEquals(recordedRequest.path, "/path/to/api?param1=foo")
...

// With a little Kotlin Magic


mockWebServer `received request` ExpectedRequest(
authorization = "Bearer SECURE_TOKEN",
method = POST,
contentType = JSON,
path = "/path/to/api?param1=foo",
body = """{ "bar": 42 }"""
)
Take Control of Your API
// Original verbose API
val recordedRequest = mockWebServer.takeRequest()
assertEquals(recordedRequest.getHeader("Authorization"), "Bearer SECURE_TOKEN")
assertEquals(recordedRequest.method, "POST")
assertEquals(recordedRequest.path, "/path/to/api?param1=foo")
...

// With a little Kotlin Magic


mockWebServer `received request` ExpectedRequest(
authorization = "Bearer SECURE_TOKEN",
method = POST,
contentType = JSON,
path = "/path/to/api?param1=foo",
body = """{ "bar": 42 }"""
)
Take Control of Your API
enum class HttpMethod {
DELETE,
GET,
PATCH,
POST,
PUT
}
Take Control of Your API
enum class HttpMethod { enum class ContentType(val type: String) {
DELETE, FORM_URL_ENCODED("application/x-www-form-urlencoded"),
GET, JSON("application/json; charset=UTF-8"),
PATCH, }
POST,
PUT
}
Take Control of Your API
enum class HttpMethod { enum class ContentType(val type: String) {
DELETE, FORM_URL_ENCODED("application/x-www-form-urlencoded"),
GET, JSON("application/json; charset=UTF-8"),
PATCH, }
POST,
PUT
}

data class ExpectedRequest(


val authorization: String?,
val method: HttpMethod,
val contentType: ContentType?,
val path: String,
val body: String
)
Take Control of Your API
infix fun MockWebServer.`received request`(expectedRequest: ExpectedRequest) {
val actualRequest: RecordedRequest = this.takeRequest()

actualRequest `has authorization` expectedRequest.authorization


actualRequest `has method` expectedRequest.method.name
actualRequest `has content type` expectedRequest.contentType?.type
actualRequest `has path` expectedRequest.path
actualRequest `has body` expectedRequest.body
}
Take Control of Your API
infix fun MockWebServer.`received request`(expectedRequest: ExpectedRequest) {
val actualRequest: RecordedRequest = this.takeRequest()

actualRequest `has authorization` expectedRequest.authorization


actualRequest `has method` expectedRequest.method.name
actualRequest `has content type` expectedRequest.contentType?.type
actualRequest `has path` expectedRequest.path
actualRequest `has body` expectedRequest.body
}
Take Control of Your API
infix fun MockWebServer.`received request`(expectedRequest: ExpectedRequest) {
val actualRequest: RecordedRequest = this.takeRequest()

actualRequest `has authorization` expectedRequest.authorization


actualRequest `has method` expectedRequest.method.name
actualRequest `has content type` expectedRequest.contentType?.type
actualRequest `has path` expectedRequest.path
actualRequest `has body` expectedRequest.body
}
Take Control of Your API
infix fun MockWebServer.`received request`(expectedRequest: ExpectedRequest) {
val actualRequest: RecordedRequest = this.takeRequest()

actualRequest `has authorization` expectedRequest.authorization


actualRequest `has method` expectedRequest.method.name
actualRequest `has content type` expectedRequest.contentType?.type
actualRequest `has path` expectedRequest.path
actualRequest `has body` expectedRequest.body
}
Take Control of Your API
infix fun MockWebServer.`received request`(expectedRequest: ExpectedRequest) {
val actualRequest: RecordedRequest = this.takeRequest()

actualRequest `has authorization` expectedRequest.authorization


actualRequest `has method` expectedRequest.method.name
actualRequest `has content type` expectedRequest.contentType?.type
actualRequest `has path` expectedRequest.path
actualRequest `has body` expectedRequest.body
}

infix fun RecordedRequest.`has authorization`(authorization: String) {


this.getHeader("Authorization") `should equal` authorization
}
Take Control of Your API
infix fun MockWebServer.`received request`(expectedRequest: ExpectedRequest) {
val actualRequest: RecordedRequest = this.takeRequest()

actualRequest `has authorization` expectedRequest.authorization


actualRequest `has method` expectedRequest.method.name
actualRequest `has content type` expectedRequest.contentType?.type
actualRequest `has path` expectedRequest.path
actualRequest `has body` expectedRequest.body
}

infix fun RecordedRequest.`has authorization`(authorization: String) {


this.getHeader("Authorization") `should equal` authorization
}

// From Kluent assertions library https://markusamshove.github.io/Kluent/


infix fun <T> T.`should equal`(expected: T?): T = this.apply { assertEquals(expected, this) }
Take Control of Your API
// Original verbose API
val recordedRequest = mockWebServer.takeRequest()
assertEquals(recordedRequest.getHeader("Authorization"), "Bearer SECURE_TOKEN")
assertEquals(recordedRequest.method, "POST")
assertEquals(recordedRequest.path, "/path/to/api?param1=foo")
...

// With a little Kotlin Magic


mockWebServer `received request` ExpectedRequest(
authorization = "Bearer SECURE_TOKEN",
method = POST,
contentType = JSON,
path = "/path/to/api?param1=foo",
body = """{ "bar": 42 }"""
)
Take Control of Your API: Operator Overloading
Take Control of Your API: Operator Overloading

Kotlin allows us to provide alternate


implementations for operators such as
"+", "==", "in", etc.
Take Control of Your API: Operator Overloading

// https://github.com/ReactiveX/RxKotlin
operator fun CompositeDisposable.plusAssign(disposable: Disposable) {
add(disposable)
}
Take Control of Your API: Operator Overloading

// https://github.com/ReactiveX/RxKotlin
operator fun CompositeDisposable.plusAssign(disposable: Disposable) {
add(disposable)
}
Take Control of Your API: Operator Overloading

// https://github.com/ReactiveX/RxKotlin
operator fun CompositeDisposable.plusAssign(disposable: Disposable) {
add(disposable)
}

// Allows us to do
compositeDisposable += observable.subscribe()
Take Control of Your API: Delegated Properties
Take Control of Your API: Delegated Properties

“There are certain common kinds of properties,


that, though we can implement them manually every
time we need them, would be very nice to implement
once and for all, and put into a library.”

- Kotlin Documentation
Delegated Properties
private const val KEY_TITLE = "title"
private const val KEY_MESSAGE = "message"

fun newInstance(title: String, message: String): InfoDialogFragment {


val bundle = Bundle()
bundle.putString(KEY_TITLE, title)
bundle.putString(KEY_MESSAGE, message)

val fragment = InfoDialogFragment()


fragment.arguments = bundle

return fragment
}
Delegated Properties
private const val KEY_TITLE = "title"
private const val KEY_MESSAGE = "message"

fun newInstance(title: String, message: String): InfoDialogFragment {


val bundle = Bundle()
bundle.putString(KEY_TITLE, title)
bundle.putString(KEY_MESSAGE, message)

val fragment = InfoDialogFragment()


fragment.arguments = bundle

return fragment
}
Delegated Properties
private const val KEY_TITLE = "title"
private const val KEY_MESSAGE = "message"

fun newInstance(title: String, message: String): InfoDialogFragment {


val bundle = Bundle()
bundle.putString(KEY_TITLE, title)
bundle.putString(KEY_MESSAGE, message)

val fragment = InfoDialogFragment()


fragment.arguments = bundle

return fragment
}
Delegated Properties

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =


AlertDialog.Builder(activity!!)
.setTitle(arguments?.getString(KEY_TITLE))
.setMessage(arguments?.getString(KEY_MESSAGE))
.setPositiveButton("Okay", null)
.create()
Delegated Properties

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =


AlertDialog.Builder(activity!!)
.setTitle(arguments?.getString(KEY_TITLE))
.setMessage(arguments?.getString(KEY_MESSAGE))
.setPositiveButton("Okay", null)
.create()
Delegated Properties: Bundle Delegates
var Bundle.dialogTitle: String? by StringBundleDelegate()
var Bundle.dialogMessage: String? by StringBundleDelegate()
Delegated Properties: Bundle Delegates
var Bundle.dialogTitle: String? by StringBundleDelegate()
var Bundle.dialogMessage: String? by StringBundleDelegate()
Delegated Properties: Bundle Delegates
var Bundle.dialogTitle: String? by StringBundleDelegate()
var Bundle.dialogMessage: String? by StringBundleDelegate()
Delegated Properties: Bundle Delegates
var Bundle.dialogTitle: String? by StringBundleDelegate()
var Bundle.dialogMessage: String? by StringBundleDelegate()
Delegated Properties: Bundle Delegates
var Bundle.dialogTitle: String? by StringBundleDelegate()
var Bundle.dialogMessage: String? by StringBundleDelegate()

fun newInstance(title: String, message: String): InfoDialogFragment {


val bundle = Bundle()
bundle.dialogTitle = title
bundle.dialogMessage = message

val fragment = InfoDialogFragment()


fragment.arguments = bundle

return fragment
}
Delegated Properties: Bundle Delegates
var Bundle.dialogTitle: String? by StringBundleDelegate()
var Bundle.dialogMessage: String? by StringBundleDelegate()

fun newInstance(title: String, message: String): InfoDialogFragment {


val bundle = Bundle()
bundle.dialogTitle = title
bundle.dialogMessage = message

val fragment = InfoDialogFragment()


fragment.arguments = bundle

return fragment
}
Delegated Properties: Bundle Delegates

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =


AlertDialog.Builder(activity!!)
.setTitle(arguments?.getString(KEY_TITLE))
.setMessage(arguments?.getString(KEY_MESSAGE))
.setPositiveButton("Okay", null)
.create()
Delegated Properties: Bundle Delegates

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =


AlertDialog.Builder(activity!!)
.setTitle(arguments?.dialogTitle)
.setMessage(arguments?.dialogMessage)
.setPositiveButton("Okay", null)
.create()
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
class StringBundleDelegate {
operator fun getValue(
bundle: Bundle,
property: KProperty<*>
): String? {
return bundle.getString(property.name)
}

operator fun setValue(


bundle: Bundle,
property: KProperty<*>,
value: String?
) {
bundle.putString(property.name, value)
}
}
Bundle Delegates
var Bundle.dialogTitle: String? by StringBundleDelegate()
var Bundle.dialogMessage: String? by StringBundleDelegate()

fun newInstance(title: String, message: String): InfoDialogFragment {


val bundle = Bundle()
bundle.dialogTitle = title
bundle.dialogMessage = message

val fragment = InfoDialogFragment()


fragment.arguments = bundle

return fragment
}
Bundle Delegates: The Byte Code
Mac: ⌘ + ⇧ + A Windows: CTRL+SHIFT+A
Bundle Delegates: The Byte Code
Bundle Delegates: The Byte Code
Built-in Delegates
● Lazy

● Observable

● Vetoable
Built-in Delegates: Lazy
val lazyValue: String by lazy {
println("Computed!")
"Hello"
}
Built-in Delegates: Lazy
val lazyValue: String by lazy {
println("Computed!")
"Hello"
}

fun main(args: Array<String>) {


println(lazyValue) // prints Computed!\nHello
println(lazyValue) // prints Hello
}
Leverage the Extensions in the Standard Library
List<T> Map<K, V>
- map/flatMap - getOrPut
- any/all/none - getOrDefault
- count - count
- filter/filterNot - filter/filterKeys
- joinToString - mapKeys
- associateBy - flatMap
- indecies/lastIndex/last - mapNotNull
- groupBy
- drop/take
- mapIndex/mapNotNull
List<T> Map<K, V>
- map/flatMap - getOrPut
- any/all/none - getOrDefault
- count - count
- filter/filterNot - filter/filterKeys
- joinToString - mapKeys
- associateBy - flatMap
- indecies/lastIndex/last - mapNotNull
- groupBy
- drop/take
- mapIndex/mapNotNull
List<T> Map<K, V>
- map/flatMap - getOrPut
- any/all/none - getOrDefault
- count - count
- filter/filterNot - filter/filterKeys
- joinToString - mapKeys
- associateBy - flatMap
- indecies/lastIndex/last - mapNotNull
- groupBy
- drop/take
- mapIndex/mapNotNull
List<T> Map<K, V>
- map/flatMap - getOrPut
- any/all/none - getOrDefault
- count - count
- filter/filterNot - filter/filterKeys
- joinToString - mapKeys
- associateBy - flatMap
- indecies/lastIndex/last - mapNotNull
- groupBy
- drop/take
- mapIndex/mapNotNull
Kotlin Standard Library Extension Functions
Scoped to Receiver Receiver as Parameter

Returns
Receiver

Returns
Last
Expression

https://hackernoon.com/kotlin-a-deeper-look-8569d4da36f
Kotlin Standard Library Extension Functions
Scoped to Receiver

https://hackernoon.com/kotlin-a-deeper-look-8569d4da36f
Kotlin Standard Library Extension Functions
Receiver as Parameter

https://hackernoon.com/kotlin-a-deeper-look-8569d4da36f
Kotlin Standard Library Extension Functions

Returns
Receiver

https://hackernoon.com/kotlin-a-deeper-look-8569d4da36f
Kotlin Standard Library Extension Functions

Returns
Last
Expression

https://hackernoon.com/kotlin-a-deeper-look-8569d4da36f
Let

// receiver as parameter
// returns last expression
val someComponent: SomeDaggerComponent =
application
.let { it as MyApplication }
.someDaggerComponent
Let

// receiver as parameter
// returns last expression
val someComponent: SomeDaggerComponent =
application
.let { it as MyApplication }
.someDaggerComponent
Let

// receiver as parameter
// returns last expression
val someComponent: SomeDaggerComponent =
application
.let { it as MyApplication }
.someDaggerComponent
Let

// receiver as parameter
// returns last expression
val someComponent: SomeDaggerComponent =
application
.let { it as MyApplication }
.someDaggerComponent
Apply
// scoped to receiver
// returns receiver
val myBundle: Bundle =
Bundle().apply {
// this is Bundle
putString(MY_STRING_KEY, "myString")
putBoolean(MY_BOOLEAN_KEY, true)
}
Apply
// scoped to receiver
// returns receiver
val myBundle: Bundle =
Bundle().apply {
// this is Bundle
putString(MY_STRING_KEY, "myString")
putBoolean(MY_BOOLEAN_KEY, true)
}
Apply
// scoped to receiver
// returns receiver
val myBundle: Bundle =
Bundle().apply {
// this is Bundle
putString(MY_STRING_KEY, "myString")
putBoolean(MY_BOOLEAN_KEY, true)
}
Also

// receiver as parameter
// returns receiver
val myUser: User = User("Yousuf")
.also { user ->
Log.d("SomeTag", user.name)
}
Also

// receiver as parameter
// returns receiver
val myUser: User = User("Yousuf")
.also { user ->
Log.d("SomeTag", user.name)
}
Also

// receiver as parameter
// returns receiver
val myUser: User = User("Yousuf")
.also { user ->
Log.d("SomeTag", user.name)
}
Also

// receiver as parameter
// returns receiver
val myUser: User = User("Yousuf")
.also { user ->
Log.d("SomeTag", user.name)
}
Run
// scoped to receiver
// returns last expression
val myNullableString: String? = null

myNullableString
?.also { nonNullString ->
doSomethingForNonNullString(nonNullString)
}
?: run { doSomethingForNullString() }
Run
// scoped to receiver
// returns last expression
val myNullableString: String? = null

myNullableString
?.also { nonNullString ->
doSomethingForNonNullString(nonNullString)
}
?: run { doSomethingForNullString() }
Run
// scoped to receiver
// returns last expression
val myNullableString: String? = null

myNullableString
?.also { nonNullString ->
doSomethingForNonNullString(nonNullString)
}
?: run { doSomethingForNullString() }
Run
// scoped to receiver
// returns last expression
val myNullableString: String? = null

myNullableString
?.also { nonNullString ->
doSomethingForNonNullString(nonNullString)
}
?: run { doSomethingForNullString() }
Run
// scoped to receiver
// returns last expression
val myNullableString: String? = null

myNullableString
?.also { nonNullString ->
doSomethingForNonNullString(nonNullString)
}
?: run { doSomethingForNullString() }
Run
// scoped to receiver
// returns last expression
val myNullableString: String? = null

myNullableString
?.also { nonNullString ->
doSomethingForNonNullString(nonNullString)
}
?: run { doSomethingForNullString() }
1. Leverage Kotlin’s Type System
2. Think Functionally
3. Take Control of Your API
4. Leverage the Standard Library
1. Leverage Kotlin’s Type System
2. Think Functionally
3. Take Control of Your API
4. Leverage the Standard Library
1. Leverage Kotlin’s Type System
2. Think Functionally
3. Take Control of Your API
4. Leverage the Standard Library
1. Leverage Kotlin’s Type System
2. Think Functionally
3. Take Control of Your API
4. Leverage the Standard Library
Thank You

Twitter: @AOrobator Twitter: @YousufHaque


Github: AOrobator Github: Yousuf-Haque
Medium: @andreworobator

You might also like