Kotlin Multiplatform for Android & iOS library developers
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Subtitle |
| |
Title of Series | ||
Number of Parts | 542 | |
Author | ||
Contributors | ||
License | CC Attribution 2.0 Belgium: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor. | |
Identifiers | 10.5446/61870 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
00:00
Software developerMobile WebComputing platformMultiplicationFormal languageDifferent (Kate Ryan album)Cross-platformAndroid (robot)Process (computing)outputMereologyLibrary (computing)Integrated development environmentMobile WebCodeSoftware developerLevel (video gaming)Single-precision floating-point formatComputer animation
01:55
CoroutineElectronic mailing listGame theoryOvalTime domainInternet service providerGamma functionMaizePhysical lawCoroutineDataflowoutputLibrary (computing)ResultantCodeProcess (computing)Type theoryAndroid (robot)Functional (mathematics)Different (Kate Ryan album)Social classComputing platformSoftware developerMultiplication signMereologyComplete metric spaceSystem callBlogElectronic mailing listLinear regressionInternet service providerCASE <Informatik>Object (grammar)Variable (mathematics)Operator (mathematics)SynchronizationMobile appExterior algebraInteractive televisionSoftwareProjective planeExtension (kinesiology)Default (computer science)Point (geometry)Formal languagePower (physics)Translation (relic)Generic programmingParameter (computer programming)Repository (publishing)Wrapper (data mining)Implementation
09:07
Exception handlingException handlingDifferent (Kate Ryan album)Mobile appComputer animation
09:28
Codierung <Programmierung>Error messageFunctional (mathematics)Exterior algebraException handling
10:11
Limit (category theory)Error messageFunctional (mathematics)Social classCompilerInheritance (object-oriented programming)Network topologyInterface (computing)Exception handlingMultiplication signDiagram
10:56
Finite element methodElectronic program guideCASE <Informatik>Inheritance (object-oriented programming)Computer animationDiagram
11:10
Context awarenessoutputContext awarenessDefault (computer science)Operating systemLibrary (computing)Social classCASE <Informatik>Mobile appTelephone number mappingCodePhysical systemWritingState of matter
Transcript: English(auto-generated)
00:12
So we are about to start.
00:21
So hi everyone, welcome back to the next talk. Today we have on stage Ana Labellarte and Paolo Rotolo from Nextom, talking about Kotlin Multiplatform for Android and iOS library developers, I guess it was. Now yeah, gonna talk about it. Awesome. Thank you.
00:42
Yes, I'm Paolo, and it's a very pleasure to be here with you. We come from Italy, we come from a small company in the southern Italy, and we decided to introduce Kotlin Multiplatform in our code about a year ago. We did this because for us it was easier to develop, and also we wanted to share
01:04
as much code as possible. We make libraries, so we didn't have the part of UI to transpose in Multiplatform. And this is our journey in the Multiplatform world. Now we've seen during this conference that with Kotlin Multiplatform Mobile we can develop
01:24
a library that targets both Android and iOS by just writing a single code base in Kotlin. Now if you look at what happens when we distribute the jar inside an Android library, we can say that the process is pretty straightforward. Now for an Android developer the language is the same, the IDE that uses is the same,
01:43
and most of the library that we can use are the same, so we can't tell the difference between a library that was made with Kotlin Multiplatform Mobile and one made with the native tooling. Now things are not quite the same when we talk about the iOS part, so if we distribute the framework, the process is not so straightforward.
02:04
And many of the problems arise because the code is converted first in Objective-C, and then most of the time we'll be using side projects that have Zwift as the main language. Now for a Zwift developer point of view, sometimes what can happen is that they can find the API that we expose is just strange, and this is just a base case scenario.
02:24
Other times the app made its crash, and this is due to some differences that there are between the two platforms that aren't automatically translated for us by the compiler. So we will see during this talk what we can do to, what happens when we distribute the framework, and what we can do to make libraries enjoyable also for the iOS part.
02:45
Now let's start with a simple example. So in this case we talk about coroutines. So inside our common code we can use coroutines. So we can have a function like this, which is a suspend function because it performs some operation with the network and some interaction with a persistent layer.
03:02
Now on Android we don't have many issues because if we have a coroutine scope we can launch the coroutine. But what happens on iOS when we don't have the coroutine library? Now this function gets converted by two alternatives. The first one which uses a completion handler, which is basically a callback that gets called
03:22
either when we have a result, and in this case the todo variable will be populated, or when we have an error, so in this case I will have the error variable populated. Now if we don't want to use the callback because it can become cumbersome when we have different functions, one inside the other, we can use the second alternative
03:42
which uses sync and await, but we have to target at least iOS 13. Now if we go back on Android, we launch the coroutine inside the scope, and this means that if I don't need the job anymore, what I can do is just cancel the scope, and then also the job gets cancelled.
04:00
By default I don't have this power on iOS. So what we can do is try to use a library which is called Coru. Yes, we fixed that problem with Coru, that is actually a library inspired by a blog post of TouchLab, so thanks TouchLab for this. How does it work? Basically you have to include of course in the common dependency of your code, and
04:24
using this library it basically introduces you to a new annotation that is to native class. So with that annotation you can specify a name for a class that will be generated just in the iOS implementation of your code. So if you have a look at the generated class, we can see that it is basically a wrapper
04:46
of our original repository. So you have two parameters that are passed, we have wrapped this original repository, and we have a scope provider, we'll see what a scope provider is later. And all the methods of that generated class are the same methods that you have in your
05:03
original repository, but the result type list of todo is wrapped in another object. So if we try to use this on iOS, the code that gets generated is something like that. We have two callbacks actually now, but I can see, for example, other two problems
05:26
with that code. First of all, we are exposing a coroutine scope for iOS developers, and maybe iOS developers are not familiar with that concept of scope like coroutine developers are.
05:40
And also, we have that object that now is an SRA, and it is not a list of todo anymore. That is because we are wrapped the list of todo in another object that accepts our generics. And the Objective-C translation of the Kotlin code cannot do that for now, so it gets
06:01
simplified to an SRA. To solve this, we go back in the Common Code, and we use another function of that library, that is launch on scope. So we can define a scope in Common Code and tell the library to run all our coroutines in that scope.
06:22
So the scope will not be provided by iOS developers anymore. Also, for the second problem, it's just a workaround. We can define a data class, we can define a type alias, or something to hide the fact that we are using a list of something. So if we try to generate a new code for this, this is the final code that is much
06:45
more readable and usable. And, of course, now we can dismiss the job if we are not interested in the result anymore. What about flows? We've talked about coroutines, what about flows?
07:01
This is an example of a simple flows that emits integers. Of course, again, on Android, it is simple. You have a scope, you can start collecting values. On Swift, the code that gets generated is something like that. We still have a collect function to collect the values of the flow, but we have to pass
07:23
this, this is a flow collector, so we have to implement the function emit. Do something with that value that gets emitted, and then when we are done, call the competition handler so we can receive the next value. Also, notice that we don't have the type that we are collecting from the flow, we just
07:43
have any. How we can improve this? First of all, we tried to make that collector generic, so we can use it in more parts of our code. We exposed another callback, and we actually casted the value as the one that we wanted.
08:10
But we found that this is not good enough, also because the iOS developer has to do this in his code, we are not doing it in common, so every time he has to use our
08:23
library, he has to define this. So we decided to fix it in common code, and again, we used this, that is a common flow. There is a class found actually in the Kotlin Conf app, and this class wraps app flow
08:42
and basically emits all the values of the flow and returns a crossable object so you can dismiss the flow when you don't want to listen to it anymore. So again, we return now a common flow using the extension function, and on iOS again, now
09:01
we have a much more readable code that we can also cancel if we want. Now, we mentioned before that sometimes the app may crash because of differences between the two platforms, and one great example of this is our exception handling are handled in two languages, because Kotlin only works with unchecked exception,
09:23
while Swift only works with the checked one. And now we will see what this means and what happens. So we are bringing back the function from the coroutine that we saw before. So in this case, because in Kotlin, I don't have to mark explicitly each throwing function, I can wrap it inside a try-catch, so if something happens, I will receive the error
09:43
inside the callback. Now, if we bring back also the first alternative that we saw before with coroutine, what I expect is to have the error in the function, but if I launch the application, it actually crashes. And this is because in Swift, I have to mark explicitly each throwing function.
10:01
So the fix is actually quite easy because there is an annotation that we can use, which is called throws exception. So by doing this just in the common code, and we don't have to make any changes inside the Swift implementation, so in this case, I will receive the error in the callback. And this works also with non-suspending functions.
10:22
So if I have this function and mark it throws and exception, once I compile the code, the generated function in Swift will be marked as throwing. So this time will be the compiler to force us to handle the exception. Now, another API that is not quite Swift-friendly is the one of sealed classes.
10:45
Now, on Kotlin, we can restrict the concept of inheritance by using sealed classes and sealed interfaces. So when we use them inside our Android code, we can just make something like this because, oops, okay, something like this, because we know for sure that those trees
11:05
are data, error, and loading in this case are the only cases that we have to handle. But on iOS, actually, it gets converted by just using the concept of inheritance. And so when I have to handle the, in this case, the status, I have to define also the case,
11:21
a default case, which I know for sure that will never be called. And on Swift, we actually have a concept which is similar to the concept of sealed classes, which is the concept of enum. So what we want is to map the sealed classes with enum. And to do so, we can use a library, which is called, it's quite dark, but Merkle Key Swift.
11:42
And in this case, using this library, it automatically detects any sealed classes and sealed interfaces, and generates, in this case, would be UI state KS. And it just takes the status as input, and it is actually an enum that I can use. So for a Swift developer, this is much easier to use
12:01
because I don't have to define a default case anymore. So if you're writing code that is platform-specific for Android, for example, you probably will need a context to access some system functionalities. What happens in the library ecosystem?
12:21
So you may expose an API like this that gets the context from the user of the library. But of course, you don't have to do this on iOS because you don't need the context on iOS. How you can improve that and how you can unify those APIs? We try to use Jetpack app startup for this because if you include the app startup
12:46
in your app, in your library, basically, you will be able to get a context that is injected by the operative system and maybe save it.