Detecting language using up to the first 30 seconds. Use `--language` to specify the language Detected language: English [00:00.000 --> 00:11.680] Hey, everyone. Thank you for joining me. I'm Solomon Briss and this is A Mirror Without [00:11.680 --> 00:17.640] Reflection for Cartoon Multi-Platform. So, without further ado, let's go into the subject [00:17.640 --> 00:25.520] and define what is reflection. Reflection is a feature that allows an executing Java [00:25.520 --> 00:30.680] program to examine or introspect upon itself and manipulate internal properties of the [00:30.680 --> 00:36.080] program. For example, it's possible for a Java class to obtain the name of all of its [00:36.080 --> 00:44.880] members. This definition is extracted from the Java documentation and it explains that [00:44.880 --> 00:54.080] reflection basically allows a program to introspect upon itself and look at its own method and [00:54.080 --> 01:05.240] properties. For example, in this code, I simply print every field and method that a class [01:05.240 --> 01:15.960] is declaring as accessible. And so, this is possible thanks to these class objects that [01:15.960 --> 01:28.600] the Java runtime gives me and it allows me to access all fields, methods, properties, [01:28.600 --> 01:36.400] and everything that defines this class. Now, that's not the only thing Java reflection [01:36.400 --> 01:44.520] can do. Java reflection can also provide proxies. So, for example, here, I create a simple [01:44.520 --> 01:55.920] printer and proxy. So, here, I create a proxy by saying, okay, here's a class. Here's its [01:55.920 --> 02:00.840] class loader. Here's a class. It's not really a class. It's an interface. So, here's an [02:00.840 --> 02:09.480] interface. And what I'm asking the runtime to do is to give me an object that implements [02:09.480 --> 02:17.520] this interface and delegates every call to this lambda. So, basically, I'm creating an [02:17.520 --> 02:25.400] implementation of an interface at runtime. And this is how you can use it. As you can [02:25.400 --> 02:33.600] see, it's pretty simple. All you have to do is call this create proxy method and then [02:33.600 --> 02:41.080] you'll have an interface, an implementation of the interface created at runtime. So, this [02:41.080 --> 02:49.400] talk is going to be about a lot of definitions because we are going to have multiple pieces [02:49.400 --> 02:55.920] of the puzzle. So, the first piece of the puzzle was, of course, reflection. Let's go [02:55.920 --> 03:02.240] into Kotlin multiplatform. Maybe let's not go into Kotlin multiplatform because you've [03:02.240 --> 03:07.960] just seen an entire presentation about what is Kotlin multiplatform and how it works. So, [03:07.960 --> 03:13.760] I'm not going to go into details about what is Kotlin multiplatform and how it works, but [03:13.760 --> 03:21.320] I'm simply going to say that Kotlin multiplatform is a way to compile Kotlin code for different [03:21.320 --> 03:29.720] targets, namely JVM and Android on one side, JavaScript on another side, and finally Kotlin [03:29.720 --> 03:40.520] native on the last side. Kotlin native encompasses iOS and also other less interesting targets. [03:40.520 --> 03:47.400] Let's face it, Kotlin native exists for the sole purpose of iOS. So, while Kotlin JVM [03:47.400 --> 03:52.400] supports reflection, Kotlin JS and Kotlin native do not. What is important to understand [03:52.400 --> 03:59.760] in this sentence is that reflection is not a feature of Kotlin JVM. It's a feature of [03:59.760 --> 04:08.680] the JVM that Kotlin uses and builds upon with its own reflection library, but basically [04:08.680 --> 04:14.720] it's a feature of the JVM. It's not a feature of the Kotlin language. As such, it is not [04:14.720 --> 04:22.840] provided in Kotlin JS and in Kotlin native. So, Kotlin multiplatform being the center [04:22.840 --> 04:33.840] of Kotlin JVM, Kotlin JS and Kotlin native, hence, do not support reflection. So, we need [04:33.840 --> 04:43.680] to get together and find another way of doing what we usually do with reflection. Maybe if [04:43.680 --> 04:55.360] we go back to the definition of what reflection is, we can single out this word. Reflection [04:55.360 --> 05:02.680] is a feature that allows an executing Java program. So, what this means is that reflection [05:02.680 --> 05:10.800] is a runtime feature. And we all know that what we cannot do at runtime, let's do at [05:10.800 --> 05:18.560] compile time. What can go wrong, right? So, to do that at compile time, what reflection [05:18.560 --> 05:27.680] does at runtime, we need to add several other pieces of our puzzle. Kotlin Poets is a Kotlin [05:27.680 --> 05:36.880] and Java API for generating Kotlin source files. So, what you could do is generate Kotlin [05:36.880 --> 05:47.520] source files by hand with templates and fill yourself like the vibe of the PHP 2000 error [05:47.520 --> 05:55.600] where everything was done with templating or you could use a type API that will build [05:55.600 --> 06:01.440] the Kotlin file for you. So, I strongly encourage you to not generate your Kotlin source files [06:01.440 --> 06:08.680] by hand and use an API such as Kotlin Poets. And here, for example, it's very simple. I [06:08.680 --> 06:15.640] create a new function called hello. I declare that it takes a name argument and I add the [06:15.640 --> 06:24.760] statement println hello name. So, it generates basically this function. Okay. So, that's [06:24.760 --> 06:35.840] a very important piece of our puzzle but that's by far not the most complicated one. So, the [06:35.840 --> 06:44.960] next piece of our puzzle is KSP. And KSP stands for Kerbal Space Program. It's a very good [06:44.960 --> 06:53.480] video game and the goal of this video game is to build a rocket and explore space. It's [06:53.480 --> 07:00.320] an exploration game. So, it's purposefully undocumented. So, there's no manual for discovery [07:00.320 --> 07:05.320] and that's the entire game. You need to build your rocket, send your Kerbal to space and [07:05.320 --> 07:12.560] see what happened. So, the game is heavily based on trial and error. Not all Kerbal will [07:12.560 --> 07:20.360] survive the journey. You will send them to space and not all Kerbal will come back. But [07:20.360 --> 07:27.960] when you do build a rocket and a space station in orbit, you feel a great sense of accomplishment. [07:27.960 --> 07:35.920] And as it happens, KSP also stands for Kotlin Symbol Processing API. The goal is to build [07:35.920 --> 07:45.920] a compiler processor, a compiler code processor and it is very, very lightly documented. Let's [07:45.920 --> 07:55.040] be honest, there is no manual for its discovery. You will use trial and error and you will [07:55.040 --> 08:03.320] scream at your screen, yelling at your frustration. Using KSP is a very good exercise in managing [08:03.320 --> 08:15.720] your frustration because of its light documentation. But when you finally achieve a functional [08:15.720 --> 08:22.960] Kotlin Symbol processor, you will, just like in the KSP video game, feel a great sense [08:22.960 --> 08:31.880] of accomplishment. So, let's see all our pieces of the puzzle. We use KSP to instrument [08:31.880 --> 08:40.000] codes at compile time. We use Kotlin Poets to generate codes at compile time. And we [08:40.000 --> 08:45.880] use Kotlin Multiplatform to compile everything for all targets that Kotlin Multiplatform [08:45.880 --> 08:55.360] supports. So, the idea here is not to allow a code to introspect upon itself at runtime, [08:55.360 --> 09:05.040] but to generate the information your code needs at compile time. It is a lot more optimized, [09:05.040 --> 09:09.600] of course, because you don't have to introspect all the code and all the information you need [09:09.600 --> 09:15.760] are generated for you at compile time, but it is, of course, a lot more complicated. [09:15.760 --> 09:27.920] So, how do you create a mirror generator? So, a mirror is a class that contains reflection [09:27.920 --> 09:33.600] information of another class. So, how would you create a mirror generator? Well, creating [09:33.600 --> 09:44.680] a symbol processor in KSP is not that complicated. What you need to do is create a symbol processor [09:44.680 --> 09:55.080] class that takes a code generator and a logger as a constructor input. And you will use those [09:55.080 --> 10:04.040] to, well, generate code and log when things go right or wrong. And then you can get, you [10:04.040 --> 10:13.400] can find all symbols that are annotated by a specific annotation and then simply see [10:13.400 --> 10:20.520] what type of symbol that is, and then you can continue to instrument the code starting [10:20.520 --> 10:28.440] with this. So, as you can see here, for example, look at if the symbol annotated is a property [10:28.440 --> 10:35.400] or maybe it's just a property setter because you can, in Kotlin, annotate, get and set properties [10:35.400 --> 10:45.320] methods or maybe it's a function declaration or maybe it's a class declaration and there [10:45.320 --> 10:56.240] are a lot of other things available. What's interesting in KSP and what I'm not showing [10:56.240 --> 11:09.920] here in code is that you could ask KSP to give you all symbols that are of a simple, [11:09.920 --> 11:15.520] of a declaring interface, for example, that are implementing an interface. You don't have, [11:15.520 --> 11:26.880] just like APT, you don't have to use annotations. Annotations are a very valid means of conveying [11:26.880 --> 11:33.400] the information that the code will be instrumented. But you could with KSP say, okay, give me [11:33.400 --> 11:40.960] all symbols, all classes that implement this interface, for example, or give me all codes [11:40.960 --> 11:49.680] to these methods or these kind of things. And then what you need to do after you have [11:49.680 --> 11:58.120] instrumented the code is to generate your file, the Kotlin source file that you will [11:58.120 --> 12:12.240] generate. And the good news is that Kotlin Poet does support KSP. So you don't have to [12:12.240 --> 12:21.960] write a facade between the KSP code generator and the KSP code generator. Kotlin Poet does [12:21.960 --> 12:30.120] support KSP. So it's, as you can see, pretty easy to write your very own code generator [12:30.120 --> 12:44.720] with KSP. And then what you need to do is to add your symbol processor to Kotlin, to [12:44.720 --> 12:51.720] your Kotlin compilation tool chain with Gradle. And as you can see, it's pretty simple, just [12:51.720 --> 13:03.640] apply the plugin. Now, the KSP plugins is versioned using its own version number and [13:03.640 --> 13:12.920] the Kotlin version number. For example, here, it's version 109 of the Kotlin symbol processor [13:12.920 --> 13:24.160] of KSP and it's version 1810 of the Kotlin language. And at the moment, because the Kotlin [13:24.160 --> 13:32.360] compiler plugin API keeps changing and is not stable and is not documented, KSP depends [13:32.360 --> 13:41.560] on a very specific version of the Kotlin language. So you need to upgrade KSP with the same Kotlin [13:41.560 --> 13:50.360] version that you need to upgrade Kotlin. And that's kind of a bummer because you need to [13:50.360 --> 13:59.240] wait when a new Kotlin language comes up, you need to wait for KSP to be compatible with [13:59.240 --> 14:05.440] this new version, even for minor version. If you use the wrong minor version, KSP will [14:05.440 --> 14:11.960] warn you that it is not compatible with this minor version. And once again, that's because [14:11.960 --> 14:19.320] the Kotlin compiler plugin API isn't stable and that KSP is using internal function and [14:19.320 --> 14:28.360] features of the Kotlin compiler plugin. Then, of course, you probably need to add your own [14:28.360 --> 14:35.640] runtime because when you generate code, you will probably need to provide with the generated [14:35.640 --> 14:44.000] code a runtime of your own. And then you need to declare that your KSP code processor will [14:44.000 --> 14:53.360] run on this code. Now, as you can see, it is declared differently than with regular Kotlin [14:53.360 --> 15:01.840] dependencies because at the moment, KSP doesn't interact with the Kotlin Gradle compiler with [15:01.840 --> 15:11.600] the Kotlin Gradle DSL. So you have to use this word, KSP common main metadata configuration [15:11.600 --> 15:22.000] in Gradle dependencies. So what can you do with this technology? Well, for the last two [15:22.000 --> 15:29.680] years, I have been developing an example because it was needed for the company I worked at [15:29.680 --> 15:39.920] and that was mocking. So what we have here is a class that works with Kotlin multi-platform [15:39.920 --> 15:49.080] test and that works with all targets of Kotlin and that generates mocks at compile time because [15:49.080 --> 16:01.320] mocking in, for example, mock K or with mockito, mocking uses the proxy reflection feature of [16:01.320 --> 16:10.720] the JVM which does not exist in Kotlin multi-platform. So, for example, here we say we want a view [16:10.720 --> 16:19.640] that will be mocked. So view is an interface and it will be generated by the mock AMP compiler [16:19.640 --> 16:25.640] plugin. We want a fake and a fake is a data class and we want a data class that filled [16:25.640 --> 16:32.560] with fake values, empty string, zeroed integers and all those kind of stuff. We want a controller [16:32.560 --> 16:45.440] that uses both a fake and a mock. We want to define the behavior of our mock. For example, [16:45.440 --> 16:54.080] here I say that in the interface view in my mock, in my view mock, if I call view.render [16:54.080 --> 17:03.000] with any argument it will return true and I want to be able to verify that a mocked [17:03.000 --> 17:12.600] has been called with a specific data in this instance model. So all that and all that DSL [17:12.600 --> 17:20.400] is possible thanks to KSP and Kotlin Poets and the ability to generate code at compile [17:20.400 --> 17:27.480] time. So what was previously unavailable to Kotlin multi-platform because reflection wasn't [17:27.480 --> 17:36.480] available is now available thanks to code generation. And by the way, if you're interested [17:36.480 --> 17:45.240] in this in mocking for Kotlin multi-platform you can use mock AMP which is a library that [17:45.240 --> 17:53.160] we built with Deezer and this library, this testing library is used in production meaning [17:53.160 --> 18:02.960] in test production at Deezer, almost all the multi-platform tests at Deezer uses this mock [18:02.960 --> 18:13.200] AMP library that we developed together. So there's a problem with KSP. If we go back [18:13.200 --> 18:23.600] to the example I just gave you, this method uses this inject mocks function, this class [18:23.600 --> 18:31.000] uses inject mocks. And the fact is that inject mocks is precisely the function that is generated [18:31.000 --> 18:37.840] for this class. Because this class, we can see here, because this class has atmock annotated [18:37.840 --> 18:45.160] properties and at fake annotated properties, then an inject mocks function will be generated [18:45.160 --> 18:55.800] by the mock AMP compiler plug-in slash symbol processor. And when you load the project, [18:55.800 --> 19:02.520] the Deezer project or any project that uses this system, well, inject mock is an error [19:02.520 --> 19:07.900] because it hasn't been generated yet. So idea will show you an error saying, okay, this [19:07.900 --> 19:13.720] function just doesn't exist. I don't know what you are talking about. So you need to [19:13.720 --> 19:23.480] either build the project or you need to say to, you need to say to Gradle to generate and [19:23.480 --> 19:31.080] run KSP. And at the moment, there is no way around that. And that's because KSP has a very [19:31.080 --> 19:37.800] important limitation. It treats the source code it is instrumenting as read only. There [19:37.800 --> 19:46.000] is no way with KSP to add properties or to modify a symbol that you are instrumenting. [19:46.000 --> 19:54.920] So this my test class, there's no way with KSP that I can add a property or I can add [19:54.920 --> 20:01.680] an annotation and all that. And since there is no reflection in Kotlin multiplatform, there [20:01.680 --> 20:11.760] is no way to find a class that exists, but there's no class dot with name. So that means [20:11.760 --> 20:19.680] that you need in your code to use the code that is generated and that code doesn't exist [20:19.680 --> 20:29.840] unless you generate it. And that's a small price to pay to use KSP. So why would you [20:29.840 --> 20:37.680] use KSP as opposed to writing a full-fledged Kotlin compiler plugin? First and foremost [20:37.680 --> 20:52.400] because KSP provides a kind of stable API. The API changes, but it follows a depreciation [20:52.400 --> 21:00.800] cycle. And the API of KSP is supposed to be public, so they treat it with respect of a [21:00.800 --> 21:07.600] public API. And also because when you use KSP, you don't have to write a compiler plugin. [21:07.600 --> 21:16.320] Writing a compiler plugin with Kotlin just not only means that you will have to understand [21:16.320 --> 21:23.240] the inner components of the Kotlin compiler, which are absolutely not documented. KSP is [21:23.240 --> 21:28.280] a little bit documented. The Kotlin compiler internals are just not documented, but it [21:28.280 --> 21:33.160] also means that you will have to handle compiler integration and gradle integration. So you [21:33.160 --> 21:37.880] will have to add your own gradle plugin, you will have to add your own compiler plugin, [21:37.880 --> 21:45.680] and it becomes a very complicated endeavor. And finally, because for code-generating [21:45.680 --> 21:55.540] use cases, KSP remains a lot simpler than writing a compiler plugin, which once again [21:55.540 --> 22:00.600] is done completely in the dark. You won't have any support if you try to write your [22:00.600 --> 22:10.720] own compiler plugin. So using KSP, KSP is still a very, very important tool in the grand open [22:10.720 --> 22:18.640] source library of Kotlin multiplatform project. A lot of Kotlin multiplatform libraries use [22:18.640 --> 22:30.520] KSP now, and I encourage you to contribute to that grand library. And that's it for me. [22:30.520 --> 22:37.320] Just want to say that I represent here coding coders. We are certified for our Kotlin training, [22:37.320 --> 22:44.960] so if you want Kotlin multiplatform training, be sure to contact us. We have lots of libraries [22:44.960 --> 22:51.880] that are open source. Romain with the next talk is going to present you another one of [22:51.880 --> 22:58.760] them, and we like to do our open source work with Kotlin multiplatform for every target. [22:58.760 --> 23:07.480] It can compile too. So whether you want to contribute to Kotlin multiplatform libraries [23:07.480 --> 23:19.320] or learn how to use Kotlin multiplatform, be sure to contact us. Thank you very much. [23:19.320 --> 23:27.680] Thank you again. We have time for one question. If someone has a question, raise your hand. [23:27.680 --> 23:34.680] Shout it, and you have to repeat the question. Yes, so you've decided to write your own library. [23:34.680 --> 23:42.960] There was any way of making Mokey to work with yours, but if you were, is that possible? [23:42.960 --> 23:50.280] So the question is, rather than using, rather than creating a whole new library for marking [23:50.280 --> 23:56.280] a Kotlin multiplatform, is there a way to put Mokey to Kotlin multiplatform? And the [23:56.280 --> 24:03.360] answer is definitely no. Mokey to uses a lot of reflection, just not just for proxy, but [24:03.360 --> 24:12.840] for object generation and for verification, and it instruments the runtime heavily. And [24:12.840 --> 24:17.960] since there is no runtime, there is no JVM runtime in Kotlin multiplatform, there is no [24:17.960 --> 24:24.800] way to port Mokey to Kotlin multiplatform. Now, what we've tried to do with Mokey and [24:24.800 --> 24:33.600] P is to emulate the same API that Mokey provides so that when you use Mokey and P, you're at [24:33.600 --> 24:41.400] home, you're using an API that is really close, but there's no way to port Mokey to itself. [24:41.400 --> 24:55.680] Thank you very much, and have a nice first time.