droidcon NYC 2018 - Effective Kotlin

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
my name is Andrew or Roberto and I'm an Android engineer at smart things my name is Yousef Huck and I'm an Android engineer at Tim Hortons Kotlin is a language designed that it's a language designed to be easy to pick up but this is a talk about some topics that you may have missed along the way that could make you more effective developer day to day let's start with the Colin type system there's a bunch of interesting things going on that you can you know make use of this is Collins type Dimond it's it may look a little funky but basically there's like two graphs going on and if you'll note that everything that's in the left graph has a corresponding type in the right graph basically any non nullable type that you have has a nullable super type and similar to Java there's a concept in Java called object from which every other object inherits in Kotlin we have a similar object that's called any and every object in Kotlin inherits from any at some point conversely there's a concept in Colin that doesn't exist in Java and that's the concept of nothing nothing you'll see at the bottom of both the graphs nothing extends from everything why would we need such thing well we'll find out nothing can't be instantiated this is the entire definition of the nothing class it's got a private constructor all it is is a type it has no body this is it so why would this be useful we can actually specify functions that return nothing and what this basically means is this function will never return it's a function that will always throw but given that nothing is a subtype of everything we can actually use it in a situation that we wouldn't have been able to do in Java in in Kotlin let's say you wanted to do some incremental development and cover one branch of implementation compile it test it see make sure it worked and then move on to the other branch you can use this nothing definition or there's nothing tight to handle the else branch to do is a function defined in the Kotlin standard library and because nothing extends from everything including integer the type inference of this expression still evaluates to int even though we haven't fully implemented the other branch yet there's another concept to be aware of which is which is known as platform types platform types usually come into play when Kotlin code is interoperating with Java code this is the standard Kotlin scenario if I just have a non null string I can just call a function on it directly no safe call necessary however if I have a knowable string the compiler will complain and it won't let me call or invoke that function on it without prefixing it with a null safe operator and then the return type of the of the function also becomes a nullable operator however I want to propose a thought experiment let's say I have this Java class and this Java class just has one function that returns a string and in this case it's returning a null string well if I try to call this class from Kotlin the type hint that the compiler will infer is actually string exclamation point this is known as what's called a platform type platform types are types that the compiler doesn't actually know what the real null ability situation is you have to tell the compiler specifically in fact the editor will give you a warning saying hey I don't know what this type is you have to specify it explicitly but if you don't you can actually invoke a function on it in your Kotlin code the compiler will not complain and you are able to invoke it and execute it and you will get a runtime exception instead of a compile time exception which is the whole point of this null safety operation so what you can do is you can chain you can specify the type explicitly to a nullable string and then you get your type safety back and the compiler will complain again and you can go ahead and change your invocation to a safe invocation alternatively if you own the Java code that hasn't been specified you should add the support annotations for null ability the Android studio will pick up these annotations and force you to to call your null nullable functions with a with a safe call in order to become a more effective cotton developer in addition to liver - in addition to leveraging the existing type system it helps to create your own custom type system this way you can get compile time errors instead of runtime errors this is playlists repository it's the interface that I'm using for interacting with playlists in Oracle music player here we have a function add song to playlist that takes too long a song ID and a playlist ID the potential bug with this method is a little subtle but it could have really weird effects for the user I'm storing both IDs as Long's in the database this means that a playlist ID and a song ID are both recognized by the compiler as having the same type even though they're not logically the same thing this means that the compiler wouldn't complain if I accidentally switched around the parameters logically this doesn't make sense but the compiler is perfectly content with this buggy code it will successfully build and allow you to ship strange errors to your users with Kotlin you can create custom type systems to ensure compile time safety this way will fail the build whenever we pass in the wrong type of ID we'll start off with a class for a song ID and playlist ID then we'll change the parameters of our function so that it has the right logical types since this method accepts strongly typed arguments a song ID and a playlist ID if you accidentally mix up the parameters the compiler will fail the build now we have our compile time safety but we're not done we still need to actually generate these types of now I don't want to modify my model classes due to separation of concerns so I decided to make extension properties extension properties allow us to tack on additional properties to an existing class even if we don't own the source code then we can use them as part as if they were part of the original API now we can be sure that the compiler will fail the build if we switch up the parameters but we can go a little bit further in my codebase it's possible for a song to have an invalid ID if it hasn't been added to the database previously I was using an ID of negative one to represent this state but we can do much better with sealed classes here's the updated version of song ID where the type system takes into account that a song ID can be invalid the first difference is that we've turned song ID into a sealed class sealed classes have the ability to create restricted type hierarchies you can think of seal classes like enums on steroids just like an enum can only be one of its declared instances a sealed class can only be one of its declared subtypes unlike an enum you can instantiate multiple instances of the same subtype the way we've defined this type system a song ID can either be a valid song ID with an ID prop with an ID property or an invalid song ID that has no property since it only makes sense for there to be one instance of an invalid song ID will use Cullen's object keyword to create a singleton for us finally we'll update our extension property to take a to take account for this new type system sealed classes really shine when you use them in a wet expression if it's possible to verify this statement that the statement covers all cases you don't need to add an else clause to the statement however this works only if you use the result of the wine statement in an expression get song by ID returns a nullable song and by setting this function equal to this one statement we're forcing the win statement to evaluate to a nullable song the first case checks to see if the passed end song ID is an instance of valid song ID if it is the compiler automatically cap automatically smart cast it to a valid song ID so we can access the ID property and when the song ID is invalid we just return not know else statement is required because the compiler knows that we've covered all possible cases it's worth mentioning the costs of creating this type system we'll have to allocate memory for these wrapper classes and we'll have to under a consonant direction if we did this for everything there would be a significant penalty but if we do this for nothing we have a high chance of bugs take a look at Python or JavaScript that being said be on the lookout for a new feature in : 1.3 inline classes these have the type safety of wrapper classes with none of the overhead another way to improve call time safety is by using when statements as expressions let's say that you're creating a recyclerview adapter and it has multiple view types you decide to model these view types as an enum in oncreate view holder we use a one statement as an expression by assigning it to a variable since a one expression has to evaluate to an actual value it needs to be exhaustive or it needs to cover all possible cases if we add a new view type to our enum we get a compile-time error instead of silently ignoring the new type if we hover over there it reads when expression must be exhausted and necessary type 3 branch or else branch instead an online via holder we have a whene statement that is not an expression if we added a new view type here it would just get silently ignored however when we make the function equal the when statement we're now using the value of the when statement so it's an expression and we have compiled time safety adding a new view type in up here would cause a compiler Kotlin has support for a bunch of different functional programming concepts that a lot of job developers aren't used to and learning to think in these functional concepts or even just read some of the different functional concepts will would really pay off in navigating new code bases and even making api's of your own this is what's called lambda expressions ticket syntax this is how you define the type of a function here I have the arguments that are being passed into the function and in this case there's only one and then to the right-hand side of the arrow I have the return type of the function you can see that this is a function that plus is 3 to whatever you pass in you can also have as many arguments as you want they have to be separated via comas it within the parentheses that you can define and then once you've defined them you can call them and invoke them like you would any other function and note these are local variables that I've defined so you can pass these around like you would any other piece of data or or object this is a lambda with a receiver and the that's like thrown around a lot but it's not too hard to figure out the receiver is simply the type that you're gonna invoke this function on and in the body of the lambda you define the receiver is that this instance so in this case if I invoke this function on 7 that this is 7 and then it pluses 3 and returns 10 you can also pass additional arguments into a lambda with a receiver those go inside the parenthesis and are available to you as normal parameters and you still have access to the this keyword inside that your lambda and just like the regular lambdas you can call these and invoke these like you would any other function but what's nice about this is that you have a nice fluent chainable API that you can you know it's nice to read and it's nice to program in and you get the nice like dot autocomplete stuff Kotlin has support for top level and local functions and Java developers aren't usually used to this I'll explain why you would use each one let's just say I have a class and I have some function in a class and there's a piece of logic that I want to define and I want to reuse within this function but it's not so reusable that I want to pollute my class with it I want to use it basically within the scope of the function but I don't need it anywhere else I can actually define a function within a function and then invoke it from the enclosing scope and this is super useful for like utility functions or you know it's small little convenience things that you want to abstract but don't need to put it on the whole class namespace similarly Kotlin has top-level functions as well these are functions that can be defined in the top level of file they do not need to be in a class itself so here I have some top-level function and I can invoke it from anywhere I want and this is also useful for utility classes like taking a string and converting it string utils etc these are just functions that you can put at the top level but beware that if you put too many of these it's gonna pollute your global namespace and you know exercise caution who here has seen something like this before everyone should be raising their hand this is an example of a higher-order function a higher-order function is a function that takes in or returns another function and this is super useful for reusing pieces of logic or using pieces of data similar how you would like an interface or you know just some object but you don't have to go through the overhead of defining the actual interface or an actual class you can simply you know define the function that it's going to take and instantiate that you know whenever you need to this is another example of a higher-order function usage that I use all the time let's say I have a list of users and I want a list of names I can use the dot map function on list and pass it a function that will grab the name from whatever user is passed in and return it so what this is going to do is it's going to take this list and take my lambda and apply it to each element in the collection and then create a new collection out of whatever was returned out of my lambda and in this case the user of the name of the user so we've been talking about a couple of functional things but that's not what functional programming is functional programming comprises a few different concepts some of which may be useful to you some of which may not but I'd like to go over some of that the first is a concept of pure functions a pure function is one that takes in a series of parameters does some computation or calculation and then return something else and it does this without mutating any of the arguments or without mutating any of the global global state nor without accessing any dependencies that were not defined as parameters so basically it's this black box you put something in you get something out and what's important to note about a pure function is that given that there's no dependency on state and it doesn't change state anytime you call a pure function with a given set of parameters it will always return the same result same parameters in same parameters out always it's like a function in the math sense another concept is the concept of immutable data Kotlin has the Val keyword that allows for read-only properties it also has data classes that kind of bring like value type like functionality to Kotlin and the copy method allows you to basically change certain properties on a data class and instantiate a new one without mutating or modifying the original one and when you combine that with explicit and isolated side-effects you basically push your mutation and state changes to specific portions of your application which basically means that the rest of your application is easy to reason about because there's no state changing everything's instantiating a new object and the areas that do change state very explicitly change state so it's easy to predict what's going on and what's happening in your code base monads will inevitably inevitably come up in a conversation about functional programming I'm not going to go into the mathematical definition of what if it what it is but you basically need to know that a monad is something that basically has like some amount of context with it you've probably been using them already cought lends nulls are monads they basically represent null or not no list as a monad that represents collection observable represents changes over time option represents presence or absence validated represents something that's valid or invalid that sounds kind of like my song ID types it is in fact I'll be talking about something that you wear you can grab an existing implementation of valid valid from which you can grab an existing implementation of that I'll get to that in a second sorry but I want to take a minute to talk about option people asked me about why would I use option if I have Collins nulls well in rxjava 2 you can't push a null into a stream so you would actually want to wrap it at an option and push that into a stream instead and I also want to talk about try and we'll dive into an implication of it and we've already talked about validated so spoiler alert there's a library that implements a bunch of these so you don't have to implement yourself and implement them yourself which is what Andrew was saying arrow is a library that is meant to be the functional companion to the Kotlin standard library most of these are implemented in it I encourage you to go check it out it's a bunch of useful things and actually let's take let's take a dive into the Tri monad and I've got a snippet from the library itself here we have a sealed class of tri that carries just some type a and it can either be a success or a failure the success carries the actual value failure carries the exception so here's how you would like wrap something in a try this is taken from the library as well this is how you bring something into the Tri context basically you pass it a function that returns some value func no parameters to a and you try to execute it and if it succeeds it wraps that result in a success and if it fails it wraps that error that it caught in a failure and the way you use that is instead of using Collins lakh so Kotlin doesn't have checked exceptions the way you get around that is by annotating a function with a throws throws annotation but Collins compiler won't force you to catch that exception only Java's compiler will so the way you can get around that is by instead using the try monad and using that to carry the success or failure of an object and what what's nice is that you get a semantically meaningful function signature you know that this is something that can succeed or fail by the return type and you're guaranteed that you will not get a runtime exception or or anything like that because you're catching it or before it can you know be throwing up the stat in order to become a more effective Kotlin developer you should take control of your api i recently wrote some code that did a bunch of networking calls using RX java when it was time to start writing tests i took a look at some old java code the java way to do this would have been to first declare a test subscriber then fire off the networking event and after that there was a solid chunk of boilerplate code we have to wait a second to give the network time to respond we see yep we have to wait we'd have to assert that the test subscriber had successfully completed we would make sure that we only got one value count and that value was what we expected it to be instead of writing these same assertions over and over again i decided to write an extension function here we're creating an extension function on a generic test subscriber that will do all that all of the assertions for us the function name is sandwich by backticks and this allows us to put spaces in the name of our function for readability backticks and function names is something that you can only do on the JVM so regular Android doesn't support this now let's look at the call site all they want line and since we made this an infix function we don't have to use parentheses when we pass in this function single parameter when I was writing tests for my networking code I also wanted to make sure that I was sending out the proper requests to do this I had to grab the request from mock web server then I had to write assert statements for every single attribute that I wanted to test again I found myself dealing with a lot of boilerplate so I decided to take control of my API with the help of extension functions I was able to eliminate boilerplate and improve the readability of my tests and since the request method and content type were both enums I was able to use autocomplete when I was writing my tests by the way down at the bottom body is being set to a Kotlin raw string you define one using triple quotes and they're pretty useful for displaying JSON because you don't have to escape special characters let's dig into how all this is implemented first we'll create an enum called HTTP method which contains all the HTTP methods next we'll create a an enum for all the content types that were used once that's done we can create the expected request plot data class which holds all the attributes of the requests that we'd like to validate after that we'll define an extension function on mock web server that's going to do the actual assertions for us just like the Java code we start out by getting the recorded request from mock web server in an extension function this keyword refers to the receiver type in this case it's the instance of mock web server that were interacting with next we'll make a series of assertions that check each attribute of the request that were interested in let's dive into has authorization one of the extension functions that I defined on recorded request it grabs the off header and says and that it says that it should equal authorization this should equal method comes from the fluent assertions library clue n't and it just forwards calls to j-unit assertions it's part of what inspired me to write this code fluent has many other useful methods that improve readability for your tests and I'd recommend checking it out and here's the finished call sign another way to take control of your API is to use operator overloading Kotlin allows us to use our calendar Lau's us to provide alternate implementations for operators such as plus double equals and so on for an example let's take a look at a library called rx Colin it provides Kotlin extensions to make rx Java easier to work with it's sort of similar to Android KTX the operator keyword paired with the function name plus a sign tells Colin we want to override the plus equals operator and this allows us to achieve the following syntax another feature of colin that can help you take control of your api our delegated properties there are certain kinds of properties that even though we can implement them manually every time it'd be nice to refactor and put them into a library as android developers were all used to working with oneness there are a couple of steps involved which can get kind of tedious sometimes first we have to declare a constant for constants for the keys and then we have to call put string and pass in the key as well as our desired value once we've put our values in the bundle we have to call getstring and pass in our predefined constants with delegated properties we can actually eliminate a lot of this boilerplate we'll start off by declaring a couple of extension properties on our bundle next we'll use the by keyword this lets the compiler know that we want to use delegation this means that whatever comes next will handle how our property is accessed finally we have the actual delegates themselves whenever we call get or set on this property those calls will be forwarded to our delegates now when we want to put things in our bundle we can just use the properties that we've defined and getting the properties out of the bundle is just as easy just reference the extension property let's take a closer look at string bundle delegate to see what's going on under the we can see that string bundle delegate has two methods get value and set value we'll go over get value first the first thing to note is the operator keyword this let's call and note that we're going to overload a particular operator since the name of this function is get value the compiler knows we'll be using delegation and overloading the by operator now let's take a closer look at the method signature the first parameter is a bundle and it's the bundle that we're using property delegation on next we have a que property this is an interface which gives us all sorts of details and information about the property then we have the return type string now we've come to the body of the function this is where we're actually interacting with the bundle as you can see we're just calling getstring and instead of declaring a constant for the key we're just using the name of the property set value works in a very similar way again we have the operator keyword in combination with the function name set value this lets the compiler know that we're going to be using delegation the first parameter again is the bundle that we're using delegation on and the next property is the property that we're delegating just like with get value and the last parameter is the value that we want to set the body of the function is what you'd expect it to be we're just calling put string on the bundle and using the properties name as the key and here's how it's used just like a regular property for the more curious it's possible to take a look at the Kotlin byte code in order to gain a deeper understanding of what's going on after invoking find action you can type in show call in byte code and once you're there you can hit the decompile button and you'll see the byte code D compiled into human readable Java this is going to be the Java equivalent of your code we don't have time to go into the byte code today it's a great way to get a deeper understanding of how colum works before we create any more custom delegates it's important to know that there are a number of delegates that are included in the Kotlin standard library here is an example of Collins lazy delegate which provides lazy initialization the first time this property is accessed the land that will execute and its value will be computed and cached the next time we accesses property instead of recomputing it it'll return the cached value collins standard library is pretty extensive and has functions to do probably anything you can think of you know within reason so I'd like to just go over some some of the more useful ones here's just like a list of a bunch of them that I use pretty frequently you know pick some out you know go Google some afterwards but let me go over some of my favorites join to string will take a list of strings or a list of any object and it'll basically join it to a string and do the correct logic so that the comma doesn't end up at the end of the list which is something that I don't want to have to implement again and now I don't have to associate by will take a list and a lambda and it'll call that lambda on each item in your list and all the ones that have the same return value it'll group them up into a map so that you can basically turn a list into a map using a function to associate by and I've found that super useful get or put is a function on map that basically says hey get me this key and if it's not present use this lambda and put it in the map and then give it to me so basically you don't have to do this like a null check of like hey is this in the map but in the map how long standard library does that for you you should go check out the standard library for both of these and whatever your favorite data structure is I'm sure there's something that it will catch your eye there so something that I use a lot that is was very confusing to figure out why I would use it are the like standard library extension functions and the easiest way to talk about it is in this chart basically these are extension functions that are on arbitrary objects that you can use and they kind of behave in slightly different ways but all very similarly there's two axes on which these change and that has to do basically with what is beep how is the receiver being passed into the lambda whether it's scoped or a parameter or and how what's what's being returned is it the receiver that you called it on or is it the return value of your lambda and so just to go over it again the differences are whether or not the lambda is scoped to the receiver or it's the receiver is passing as a parameter and then the other vector is whether it's returning the receiver itself or if it's returning the last expression the land that you define and let's go over some examples this is something I do a lot this is the let extension function it's basically the same thing as map whatever you call it on in this case application it'll pass into your lambda as a parameter and in this case it and it'll return whatever you returned out of your expression so in this case I'm casting it as an application and it's going to return my application out and I can then call some dagger component on it because now it's been casted so it's basically a map function this is apply I use this for when I have an object and I want to do some mutations on it and then return that same object the receiver in this case is bundle and because this is apply it's going to return the same receiver so it's going to return this bundle no matter what I do in the lambda but inside the lambda I'm gonna call my put string foot boolean and so on and so forth and then it's going to return the bundle with the stuff in it also is very similar to apply in fact it's the same thing except it returned it passes the receiver in as a parameter so instead of the this keyword being the user the user is actually a parameter in the also block and the way I use this is I want to I want to have some class that I want to use and also I want to do something in this case I have this user that I want to apply that I want to assign but also I want to log it and like I don't want to do anything regarding like mutating it or anything like that effectively they're the same but the connotation different between apply and also run is one that I don't use very frequently but it's very useful for whether I want to do something if it's if something is null or and do something else if it's not no in this case the receiver is a nullable string and what I'm doing is I'm doing a safe call on this malleable string so this also extension will only be executed if the string is not null so I'm gonna be passed in a non null string and it'll it'll do something with it and if you'll notice that there's actually because it's a safe call if it is null then the whole left-hand side of this Elvis operator at the bottom will evaluate to null and the way the Elvis operator works is that it has a left and a right hand side if the left-hand side is null it'll do the right-hand side so basically if if the noble string is not known it'll execute the do not not null for string and if it is null the Elvis operator will handle that and execute the right-hand side in which case I will do something else so it's just a more Colony way of writing if null else this but it that's how I use the run extension so these are just a few tips to get you on your way to becoming a more effective Colin developer in summary you should leverage Collins built int you should leverage Collins type system the built-in types will get you pretty far but don't hesitate to create your own custom types you should learn to think functionally if not just to read other people's code its opens up a whole new world of designing your own API is and functionality and you know you'll get a lot of use use from it take control of your API extension functions delegated properties and operator overloading are all great sources of syntactic sugar you should leverage a standard library the best code is the code that you don't have to write and you don't have to write a whole bunch of code if you use the standard library that's what we had
Info
Channel: droidcon NYC
Views: 2,908
Rating: 4.8644066 out of 5
Keywords:
Id: lU8kafFjUJM
Channel Id: undefined
Length: 34min 48sec (2088 seconds)
Published: Wed Oct 17 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.