Slide 1

Slide 1 text

fun() Talk Exploring Kotlin Functions @n8ebel

Slide 2

Slide 2 text

Kotlin ♥ Functions @n8ebel #droidconbos 2

Slide 3

Slide 3 text

Easy to Get Started • Can use the IDE conversion tool • Can try online (https://try.kotlinlang.org) • Easy to transfer existing knowledge @n8ebel #droidconbos 3

Slide 4

Slide 4 text

Flexible & Convenient • Parameter & type flexibility • Variations in scoping • Variety of modifiers @n8ebel #droidconbos 4

Slide 5

Slide 5 text

Freedom to Reimagine The flexibility & functionality of functions allow us to break away from traditional java conventions and reimagine how we build our projects @n8ebel #droidconbos 5

Slide 6

Slide 6 text

Let's Have Some fun() @n8ebel #droidconbos 6

Slide 7

Slide 7 text

From Methods to Functions @n8ebel #droidconbos 7

Slide 8

Slide 8 text

Hello Java Method void helloFunctions() { System.out.println("Yay, Functions"); } • return type • name • method body @n8ebel #droidconbos 8

Slide 9

Slide 9 text

java method converted to kotlin fun helloFunctions() { println("Yay, Functions") } • adds the fun keyword • no explicit return type • same name @n8ebel #droidconbos 9

Slide 10

Slide 10 text

Further Simplification fun helloFunctions() = println("Yay, Functions") @n8ebel #droidconbos 10

Slide 11

Slide 11 text

So What's Left? Seems pretty straightforward, what else is there to know? @n8ebel #droidconbos 11

Slide 12

Slide 12 text

A Lot @n8ebel #droidconbos 12

Slide 13

Slide 13 text

Let's Build On What We Know @n8ebel #droidconbos 13

Slide 14

Slide 14 text

Parameter & Type Freedom • Default parameters • Named parameters • Return types • when can we omit? • when can we infer? • Generic functions @n8ebel #droidconbos 14

Slide 15

Slide 15 text

Parameters fun helloFunctions(excitingThing:String) { println("Yay, " + excitingThing) } helloFunctions("functions") // outputs "Yay, functions" @n8ebel #droidconbos 15

Slide 16

Slide 16 text

Parameters fun helloFunctions(exclamation:String, excitingThing:String) { println(exclamation + ", " + excitingThing) } helloFunctions("Yay", "functions") // outputs "Yay, functions" @n8ebel #droidconbos 16

Slide 17

Slide 17 text

Now It Gets Interesting @n8ebel #droidconbos 17

Slide 18

Slide 18 text

Default Parameter Values fun helloFunctions(exclamation:String, excitingThing:String = "functions") { println(exclamation + ", " + excitingThing) } helloFunctions("Yay", "functions") // outputs "Yay, functions" helloFunctions("Yay") // outputs "Yay, functions" @n8ebel #droidconbos 18

Slide 19

Slide 19 text

Function parameters can have default values, which are used when a corresponding argument is omitted @n8ebel #droidconbos 19

Slide 20

Slide 20 text

Default Parameter Values • allows us the flexibility of overloads without the verbosity of writing them • help document the function contract by indicating what "sensible defaults" might be @n8ebel #droidconbos 20

Slide 21

Slide 21 text

Default Parameters & Java Java doesn't have default parameter values • must specific all parameter values when calling from Java • can use @JvmOverloads to generate overloads for each parameter • generated overloads will used the specified default values @n8ebel #droidconbos 21

Slide 22

Slide 22 text

Named Arguments Improve readability of function invocations helloFunctions("functions", "functions") • How do we know which value is correct? @n8ebel #droidconbos 22

Slide 23

Slide 23 text

Named Arguments Much easier to understand with named arguments helloFunctions(exclamation = "yay!", excitingThing = "functions") @n8ebel #droidconbos 23

Slide 24

Slide 24 text

Named Arguments Modify order of passed parameters by using named arguments fun helloFunctions(exclamation:String, excitingThing:String = "functions") { println(exclamation + ", " + excitingThing) } helloFunctions("Hooray", "functions") helloFunctions("Hooray") helloFunctions(excitingThing = "functions", exclamation = "Hooray") // all output "Hooray, functions" @n8ebel #droidconbos 24

Slide 25

Slide 25 text

Named Arguments There are limitations to how named & positioned arguments are used - once an argument name is specificed, all subsequent arguments must be named as well @n8ebel #droidconbos 25

Slide 26

Slide 26 text

Named Arguments helloFunctions("hooray", "Droidcon Boston") helloFunctions("hooray", excitingThing = "Droidcon Boston") // both output "hooray, Droidcon Boston" helloFunctions(excitingThing = "Droidcon Boston", "hooray") // error: Mixing named and positioned arguments not allowed @n8ebel #droidconbos 26

Slide 27

Slide 27 text

Variable Number of Arguments We can define a parameter to accept a variable number of arguments T - use the vararg keyword - the vararg param is then treated as an array of type T - default value must now be an array @n8ebel #droidconbos 27

Slide 28

Slide 28 text

Variable Number of Arguments fun helloFunctions(exclamation:String, vararg excitingThings:String) { for(excitingThing in excitingThings) { println(exclamation + ", " + excitingThing) } } helloFunctions("yay!", "Droidcon Boston", "Kotlin", "Android") // outputs: // yay!, Droidcon Boston // yay!, Kotlin // yay!, Android @n8ebel #droidconbos 28

Slide 29

Slide 29 text

Variable Number of Arguments Typically, a vararg parameter will be the last one Can be used in any order if: - other parameters are called using named argument syntax - last parameter is a function passed outside the parentheses @n8ebel #droidconbos 29

Slide 30

Slide 30 text

Variable Number of Arguments This works great helloFunctions("yay!", "Droidcon Boston", "Kotlin", "Android") helloFunctions("Droidcon Boston", "Kotlin", "Android", exlamation = "yay!") // both output: // yay!, Droidcon Boston // yay!, Kotlin // yay!, Android @n8ebel #droidconbos 30

Slide 31

Slide 31 text

Variable Number of Arguments This works helloFunctions("Droidcon Boston", "Kotlin", "Android") // output: // "Droidcon Boston, Kotlin" // "Droidcon Boston, Android" @n8ebel #droidconbos 31

Slide 32

Slide 32 text

Variable Number of Arguments This won't compile helloFunctions("Droidcon Boston", exlamation = "yay!", "Kotlin", "Android") // error: "no matching function" @n8ebel #droidconbos 32

Slide 33

Slide 33 text

Variable Number of Arguments Use "spread" operator to pass an existing array of values val thingsToBeExcitedAbout = arrayOf("Droidcon Boston", "Kotlin", "Android") helloFunctions("yay!", *thingsToBeExcitedAbout) // output: // yay!, Droidcon Boston // yay!, Kotlin // yay!, Android @n8ebel #droidconbos 33

Slide 34

Slide 34 text

Variable Number of Arguments "Spreading" can be used alone, or with other passed varargs as well helloFunctions("yay!", "coffee", *thingsToBeExcitedAbout) helloFunctions("yay!", *thingsToBeExcitedAbout, "coffee") • input array to the vararg parameter is handled in order @n8ebel #droidconbos 34

Slide 35

Slide 35 text

Return Types What is the return type? fun helloFunctions(exclamation:String, excitingThing:String="functions") { println(exclamation + ", " + excitingThing) } @n8ebel #droidconbos 35

Slide 36

Slide 36 text

If a function does not return any useful value, its return type is Unit @n8ebel #droidconbos 36

Slide 37

Slide 37 text

Return Types These are equivalent fun helloFunctions(exclamation:String, excitingThing:String="functions") : Unit { println(exclamation + ", " + excitingThing) } fun helloFunctions(exclamation:String, excitingThing:String="functions") { println(exclamation + ", " + excitingThing) } @n8ebel #droidconbos 37

Slide 38

Slide 38 text

Return A Non-Unit Type Functions with block body require explicit return type & call for non-Unit functions fun helloFunctions(exclamation:String, excitingThing:String="functions") : String { return exclamation + ", " + excitingThing } @n8ebel #droidconbos 38

Slide 39

Slide 39 text

Return A Non-Unit Type Can infer return type for single-expression functions fun helloFunctions(exclamation:String, excitingThing:String="functions") = exclamation + ", " + excitingThing @n8ebel #droidconbos 39

Slide 40

Slide 40 text

Generic Functions Like classes, functions may have generic type parameters public inline fun Iterable.filter(predicate: (T) -> Boolean): List { return filterTo(ArrayList(), predicate) } public fun Iterable.toHashSet(): HashSet { return toCollection(HashSet(mapCapacity(collectionSizeOrDefault(12)))) } listOf(2,4,6,8).filter{ ... } setOf(2,4,6).toHashSet() @n8ebel #droidconbos 40

Slide 41

Slide 41 text

concise convenient flexible @n8ebel #droidconbos 41

Slide 42

Slide 42 text

What Next? @n8ebel #droidconbos 42

Slide 43

Slide 43 text

Let's explore some variations on how you can create and use functions @n8ebel #droidconbos 43

Slide 44

Slide 44 text

Variations In Scope • Top-level • Member functions • Local • CompanionObject • Extension functions @n8ebel #droidconbos 44

Slide 45

Slide 45 text

Top-Level functions • Not tied to a class • Defined within a Kotlin file • Belong to their declared file's package • Import to use within other packages @n8ebel #droidconbos 45

Slide 46

Slide 46 text

Top-Level Function Patterns • Replace stateless classes filled with static methods • Swap your "Util" or "Helper" classes with functions @n8ebel #droidconbos 46

Slide 47

Slide 47 text

Top-Level Function Considerations • Not truly removing classes • Generated as a public static method on a class using a special convention • Kt.java @n8ebel #droidconbos 47

Slide 48

Slide 48 text

Top-Level Function Considerations Inside Logging.kt package logging fun log(error:Throwable) {...} @n8ebel #droidconbos 48

Slide 49

Slide 49 text

Top-Level Function Considerations Call from Kotlin log(Throwable("oops")) @n8ebel #droidconbos 49

Slide 50

Slide 50 text

Top-Level Function Considerations Generated Code public class LoggingKt { public static void log(Throwable error) {...} } @n8ebel #droidconbos 50

Slide 51

Slide 51 text

Top-Level Function Considerations Call from Java LoggingKt.log(new Throwable("oops")) @n8ebel #droidconbos 51

Slide 52

Slide 52 text

Top-Level Function Considerations Can override the generated class/file name • Add @file:JvmName() to function's file • Must be before the declared package @n8ebel #droidconbos 52

Slide 53

Slide 53 text

Top-Level Function Considerations Inside Logging.kt @file:JvmName("LoggingFunctions") package logging fun log(error:Throwable) {...} @n8ebel #droidconbos 53

Slide 54

Slide 54 text

Top-Level Function Considerations Call from Java LoggingFunctions.log(new Throwable("oops")) @n8ebel #droidconbos 54

Slide 55

Slide 55 text

Top-Level Function Summary • Function declared in a file outside of any class • Can replace stateless helper/util classes • Can override generated class name to improve Java interop @n8ebel #droidconbos 55

Slide 56

Slide 56 text

Member Functions • Function on a class or object • Like a Java method • Have access to private members of the class or object @n8ebel #droidconbos 56

Slide 57

Slide 57 text

Member Functions class Speaker() { fun giveTalk() { ... } } // create instance of class Speaker and call giveTalk() Speaker().giveTalk() @n8ebel #droidconbos 57

Slide 58

Slide 58 text

Member Function Considerations • Default arguments can't be changed in overridden methods • If overriding a method, you must omit the default values @n8ebel #droidconbos 58

Slide 59

Slide 59 text

Local Functions Functions inside of functions • Create a function that is scoped to another function • Useful if your function is only ever called from another function @n8ebel #droidconbos 59

Slide 60

Slide 60 text

Local Functions • Declare like any other function, just within a function • Have access to all params and variables of the enclosing function @n8ebel #droidconbos 60

Slide 61

Slide 61 text

Local Functions Why would you want this? • Clean code • Avoids code duplication • Avoid deep chains of function calls @n8ebel #droidconbos 61

Slide 62

Slide 62 text

Local Functions fun parseAccount(response:AccountResponse) : Account { ... val hobbies = response.getField("hobbies").map{ val key = it.getField("key") Hobby(key) } val favoriteFoods = response.getField("favorite_foods").map{ val key = it.getField("key") Food(key) } } @n8ebel #droidconbos 62

Slide 63

Slide 63 text

Local Functions fun parseAccount(response:AccountResponse) : Account { fun parseKey(entity:ResponseEntity) = entity.getField("key") ... val hobbies = response.getField("hobbies").map{ val key = parseKey(it) Hobby(key) } val favoriteFoods = response.getField("favorite_foods").map{ val key = parseKey(it) Food(key) } } @n8ebel #droidconbos 63

Slide 64

Slide 64 text

Local Function Considerations • Local function or private method? • Is the logic going to be needed outside the current function? • Does the logic need to be tested in isolation? • Is the enclosing function still readable? @n8ebel #droidconbos 64

Slide 65

Slide 65 text

Companion Objects • No static method/functions in Kotlin • Recommended to use top-level functions instead • What if you need access to private members of an object? @n8ebel #droidconbos 65

Slide 66

Slide 66 text

Companion Objects • Want to create a factory method? • Define a member function on a companion object to gain access to private members/constructors @n8ebel #droidconbos 66

Slide 67

Slide 67 text

Companion Objects class Course private constructor(val key:String) // won't work // can't access the private constructor fun createCourse(key:String) : Course { return Course(key) } @n8ebel #droidconbos 67

Slide 68

Slide 68 text

Companion Objects class Course private constructor(val key:String) { companion object { fun createCourse(key:String) : Course { return Course(key) } } } // can then call the factory method Course.createCourse("somekey") @n8ebel #droidconbos 68

Slide 69

Slide 69 text

Companion Object Function Considerations Java usage is ugly // from Java Course.Companion.createCourse("somekey") @n8ebel #droidconbos 69

Slide 70

Slide 70 text

Companion Object Function Considerations class Course private constructor(val key:String) { companion object Factory { fun createCourse(key:String) : Course { return Course(key) } } } // from Java Course.Factory.createCourse("somekey") @n8ebel #droidconbos 70

Slide 71

Slide 71 text

different scopes for different use cases @n8ebel #droidconbos 71

Slide 72

Slide 72 text

Variations @n8ebel #droidconbos 72

Slide 73

Slide 73 text

Variations • infix • extension • higher-order • inline @n8ebel #droidconbos 73

Slide 74

Slide 74 text

infix • infix keyword enables usage of infix notation • What is infix notation? • Can omit the dot & parentheses for the function call • "key" to "value" @n8ebel #droidconbos 74

Slide 75

Slide 75 text

infix • Must be a member function or extension function • Must take a single, non-varargs, parameter with no default value @n8ebel #droidconbos 75

Slide 76

Slide 76 text

infix class ConferenceAttendee { infix fun addInterest(name:String){...} } // call the function without dot or parentheses val attendee = ConferenceAttendee() attendee addInterest "Kotlin" @n8ebel #droidconbos 76

Slide 77

Slide 77 text

infix • Provides a very clean, human-readable syntax • Core building block of custom DSLs @n8ebel #droidconbos 77

Slide 78

Slide 78 text

infix "hello" should haveSubstring("ell") "hello" shouldNot haveSubstring("olleh") https://github.com/kotlintest/kotlintest @n8ebel #droidconbos 78

Slide 79

Slide 79 text

Extension Functions • Extend the functionality of an existing class • Defined outside the class • Used as if they were a member of a class @n8ebel #droidconbos 79

Slide 80

Slide 80 text

Why Extension Functions? • Clean-up or extend classes & apis you don't control • Remove helper classes & simplify top-level functions @n8ebel #droidconbos 80

Slide 81

Slide 81 text

Extension Functions // add a new function to the View class fun View.isVisible() = visibility == View.VISIBLE yourView.isVisible() @n8ebel #droidconbos 81

Slide 82

Slide 82 text

Extension Functions fun showToast( context: Context, msg:String, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(context, msg, duration).show() } showToast(context, "Toast!") @n8ebel #droidconbos 82

Slide 83

Slide 83 text

Extension Functions fun Context.showToast( msg: CharSequence, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, msg, duration).show() } context.showToast("Toast!") @n8ebel #droidconbos 83

Slide 84

Slide 84 text

Extension Function Considerations • How are these generated under the hood? • How are these called from Java? @n8ebel #droidconbos 84

Slide 85

Slide 85 text

Extension Function Considerations • Generated as static methods that accept the receiver object as it's first argument • Default behavior is to use <filename>Kt. @n8ebel #droidconbos 85

Slide 86

Slide 86 text

Extension Function Considerations // ContextExtensions.kt fun Context.showToast(...) { ... } // when called from Java ContextExtensionsKt.showToast(context, "Toast!"); @n8ebel #droidconbos 86

Slide 87

Slide 87 text

Higher-Order Functions • Functions that take, or return, other functions • Can be lambda or function reference • Many examples in the Kotlin standard library apply, also, run @n8ebel #droidconbos 87

Slide 88

Slide 88 text

Higher-Order Functions • Enable interesting patterns & conventions • Support functional programming • Can cleanup setup/teardown patterns such as shared prefs @n8ebel #droidconbos 88

Slide 89

Slide 89 text

Higher-Order Functions fun getScoreCalculator(level:Level) { return when (level) { Level.EASY -> { state:ScoreState -> state.score * 10 } Level.HARD -> { state:ScoreState -> state.score * 5 * state.accuracy } } } @n8ebel #droidconbos 89

Slide 90

Slide 90 text

Higher-Order Functions val predicate = { number:Int -> number > 5 } listOf(2,4,6,8).filter(predicate) @n8ebel #droidconbos 90

Slide 91

Slide 91 text

Higher-Order Functions fun filterTheList(value:Int) = value > 5 listOf(2,4,6,8).filter(::filterTheList) @n8ebel #droidconbos 91

Slide 92

Slide 92 text

Higher-Order Functions If the last parameter of a function is a function, you can omit the parentheses listOf(2,4,6,8).filter{ number -> number > 5 } @n8ebel #droidconbos 92

Slide 93

Slide 93 text

Higher-Order Functions public inline fun synchronized(lock: Any, block: () -> R): R { monitorEnter(lock) try { return block() } finally { monitorExit(lock) } } // call from Kotlin synchronized(database) { database.prePopulate() } @n8ebel #droidconbos 93

Slide 94

Slide 94 text

Higher-Order Function Performance "Using higher-order functions imposes certain runtime penalties" • Extra class created when using lambda • If lambda captures variables, extra object created on each call @n8ebel #droidconbos 94

Slide 95

Slide 95 text

inline • Helps solve higher-order function performance hits • Body of the inlined function is substituted for invocations of the function @n8ebel #droidconbos 95

Slide 96

Slide 96 text

inline inline fun synchronized(lock: Lock, action: () -> T): T { lock.lock() try { return action() } finally { lock.unlock() } } // call from Kotlin synchronized(Lock()) {...} @n8ebel #droidconbos 96

Slide 97

Slide 97 text

inline // sample usage fun inlineExample(l:Lock) { println("before") synchronized(l) { println("action") } println("after") } @n8ebel #droidconbos 97

Slide 98

Slide 98 text

inline With inline the generated code is equivalent to this // resulting code fun inlineExample(l:Lock) { println("before") lock.lock() try { println("action") } finally { lock.unlock() } println("after") } @n8ebel #droidconbos 98

Slide 99

Slide 99 text

Android Reimagined These features enabled us to rethink how we build our apps @n8ebel #droidconbos 99

Slide 100

Slide 100 text

Fewer Helper Classes • ContextHelper, ViewUtils • Replace with • top-level functions • extension functions @n8ebel #droidconbos 100

Slide 101

Slide 101 text

Less Boilerplate fun doTheThingSafely(theThing:() -> Unit) { try { theThing() } catch(error:Throwable) { // handle error } } @n8ebel #droidconbos 101

Slide 102

Slide 102 text

Upgrade Our Apis Can use extensions, default params, etc to cleanup common apis • Now seeing community supported examples of this • Android KTX: https://github.com/android/android-ktx • Anko: https://github.com/Kotlin/anko @n8ebel #droidconbos 102

Slide 103

Slide 103 text

Android KTX sharedPreferences.edit() .putString("key", "without ktx") .putBoolean("isLessBoilerplate", false) .apply() sharedPreferences.edit { putString("key", "with ktx") putBoolean("isLessBoilerplate", true) } @n8ebel #droidconbos 103

Slide 104

Slide 104 text

Cleaner Syntax fun log(msg:String) {...} inline fun runOnBackgroundThread(action:() -> Unit) { ... } • More fluent syntax • Simplify test mocking • Avoids extra classes @n8ebel #droidconbos 104

Slide 105

Slide 105 text

DSLs val articleBuilder = ArticleBuilder() articleBuilder { title = "This is the title" addParagraph { body = "This is the first paragraph body" } addParagraph { body = "This is the first paragraph body" imageUrl = "https://path/to/url" } } • https://proandroiddev.com/kotlin-dsl-everywhere- de2994ef3eb0 @n8ebel #droidconbos 105

Slide 106

Slide 106 text

DSLs DSL examples • https://github.com/gradle/kotlin-dsl • https://github.com/kotlintest/kotlintest • https://github.com/Kotlin/anko/wiki/Anko-Layouts • https://kotlinlang.org/docs/reference/type-safe- builders.html @n8ebel #droidconbos 106

Slide 107

Slide 107 text

Kotlin functions provide flexibility & freedom in how you build your apps @n8ebel #droidconbos 107

Slide 108

Slide 108 text

Go, and Have fun() • Easy to get started • Can build your understanding and usage of functions over time • Enables you to rethink how you build your applications @n8ebel #droidconbos 108

Slide 109

Slide 109 text

Ready to Learn More? • https://engineering.udacity.com • https://n8ebel.com/tag/kotlin • Udacity Course: https://www.udacity.com/course/kotlin-for- android-developers--ud888 • Kotlin In Action @n8ebel #droidconbos 109

Slide 110

Slide 110 text

Thanks For Coming @n8ebel #droidconbos 110

Slide 111

Slide 111 text

Let's Continue the Discussion with("n8ebel").apply { Twitter .com Medium Instagram Facebook GitHub } @n8ebel #droidconbos 111