Migrating from Autovalue to Kotlin Data Classes
Lage, Ricardo
Languages Talk, Intermediate AutoValue is a powerful tool provided by Google to generate Java immutable value classes. If you are migrating your project to Kotlin, data classes are the most direct way to replace it while keeping the same features. There are however trade-offs to take into account. In this talk we will introduce how we migrated our project from AutoValue to Kotlin data classes and how we dealt with the issues we encountered along the way. We will also discuss the advantages and disadvantages of each approach in terms of build time, apk size, method count and serialization time. After this introduction, we will deep dive into two topics: First, we will look into different serialization strategies available and how they perform. We will look into Gson, Moshi and Kotlinx.serialization libraries and explore their trade-offs when choosing one. Second, we will explain how we built an IntelliJ/Android Studio plugin to help us automate the conversion from AutoValue to Kotlin Data Classes. We will explain how plugins work and how you can also develop one to help automate repetitive IDE-related tasks.
 I'm an Android engineer at Blablacar. And today, I'm going to present to you our experience migrating one of our apps from AutoValue to Kotlin data classes. Before we get into the migration process itself, let's just take a quick look into what is AutoValue and why we decided to use it to begin with. So here is a simple harmless user class. And we can typically call it a value or content class because we're interested in its contents. And if that is the case, there's a number of things we can do with this class to make sure we use it properly in Java. For example, we can make sure that the classes annotated the final and its fields are final as well, so we ensure that the contents are immutable. If we do that, we need to make sure we have a constructor to construct our class. OK, this is just for the time. So we need to have a constructor to ensure that our class can be accessed properly. We might also be interested in annotating fields that are nullable with a nullable annotation. Since we're interested in the contents, we may as well, we should also add override equals and hash code. If you're using Android Studio or IntelliG or most modern IDs, the IDs can generate this code for us. But we still need to manually go into the function that generates this code and add it to our user class. Again, if you're using Parcelable on Android and we want to have our user class implemented, we need to add the implementation for Parcelable. We need to expose the right getters for the content that we want to access. If we want to change some of the contents, we probably want to add some kind of function like this to copy the contents to a new instance of our user class, and so on and so forth. And in the end, what we get is this huge class with a lot of boilerplate code just to make sure that the contents of our class is properly handled. And that's not all. So there's a lot of things that I didn't add to that code. So if we want, we could also annotate the non-null fields. We didn't add the two string implementation to that class. If we remove or if we add a field, we need to ensure the consistency of the implementations of hash code, of equals, and so on. If we are doing serialization with this class, we need either to write our own custom adapter or rely on reflection and repeat this for every new class that we want to behave the same way. What is the solution? So one of them is AutoValue. And AutoValue is a library by Google which basically addresses all of these issues. And it automatically generates these boilerplate code for us. So it can generate equals, hash code, two string, and so on. And it has a powerful plugin system that we can add other dependencies to make sure these other kinds of boilerplate code that we need on Android is also generated for us. Why use it? So this is from why this? OK. No, it's good. So why use AutoValue? This is taken from their GitHub page. So these are the reasons that they explain. If you follow that link later, they have also a presentation where they explain more details about the advantages of AutoValue over different solutions. Basically, there are no runtime dependencies. Every time is generated at compile time. The API is invisible. The class that is going to be generated functions like any other in your project. There is very little cost to performance since everything is done at compile time. Your class is mostly like any other. So there's very few things you cannot do with it. There's then very little magic that is happening behind the scenes. So how does it work? Well, basically, you annotate your class with AutoValue. You don't know why this is. So what you have to do is have an abstract class that you annotate with AutoValue. And the fields now become abstract methods. And so our previous class that we had fields for the values that we interested in, now the abstract fields. And the ones that are nullable, we annotate as such. And behind the scenes, what AutoValue is doing is generating this code for us. So this is not us writing this anymore. So all the note checks, all the hash code, all the equal implementation, and so on, is generated by AutoValue. So it takes that burden from us. And it's done automatically through an annotation processor. But now, since this is an abstract class, we need to somehow expose a way to get an instance of this class. And we do this with a static method. Typically, you can also use a builder pattern if you want. So AutoValue provides that for you as well. Here, to simplify, we have a simple create static method that then behind the scenes, we are returning an instance of this AutoValue underscore user that AutoValue created for us. In addition, there are these plugins that I mentioned before. So AutoValue has this powerful plugin system that you can basically add more things to generate code for you for your class. In our project, we use these three. So with AutoValue, with AutoValue G-SUN for serialization, and AutoValue Parcel for the ParcelBow implementation. And with these plug-ins in place, all you have to do is add the header of the things you're interested in. So for ParcelBow, you just have to say that your class now implements ParcelBow. For the transformations that you want to change, some fields, you can annotate them with this with method. And for serialization, you have to add this static type adapter function that returns the type adapter that was generated for you by the plugin. What AutoValue is doing now is creating these four different classes. So these are all extending from one another. So if you look behind the scenes, what AutoValue is doing, so there is one first class with the $3 that extends from our original user abstract class. And this is the original AutoValue generated code. Then you have another one that extends from the previous that has the G-SUN type adapter inside that was also generated by the plugin. A third one, which is for the width transformation that we have. And finally, the last one, which handles the ParcelBow implementation on Android. So this is all now generated for us. And we don't need to bother with creating this code anymore. So we just have this abstract class with the content that we're interested in. There's very few extra annotations that we need to add to our class. And we get all these code generated for us for free. And if we have that, why migrate to data classes then? In principle, since we have this class now in Java, and if we are migrating our project to Kotlin, we could just have AutoValue in Kotlin. The reason we migrate to data classes is because, well, they are a native answer to value classes. So data classes already out of the box give us pretty much everything that AutoValue does as an external dependency. So if you're using Kotlin, data classes generate equals hash code and tree string for you at compile time. So you're not doing an annotation processing step anymore. This is done during compilation of your Kotlin class. And you get extra stuff. For example, copy replaces our width function that we had. And it's more powerful because it's much more flexible in what kinds of contents you can modify. And you also get destructuring declarations, which is this way of assign your object into different variables straight away, which is quite useful, for example, if you're changing our X operators or in lambda declarations in general. So what we get is comparing the two is that with data classes, we have less external dependencies. The code is much simpler, much smaller. It's just one line of code instead of all these fields and this abstract class that we have. We don't need an abstract class anymore, so we get our final class. We don't have an annotation processing step happening because this is done by Kotlin at compile time. And we have a few smaller plugin issues as well that we don't get anymore with Kotlin data classes. So with the plugin just briefly, well, there are yet other dependencies in our code. So they are not provided by Google. They're not part of AutoValue itself. So the other libraries by other developers, that if we're using them, there are other dependencies that we need to make sure we keep up to date. The width, for every little configuration that we have for the data we want to change in our classes, you need to provide a new method for it. That's how the plugin works. So if you have many transformations, that can become quite an extensive list of width functions in our class. And in case of Gsun, in our project, we use Gsun. You cannot use the field name policy. You need to annotate all your chemo case fields with this naked case version if that's what your API is returning. So in other words, what we are doing is moving from this, which is the AutoValue class in Kotlin, so to this, which is a much smaller, concise version and simple that gives us pretty much everything that AutoValue gave in Java. This is not to say that AutoValue is not good. By all means, it's very good if you're using Java, just that if you're moving to Kotlin, data classes offer a much better way to achieve the same result. Two things to say here. First, we are using a parcelized plugin, which is Kotlin's answer to parsable implementation. What is different here in this plugin is that this is a compiler plugin. So again, there is no notation processing happening here. And you can see this on Android Studio. We can go to Tools, Option, and show the Kotlin bytecode. And if you press the Compile button, you get this, which is our data class, the compiled into Java. And you can see everything that we had in our AutoValue-generated class, so all the null checks, all the equals hash code, and so on, and the parcelable implementation. And this was done at compile time by the parcelized plugin. Another thing to say here is that we are using JSON. And JSON doesn't have Kotlin support. And so if we just have this class as it is shown here, and we're trying to parse the content from a JSON response, what happens, for example, if login is not present? JSON doesn't know about non-optional and optional fields in Kotlin. So what it's going to do is assign null to this login field anyway, even though in Kotlin, this is not an expected behavior. So what happens is that the program is going to fail later. It's still going to parse. It's still going to work as a parsing result. But once you need to use that field later in your code, and you don't know exactly where, this will fail in some unexpected ways that you're only going to notice later, or you might not even notice independently on how your code works or which kinds of fields are having no values assigned to them. Which leads us to our next step, which is, so how do we handle serialization? Well, we're using JSON before, which is a classic. And it worked well for us until now. There is Moschi as an alternative, which is faster than JSON. Doesn't have many of its quirks. It's a smaller library as well. So in general, it has advantages over JSON. And more recently, I don't know how many of you attended the talk before lunch where this was shown as well, that more recently, the new version of Moschi also has a code generation for creating the adapter at annotation processing time. So you don't need to rely on Kotlin reflection, which is quite a big library. And so Moschi comes with Kotlin support out of the box. Well, for us, we had JSON all over the place in our code. There was an alternative. So there is an extension from this author, which is a library called JSONValue that works on top of JSON and generates the adapters for us. Moschi has a similar solution if you're looking at an alternative from what Moschi provides, which is called Kotchi. And they both work similarly. So JSONValue, all you have to do is annotate your class with the JSON constructor. And if you have any snake case to camel case conversion to do, you can do so with serialized name. Kotchi works similarly. So you annotate it with JSON serializable. And the camel case or the custom names that you want to change from the response, you also can set up this way. However, there is a third option that we looked into, which is newer and is also provided by Kotlin, which is Kotlin x serialization. Or we're going to refer to it as k serializer here. And k serializer, in principle, works similarly as the other two solutions. So you basically annotate it with serializable on the constructor and custom fields with serial name. But two things make Kotlin x serializer more interesting. First, this is a compiler plugin. It's not an annotation. There is no annotation processing step here. So this is our compiled user class. And you can see that the serializer annotation added this serializer field to our class at compile time. And the second thing that is interesting is that this is multi-platform. So if we look quickly at the source code of the k serializer, we have this at the common. We have this interface at the common level, which is independent of platform, which is this interface expecting a serializer in our class. And note the expect keyword here by Kotlin, which is a way to tell the compiler that we need to have a platform-specific implementation. And if you look at the GVM part of the source code, so we have the actual implementation of this function that returns the serializer that was compiled in our object for us. And you can see an example of this in this app by Jake Wharton. That is an example of Kotlin multi-platform that does search on the Android SDK, both on Android and as a Chrome extension, I think, I believe. And it uses Kotlin serialization as well as coroutines and Kotlin multi-platform. So it's an interesting example to look at. All right. So we looked at these three. There are others, but these two were the more likely candidates to transition from G-SUN in our case. They all had issues with the field name, so we can't have a general field name policy assigned to our classes. So we need to use this serialized name, this custom transformation for fields that use camel case in our project. And ultimately, K Serializer looks very appealing. It's multi-platform. It doesn't rely on reflection. It's a compiler plugin, has more flexible adapters. And when we first looked at it, was it faster? We wondered. And when we first benchmarked this in February, turns out it was not by a lot. We build on top of this work by Zach Sewers. Sorry if I'm pronouncing his name wrong. Where he benchmarked other solutions, but not the case serializer. So we added to it. And it turns out at the time, it was three times as slow in some cases. So our solution at the time was to use reflection plus G-SUN value where it made sense to use it. Since we control our API, we knew where we could rely on reflection from G-SUN. Today, however, looking at this again, so we ran the same benchmarks again. And we can see that the folks at Kotlin did a lot of work to improve the performance. And they're basically just as fast now. So there's basically no much of a difference between them. And if you look at auto-value, just out of curiosity, you can see that auto-value adds a little bit to the performance. But it's not by much. So if you're looking at auto-value for performance, that's not necessarily, you're not going to have much of a gain there. All right. So now we have all the information that we need to actually do the migration. So we know that there are advantages to using data class. And we know it can help simplify our project. However, migration at first didn't look as trivial. Because if you just use the conversion tool from Android Studio, we get an auto-value class converted to Kotlin. And then there's quite a tedious process of renaming the fields and the methods manually to data class. Which when we first tried this turned out to be quite annoying, especially because the Java classes that we had that used those model classes, we needed to rename those manually to prefix them with a get. Because from Java, when you access a data class field, we need to call getUserName, getAge, and so on. So what did we do instead? Well, we wrote an IntelliJ plugin to do this factor for us. And before we go into the code for that, let's see if we can get a quick demo. How do I get there? This is maybe a bit bigger than I expected. But so this is a very simplified version of our abstract class. We're not looking at annotation here just to simplify this. So here we have just an abstract class, but it looks basically like an auto-value class. And here we have a simple usage in Kotlin. Just combines the name with the age and returns a string. And here we have the same usage in Java. Well, it's cutting, but it does the same thing. So it formats and returns username, then comma age. So if you go back to the user class and then we have the plugin action example convert to data class, hopefully, yes. We get now our data class, our abstract class converted to data class. And in Kotlin, they are now a field. They're not any more abstract method being accessed. And in Java, we have the user get name and user get age, which developing this turnout to be simpler than expected. And so the results were quite good. So we were very happy that this worked out and simplified the migration process for us. So how does this plugin work? The entry point for a plugin, in this case, is an action. So the user is tapping on the menu option to make the conversion. So when that happens, we get this call on the action performed event in our plugin. And this is a simple boilerplate code that we can add to start the refactoring. So this is a simple example. So we look into a simple example here, where we first get the current file that is open. And that returns a PSI file. A PSI file is basically an interface that IntelliJ provides for us to navigate through our code in that file. And once we have access to the file, we can navigate the source code via a tree visitor, which then has different methods we can override to visit different elements in our code. So here, for example, every time the visitor encounters a class, it's going to call back on this visit class function. And then what we can do with it is have a write command action where we can write some code or make some replacements or some refactoring or so on in that class. So in our case, we want to convert from abstract class to a data class. What we do here then is check if it's not a data class. And if it's not, again, we're simplifying things here. We're not doing all the checks we should if we're publishing this plugin on IntelliJ. And if it's not, we can add the modifier data keyword to make it a data class. And if it's abstract, we remove the abstract keyword. Next, we have the visit name function callback, where we can also run commands to then change the names of our methods. So here, we want to change the users, like the colors, the name of the colors of our methods. Since we are changing, we have an abstract call method, for example, named age. And we want to make sure that the Java colors are now changed to get age instead of just age. So here's what's happening here. We use this reference search, dot search, to find the usages of this function. And then for each user of this function, we call this handle element rename to get age instead of age. This works well for Java, but for Kotlin, what we want to do is change the type of call. So we are not any more calling a method, we are calling a field. So for Java, for Kotlin, what we need to do is check the language of the element. So if it's Java, we can do the handle element rename that we're doing. If it's Kotlin, however, we can take advantage that this is an abstract syntax tree, basically, of our code. So we can get the parent of the method name that has been called, because the parent represents the name of the method plus its parameters, and replace it with the field name that we are creating in our Kotlin data class. And that's what we are doing here. So we are getting the parent, and we're replacing it with this new expression that we're creating based on the name of the parameter we are adding to our data class. And that's basically the gist of it. If you're interested, the source code for this example, the full one that works like I showed before in the demo, is available at this address. If you're more interested in IntelliJ plugins, here are some more links. I will publish these slides later so you can access them. One last thing that we did with our data class in our project, because we wanted to keep compatibility with Java. We still had Java classes. We're not migrating everything to Kotlin straight away. So we kept the with methods that we had, because in Java, you cannot use copy out of the box for one single transformation. In Java, you have to transform all the methods, if you want, to use copy. So it was easier to just keep the with that we had already for Java classes and wrap copy inside with the information that we wanted to change. And similarly for the create, we wanted to keep the create for the Java users, for the Java colors. So we just annotated them with GVM static so that we could access them from Java. Finally, just a short notice on tests. We had a lot of tests already for parsing our classes. And it was very helpful to ensure that everything was working as intended once we finished the transformation. So it helped us ensure the correct serialization and the serialization of our classes. It helped ensure that the conversion of the methods were correct. And in some cases, it was good to have Moquito now supporting more final classes, because in Kotlin everything is final by default. And in a few cases, we needed that, and it was helpful to have Kotlin supported. So in the end, we had these 46 auto-value classes in our project that we converted to Kotlin. These auto-value classes, they were generating these extra classes and these 500 plus methods. We got rid of four dependencies. We added one, the G-SUN value. Because annotation processing was happening less now, we had less plugins. We were not using auto-value anymore. Our build time improved a lot. The clean build was more than a minute faster. And the incremental builds when changing one of these classes improved a bit, not as much, but still was a significant change. The APK size remained the same, because the auto-value is done at compile time. So we didn't change much on the APK, other than the additional classes that we didn't have anymore. And because of the mixed serialization approach, the startup time increased a bit, but we were OK with it. We are hoping to replace G-SUN with Kotlin X serialization and then measure this again to see how it helps. And with that, I thank you. I think I was a bit early. Sorry, I was a bit fast, maybe. But any questions? No worries. First question? Yeah, so you show how to do, how to migrate from auto-value to data classes. Yeah. I'm working for a library, and I'm used to provide some builders to allow my clients to use the builder in a sort of create the classes in a elegant way. For the data classes in Kotlin, what I have seen is you have to create like a class inside and do everything by your hand. Do you know there is a better way to do it? That's the only way that this person can do it. Other than having a builder for your data class? Yeah, so the way that Kotlin provides, you create a class inside your class. Example, this is how you have the company object to me. Yeah, other than the company in origin, you need to create a class inside your data class. No, you need to create a class called builder. OK. In this class, you're putting all the values to make a builder, and you are making the builder by your hand. But I don't know if I get why you need to do that, because with data classes, you can just call the constructor directly, right? And if you need default values, you can just set default in the data class field. It's because I also have Java code. It's not migrating only. Yeah, but then you need to do something like this, right? You need to have some kind of create function that creates with the defaults, or like some kind of, yeah, you need to do a builder like you do in Java. There is no way. I'm not aware of a way to get out of that. OK, thank you. Thanks. Hi. Thanks for the talk. Just one question. When you use Moji to pass the JSON data, you can use all the features Kotlin provides, like construct the default parameters and stuff. Do you also have this with Kotlin X serialization? Or is it possible to set it up somehow? I believe so. I didn't test Kotlin X for that specifically. I tested this with JSON. And with JSON, it doesn't work. Because actually, JSON is using a trick with the unsafe package of Java. So if you're using the default methods, it's going to fail. Unless you annotate all your fields with default, then it works because it becomes a constructor. Or you can do an extra constructor in your data class to provide it with the default values. Then it works with JSON. But with Kotlin X, I'm not sure. I would say yes, but I didn't test. OK, thanks. Yes. Hi. Thank you for the talk. Just one question. Why haven't you choose Moji instead of JSON? Why haven't you choose JSON? Because we had JSON in the project already with custom adapters in different places. So it would be a bigger change. And we are hoping to migrate to Kotlin X serialization once it becomes more stable. And then we can. So I mean, this is a bigger talk. We are hoping to have a common Kotlin module that we can share between Android and iOS. And we are hoping the case serializer can be part of the solution. And so we didn't want to do a two-step migration. And JSON was already working for us. So we didn't feel the need to migrate to Moji to then migrate second time to case serializer. Yeah, yeah. I got what you mean about this case serializer. Isn't it in beta still? It's in what? Sorry? In beta? Yes. So it's marked as experimental. But I mean, it's already working. And it works already for different use cases. In January, in February, when we finished this migration, we didn't start adopting it because of the performance. So we had concerns with that. But they were quite fast and responsive to do the work to actually improve it and to make it reach the levels of JSON and Moji. And so now we are looking at it again, but probably in another talk. OK, mate, thank you.
 I'm an Android engineer at Blablacar.  And today, I'm going to present to you our experience  migrating one of our apps from AutoValue to Kotlin data  classes.  Before we get into the migration process itself,  let's just take a quick look into what is AutoValue  and why we decided to use it to begin with.  So here is a simple harmless user class.  And we can typically call it a value or content class  because we're interested in its contents.  And if that is the case, there's a number of things  we can do with this class to make sure we use it properly  in Java.  For example, we can make sure that the classes annotated  the final and its fields are final as well,  so we ensure that the contents are immutable.  If we do that, we need to make sure  we have a constructor to construct our class.  OK, this is just for the time.  So we need to have a constructor to ensure that our class can  be accessed properly.  We might also be interested in annotating fields that  are nullable with a nullable annotation.  Since we're interested in the contents, we may as well,  we should also add override equals and hash code.  If you're using Android Studio or IntelliG or most modern IDs,  the IDs can generate this code for us.  But we still need to manually go into the function  that generates this code and add it to our user class.  Again, if you're using Parcelable on Android  and we want to have our user class implemented,  we need to add the implementation for Parcelable.  We need to expose the right getters for the content  that we want to access.  If we want to change some of the contents,  we probably want to add some kind of function like this  to copy the contents to a new instance of our user class,  and so on and so forth.  And in the end, what we get is this huge class  with a lot of boilerplate code just  to make sure that the contents of our class  is properly handled.  And that's not all.  So there's a lot of things that I didn't add to that code.  So if we want, we could also annotate the non-null fields.  We didn't add the two string implementation to that class.  If we remove or if we add a field,  we need to ensure the consistency of the implementations  of hash code, of equals, and so on.  If we are doing serialization with this class,  we need either to write our own custom adapter  or rely on reflection and repeat this for every new class  that we want to behave the same way.  What is the solution?  So one of them is AutoValue.  And AutoValue is a library by Google  which basically addresses all of these issues.  And it automatically generates these boilerplate code for us.  So it can generate equals, hash code, two string, and so on.  And it has a powerful plugin system  that we can add other dependencies  to make sure these other kinds of boilerplate code  that we need on Android is also generated for us.  Why use it?  So this is from why this?  OK.  No, it's good.  So why use AutoValue?  This is taken from their GitHub page.  So these are the reasons that they explain.  If you follow that link later, they  have also a presentation where they explain more details  about the advantages of AutoValue over different solutions.  Basically, there are no runtime dependencies.  Every time is generated at compile time.  The API is invisible.  The class that is going to be generated functions  like any other in your project.  There is very little cost to performance  since everything is done at compile time.  Your class is mostly like any other.  So there's very few things you cannot do with it.  There's then very little magic that is happening behind the scenes.  So how does it work?  Well, basically, you annotate your class with AutoValue.  You don't know why this is.  So what you have to do is have an abstract class  that you annotate with AutoValue.  And the fields now become abstract methods.  And so our previous class that we had  fields for the values that we interested in,  now the abstract fields.  And the ones that are nullable, we annotate as such.  And behind the scenes, what AutoValue is doing  is generating this code for us.  So this is not us writing this anymore.  So all the note checks, all the hash code,  all the equal implementation, and so on,  is generated by AutoValue.  So it takes that burden from us.  And it's done automatically through an annotation processor.  But now, since this is an abstract class,  we need to somehow expose a way to get  an instance of this class.  And we do this with a static method.  Typically, you can also use a builder pattern if you want.  So AutoValue provides that for you as well.  Here, to simplify, we have a simple create static method  that then behind the scenes, we are  returning an instance of this AutoValue underscore  user that AutoValue created for us.  In addition, there are these plugins that I mentioned before.  So AutoValue has this powerful plugin system  that you can basically add more things to generate code for you  for your class.  In our project, we use these three.  So with AutoValue, with AutoValue G-SUN for serialization,  and AutoValue Parcel for the ParcelBow implementation.  And with these plug-ins in place,  all you have to do is add the header of the things  you're interested in.  So for ParcelBow, you just have to say that your class now  implements ParcelBow.  For the transformations that you want to change,  some fields, you can annotate them with this with method.  And for serialization, you have to add  this static type adapter function that  returns the type adapter that was generated for you  by the plugin.  What AutoValue is doing now is creating  these four different classes.  So these are all extending from one another.  So if you look behind the scenes, what AutoValue is doing,  so there is one first class with the $3  that extends from our original user abstract class.  And this is the original AutoValue generated code.  Then you have another one that extends from the previous  that has the G-SUN type adapter inside that was also  generated by the plugin.  A third one, which is for the width transformation  that we have.  And finally, the last one, which handles the ParcelBow  implementation on Android.  So this is all now generated for us.  And we don't need to bother with creating this code anymore.  So we just have this abstract class with the content  that we're interested in.  There's very few extra annotations  that we need to add to our class.  And we get all these code generated for us for free.  And if we have that, why migrate to data classes then?  In principle, since we have this class now in Java,  and if we are migrating our project to Kotlin,  we could just have AutoValue in Kotlin.  The reason we migrate to data classes  is because, well, they are a native answer to value classes.  So data classes already out of the box  give us pretty much everything that AutoValue does  as an external dependency.  So if you're using Kotlin, data classes generate equals hash  code and tree string for you at compile time.  So you're not doing an annotation processing step anymore.  This is done during compilation of your Kotlin class.  And you get extra stuff.  For example, copy replaces our width function that we had.  And it's more powerful because it's much more flexible  in what kinds of contents you can modify.  And you also get destructuring declarations,  which is this way of assign your object  into different variables straight away, which is quite  useful, for example, if you're changing our X operators  or in lambda declarations in general.  So what we get is comparing the two  is that with data classes, we have less external dependencies.  The code is much simpler, much smaller.  It's just one line of code instead of all these fields  and this abstract class that we have.  We don't need an abstract class anymore,  so we get our final class.  We don't have an annotation processing step happening  because this is done by Kotlin at compile time.  And we have a few smaller plugin issues as well  that we don't get anymore with Kotlin data classes.  So with the plugin just briefly, well,  there are yet other dependencies in our code.  So they are not provided by Google.  They're not part of AutoValue itself.  So the other libraries by other developers,  that if we're using them, there are other dependencies  that we need to make sure we keep up to date.  The width, for every little configuration  that we have for the data we want to change in our classes,  you need to provide a new method for it.  That's how the plugin works.  So if you have many transformations,  that can become quite an extensive list of width  functions in our class.  And in case of Gsun, in our project, we use Gsun.  You cannot use the field name policy.  You need to annotate all your chemo case fields  with this naked case version if that's  what your API is returning.  So in other words, what we are doing  is moving from this, which is the AutoValue class in Kotlin,  so to this, which is a much smaller, concise version  and simple that gives us pretty much everything  that AutoValue gave in Java.  This is not to say that AutoValue is not good.  By all means, it's very good if you're using Java,  just that if you're moving to Kotlin,  data classes offer a much better way  to achieve the same result.  Two things to say here.  First, we are using a parcelized plugin,  which is Kotlin's answer to parsable implementation.  What is different here in this plugin  is that this is a compiler plugin.  So again, there is no notation processing happening here.  And you can see this on Android Studio.  We can go to Tools, Option, and show the Kotlin bytecode.  And if you press the Compile button,  you get this, which is our data class,  the compiled into Java.  And you can see everything that we  had in our AutoValue-generated class,  so all the null checks, all the equals hash code, and so on,  and the parcelable implementation.  And this was done at compile time by the parcelized plugin.  Another thing to say here is that we are using JSON.  And JSON doesn't have Kotlin support.  And so if we just have this class as it is shown here,  and we're trying to parse the content from a JSON response,  what happens, for example, if login is not present?  JSON doesn't know about non-optional and optional  fields in Kotlin.  So what it's going to do is assign null to this login  field anyway, even though in Kotlin,  this is not an expected behavior.  So what happens is that the program is going to fail later.  It's still going to parse.  It's still going to work as a parsing result.  But once you need to use that field later in your code,  and you don't know exactly where,  this will fail in some unexpected ways  that you're only going to notice later,  or you might not even notice independently  on how your code works or which kinds of fields  are having no values assigned to them.  Which leads us to our next step, which is,  so how do we handle serialization?  Well, we're using JSON before, which is a classic.  And it worked well for us until now.  There is Moschi as an alternative, which is faster than JSON.  Doesn't have many of its quirks.  It's a smaller library as well.  So in general, it has advantages over JSON.  And more recently, I don't know how many of you  attended the talk before lunch where this was shown as well,  that more recently, the new version of Moschi  also has a code generation for creating the adapter  at annotation processing time.  So you don't need to rely on Kotlin reflection, which  is quite a big library.  And so Moschi comes with Kotlin support out of the box.  Well, for us, we had JSON all over the place in our code.  There was an alternative.  So there is an extension from this author,  which is a library called JSONValue that  works on top of JSON and generates the adapters for us.  Moschi has a similar solution if you're looking at an alternative  from what Moschi provides, which is called Kotchi.  And they both work similarly.  So JSONValue, all you have to do is annotate your class  with the JSON constructor.  And if you have any snake case to camel case conversion to do,  you can do so with serialized name.  Kotchi works similarly.  So you annotate it with JSON serializable.  And the camel case or the custom names  that you want to change from the response,  you also can set up this way.  However, there is a third option that we looked into,  which is newer and is also provided by Kotlin, which  is Kotlin x serialization.  Or we're going to refer to it as k serializer here.  And k serializer, in principle, works  similarly as the other two solutions.  So you basically annotate it with serializable  on the constructor and custom fields with serial name.  But two things make Kotlin x serializer  more interesting.  First, this is a compiler plugin.  It's not an annotation.  There is no annotation processing step here.  So this is our compiled user class.  And you can see that the serializer annotation added  this serializer field to our class at compile time.  And the second thing that is interesting  is that this is multi-platform.  So if we look quickly at the source code of the k serializer,  we have this at the common.  We have this interface at the common level,  which is independent of platform,  which is this interface expecting a serializer  in our class.  And note the expect keyword here by Kotlin, which is a way  to tell the compiler that we need to have a platform-specific  implementation.  And if you look at the GVM part of the source code,  so we have the actual implementation of this function  that returns the serializer that was compiled in our object  for us.  And you can see an example of this in this app by Jake Wharton.  That is an example of Kotlin multi-platform  that does search on the Android SDK, both on Android  and as a Chrome extension, I think, I believe.  And it uses Kotlin serialization as well as coroutines  and Kotlin multi-platform.  So it's an interesting example to look at.  All right.  So we looked at these three.  There are others, but these two were the more likely candidates  to transition from G-SUN in our case.  They all had issues with the field name,  so we can't have a general field name policy assigned  to our classes.  So we need to use this serialized name,  this custom transformation for fields that use  camel case in our project.  And ultimately, K Serializer looks very appealing.  It's multi-platform.  It doesn't rely on reflection.  It's a compiler plugin, has more flexible adapters.  And when we first looked at it, was it faster?  We wondered.  And when we first benchmarked this in February,  turns out it was not by a lot.  We build on top of this work by Zach Sewers.  Sorry if I'm pronouncing his name wrong.  Where he benchmarked other solutions, but not  the case serializer.  So we added to it.  And it turns out at the time, it was three times as slow  in some cases.  So our solution at the time was to use reflection plus G-SUN  value where it made sense to use it.  Since we control our API, we knew where  we could rely on reflection from G-SUN.  Today, however, looking at this again,  so we ran the same benchmarks again.  And we can see that the folks at Kotlin  did a lot of work to improve the performance.  And they're basically just as fast now.  So there's basically no much of a difference between them.  And if you look at auto-value, just out of curiosity,  you can see that auto-value adds a little bit  to the performance.  But it's not by much.  So if you're looking at auto-value for performance,  that's not necessarily, you're not  going to have much of a gain there.  All right.  So now we have all the information  that we need to actually do the migration.  So we know that there are advantages to using data class.  And we know it can help simplify our project.  However, migration at first didn't look as trivial.  Because if you just use the conversion tool from Android  Studio, we get an auto-value class converted to Kotlin.  And then there's quite a tedious process  of renaming the fields and the methods manually  to data class.  Which when we first tried this turned out  to be quite annoying, especially because the Java classes  that we had that used those model classes,  we needed to rename those manually to prefix them  with a get.  Because from Java, when you access a data class field,  we need to call getUserName, getAge, and so on.  So what did we do instead?  Well, we wrote an IntelliJ plugin  to do this factor for us.  And before we go into the code for that,  let's see if we can get a quick demo.  How do I get there?  This is maybe a bit bigger than I expected.  But so this is a very simplified version  of our abstract class.  We're not looking at annotation here just to simplify this.  So here we have just an abstract class,  but it looks basically like an auto-value class.  And here we have a simple usage in Kotlin.  Just combines the name with the age and returns a string.  And here we have the same usage in Java.  Well, it's cutting, but it does the same thing.  So it formats and returns username, then comma age.  So if you go back to the user class  and then we have the plugin action example convert  to data class, hopefully, yes.  We get now our data class, our abstract class  converted to data class.  And in Kotlin, they are now a field.  They're not any more abstract method being accessed.  And in Java, we have the user get name and user get age,  which developing this turnout to be simpler than expected.  And so the results were quite good.  So we were very happy that this worked out  and simplified the migration process for us.  So how does this plugin work?  The entry point for a plugin, in this case, is an action.  So the user is tapping on the menu option  to make the conversion.  So when that happens, we get this call on the action  performed event in our plugin.  And this is a simple boilerplate code  that we can add to start the refactoring.  So this is a simple example.  So we look into a simple example here,  where we first get the current file that is open.  And that returns a PSI file.  A PSI file is basically an interface  that IntelliJ provides for us to navigate through our code  in that file.  And once we have access to the file,  we can navigate the source code via a tree visitor, which  then has different methods we can override  to visit different elements in our code.  So here, for example, every time the visitor encounters a class,  it's going to call back on this visit class function.  And then what we can do with it is  have a write command action where we can write some code  or make some replacements or some refactoring or so on  in that class.  So in our case, we want to convert from abstract class  to a data class.  What we do here then is check if it's not a data class.  And if it's not, again, we're simplifying things here.  We're not doing all the checks we should  if we're publishing this plugin on IntelliJ.  And if it's not, we can add the modifier data keyword  to make it a data class.  And if it's abstract, we remove the abstract keyword.  Next, we have the visit name function callback,  where we can also run commands to then change  the names of our methods.  So here, we want to change the users, like the colors,  the name of the colors of our methods.  Since we are changing, we have an abstract call method,  for example, named age.  And we want to make sure that the Java colors are now  changed to get age instead of just age.  So here's what's happening here.  We use this reference search, dot search,  to find the usages of this function.  And then for each user of this function,  we call this handle element rename to get age instead of age.  This works well for Java, but for Kotlin, what we want to do  is change the type of call.  So we are not any more calling a method,  we are calling a field.  So for Java, for Kotlin, what we need to do  is check the language of the element.  So if it's Java, we can do the handle element rename  that we're doing.  If it's Kotlin, however, we can take advantage  that this is an abstract syntax tree, basically,  of our code.  So we can get the parent of the method name  that has been called, because the parent represents  the name of the method plus its parameters,  and replace it with the field name that we are creating  in our Kotlin data class.  And that's what we are doing here.  So we are getting the parent, and we're  replacing it with this new expression  that we're creating based on the name of the parameter we  are adding to our data class.  And that's basically the gist of it.  If you're interested, the source code for this example,  the full one that works like I showed before in the demo,  is available at this address.  If you're more interested in IntelliJ plugins,  here are some more links.  I will publish these slides later so you can access them.  One last thing that we did with our data class  in our project, because we wanted to keep compatibility  with Java.  We still had Java classes.  We're not migrating everything to Kotlin straight away.  So we kept the with methods that we had,  because in Java, you cannot use copy out of the box  for one single transformation.  In Java, you have to transform all the methods,  if you want, to use copy.  So it was easier to just keep the with that we had already  for Java classes and wrap copy inside with the information  that we wanted to change.  And similarly for the create, we wanted  to keep the create for the Java users, for the Java colors.  So we just annotated them with GVM static  so that we could access them from Java.  Finally, just a short notice on tests.  We had a lot of tests already for parsing our classes.  And it was very helpful to ensure  that everything was working as intended  once we finished the transformation.  So it helped us ensure the correct serialization  and the serialization of our classes.  It helped ensure that the conversion of the methods  were correct.  And in some cases, it was good to have  Moquito now supporting more final classes,  because in Kotlin everything is final by default.  And in a few cases, we needed that,  and it was helpful to have Kotlin supported.  So in the end, we had these 46 auto-value classes in our project  that we converted to Kotlin.  These auto-value classes, they were generating  these extra classes and these 500 plus methods.  We got rid of four dependencies.  We added one, the G-SUN value.  Because annotation processing was happening less now,  we had less plugins.  We were not using auto-value anymore.  Our build time improved a lot.  The clean build was more than a minute faster.  And the incremental builds when changing one of these classes  improved a bit, not as much, but still  was a significant change.  The APK size remained the same, because the auto-value  is done at compile time.  So we didn't change much on the APK,  other than the additional classes that we didn't have anymore.  And because of the mixed serialization approach,  the startup time increased a bit, but we were OK with it.  We are hoping to replace G-SUN with Kotlin X serialization  and then measure this again to see how it helps.  And with that, I thank you.  I think I was a bit early.  Sorry, I was a bit fast, maybe.  But any questions?  No worries.  First question?  Yeah, so you show how to do, how to migrate from auto-value  to data classes.  Yeah.  I'm working for a library, and I'm  used to provide some builders to allow my clients to use  the builder in a sort of create the classes in a elegant way.  For the data classes in Kotlin, what I have seen  is you have to create like a class inside and do everything  by your hand.  Do you know there is a better way to do it?  That's the only way that this person can do it.  Other than having a builder for your data class?  Yeah, so the way that Kotlin provides,  you create a class inside your class.  Example, this is how you have the company object to me.  Yeah, other than the company in origin,  you need to create a class inside your data class.  No, you need to create a class called builder.  OK.  In this class, you're putting all the values to make a builder,  and you are making the builder by your hand.  But I don't know if I get why you need to do that,  because with data classes, you can just call the constructor  directly, right?  And if you need default values, you can just set default  in the data class field.  It's because I also have Java code.  It's not migrating only.  Yeah, but then you need to do something like this, right?  You need to have some kind of create function that  creates with the defaults, or like some kind of, yeah,  you need to do a builder like you do in Java.  There is no way.  I'm not aware of a way to get out of that.  OK, thank you.  Thanks.  Hi.  Thanks for the talk.  Just one question.  When you use Moji to pass the JSON data,  you can use all the features Kotlin provides,  like construct the default parameters and stuff.  Do you also have this with Kotlin X serialization?  Or is it possible to set it up somehow?  I believe so.  I didn't test Kotlin X for that specifically.  I tested this with JSON.  And with JSON, it doesn't work.  Because actually, JSON is using a trick  with the unsafe package of Java.  So if you're using the default methods, it's going to fail.  Unless you annotate all your fields with default,  then it works because it becomes a constructor.  Or you can do an extra constructor in your data  class to provide it with the default values.  Then it works with JSON.  But with Kotlin X, I'm not sure.  I would say yes, but I didn't test.  OK, thanks.  Yes.  Hi.  Thank you for the talk.  Just one question.  Why haven't you choose Moji instead of JSON?  Why haven't you choose JSON?  Because we had JSON in the project already  with custom adapters in different places.  So it would be a bigger change.  And we are hoping to migrate to Kotlin X serialization  once it becomes more stable.  And then we can.  So I mean, this is a bigger talk.  We are hoping to have a common Kotlin module  that we can share between Android and iOS.  And we are hoping the case serializer  can be part of the solution.  And so we didn't want to do a two-step migration.  And JSON was already working for us.  So we didn't feel the need to migrate to Moji  to then migrate second time to case serializer.  Yeah, yeah.  I got what you mean about this case serializer.  Isn't it in beta still?  It's in what?  Sorry?  In beta?  Yes.  So it's marked as experimental.  But I mean, it's already working.  And it works already for different use cases.  In January, in February, when we finished this migration,  we didn't start adopting it because of the performance.  So we had concerns with that.  But they were quite fast and responsive  to do the work to actually improve it  and to make it reach the levels of JSON and Moji.  And so now we are looking at it again,  but probably in another talk.  OK, mate, thank you.
_DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_0_3347.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_3348_4451.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_4452_4559.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_4560_5147.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_5148_8759.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_8760_9287.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_9288_10055.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_10056_10259.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_10260_10715.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_10716_13235.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_13236_14003.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_14004_15191.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_15192_15911.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_15912_16559.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_16560_17075.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_17076_18671.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_18672_21191.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_21192_21827.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_21828_24143.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_24144_25055.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_25056_28631.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_28632_28847.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_28848_29003.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_29004_29519.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_29520_29771.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_29772_29855.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_29856_29903.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_29904_29963.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_29964_30071.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_30072_30239.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_30240_30311.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_30312_30455.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_30456_30599.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_30600_30659.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_30660_31055.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_31056_36743.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_36744_37223.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_37224_38339.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_38340_39287.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_39288_42899.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_42900_43031.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_43032_43367.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_43368_48575.png _DCBerlin18_407_Lage_Migrating_from_Autovalue_to_K_13CFNHHYnjs_48576_48582.png
I'm an Android engineer at Blablacar. And today, I'm going to present to you our experience migrating one of our apps from AutoValue to Kotlin data classes. Before we get into the migration process itself, let's just take a quick look into what is AutoValue and why we decided to use it to begin with. So here is a simple harmless user class. And we can typically call it a value or content class because we're interested in its contents. And if that is the case, there's a number of things we can do with this class to make sure we use it properly in Java. For example, we can make sure that the classes annotated the final and its fields are final as well, so we ensure that the contents are immutable. If we do that, we need to make sure we have a constructor to construct our class. OK, this is just for the time. So we need to have a constructor to ensure that our class can be accessed properly. We might also be interested in annotating fields that are nullable with a nullable annotation. Since we're interested in the contents, we may as well, we should also add override equals and hash code. If you're using Android Studio or IntelliG or most modern IDs, the IDs can generate this code for us. But we still need to manually go into the function that generates this code and add it to our user class. Again, if you're using Parcelable on Android and we want to have our user class implemented, we need to add the implementation for Parcelable. We need to expose the right getters for the content that we want to access. If we want to change some of the contents, we probably want to add some kind of function like this to copy the contents to a new instance of our user class, and so on and so forth. And in the end, what we get is this huge class with a lot of boilerplate code just to make sure that the contents of our class is properly handled. And that's not all. So there's a lot of things that I didn't add to that code. So if we want, we could also annotate the non-null fields. We didn't add the two string implementation to that class. If we remove or if we add a field, we need to ensure the consistency of the implementations of hash code, of equals, and so on. If we are doing serialization with this class, we need either to write our own custom adapter or rely on reflection and repeat this for every new class that we want to behave the same way. What is the solution? So one of them is AutoValue. And AutoValue is a library by Google which basically addresses all of these issues. And it automatically generates these boilerplate code for us. So it can generate equals, hash code, two string, and so on. And it has a powerful plugin system that we can add other dependencies to make sure these other kinds of boilerplate code that we need on Android is also generated for us. Why use it? So this is from why this? OK. No, it's good. So why use AutoValue? This is taken from their GitHub page. So these are the reasons that they explain. If you follow that link later, they have also a presentation where they explain more details about the advantages of AutoValue over different solutions. Basically, there are no runtime dependencies. Every time is generated at compile time. The API is invisible. The class that is going to be generated functions like any other in your project. There is very little cost to performance since everything is done at compile time. Your class is mostly like any other. So there's very few things you cannot do with it. There's then very little magic that is happening behind the scenes. So how does it work? Well, basically, you annotate your class with AutoValue. You don't know why this is. So what you have to do is have an abstract class that you annotate with AutoValue. And the fields now become abstract methods. And so our previous class that we had fields for the values that we interested in, now the abstract fields. And the ones that are nullable, we annotate as such. And behind the scenes, what AutoValue is doing is generating this code for us. So this is not us writing this anymore. So all the note checks, all the hash code, all the equal implementation, and so on, is generated by AutoValue. So it takes that burden from us. And it's done automatically through an annotation processor. But now, since this is an abstract class, we need to somehow expose a way to get an instance of this class. And we do this with a static method. Typically, you can also use a builder pattern if you want. So AutoValue provides that for you as well. Here, to simplify, we have a simple create static method that then behind the scenes, we are returning an instance of this AutoValue underscore user that AutoValue created for us. In addition, there are these plugins that I mentioned before. So AutoValue has this powerful plugin system that you can basically add more things to generate code for you for your class. In our project, we use these three. So with AutoValue, with AutoValue G-SUN for serialization, and AutoValue Parcel for the ParcelBow implementation. And with these plug-ins in place, all you have to do is add the header of the things you're interested in. So for ParcelBow, you just have to say that your class now implements ParcelBow. For the transformations that you want to change, some fields, you can annotate them with this with method. And for serialization, you have to add this static type adapter function that returns the type adapter that was generated for you by the plugin. What AutoValue is doing now is creating these four different classes. So these are all extending from one another. So if you look behind the scenes, what AutoValue is doing, so there is one first class with the $3 that extends from our original user abstract class. And this is the original AutoValue generated code. Then you have another one that extends from the previous that has the G-SUN type adapter inside that was also generated by the plugin. A third one, which is for the width transformation that we have. And finally, the last one, which handles the ParcelBow implementation on Android. So this is all now generated for us. And we don't need to bother with creating this code anymore. So we just have this abstract class with the content that we're interested in. There's very few extra annotations that we need to add to our class. And we get all these code generated for us for free. And if we have that, why migrate to data classes then? In principle, since we have this class now in Java, and if we are migrating our project to Kotlin, we could just have AutoValue in Kotlin. The reason we migrate to data classes is because, well, they are a native answer to value classes. So data classes already out of the box give us pretty much everything that AutoValue does as an external dependency. So if you're using Kotlin, data classes generate equals hash code and tree string for you at compile time. So you're not doing an annotation processing step anymore. This is done during compilation of your Kotlin class. And you get extra stuff. For example, copy replaces our width function that we had. And it's more powerful because it's much more flexible in what kinds of contents you can modify. And you also get destructuring declarations, which is this way of assign your object into different variables straight away, which is quite useful, for example, if you're changing our X operators or in lambda declarations in general. So what we get is comparing the two is that with data classes, we have less external dependencies. The code is much simpler, much smaller. It's just one line of code instead of all these fields and this abstract class that we have. We don't need an abstract class anymore, so we get our final class. We don't have an annotation processing step happening because this is done by Kotlin at compile time. And we have a few smaller plugin issues as well that we don't get anymore with Kotlin data classes. So with the plugin just briefly, well, there are yet other dependencies in our code. So they are not provided by Google. They're not part of AutoValue itself. So the other libraries by other developers, that if we're using them, there are other dependencies that we need to make sure we keep up to date. The width, for every little configuration that we have for the data we want to change in our classes, you need to provide a new method for it. That's how the plugin works. So if you have many transformations, that can become quite an extensive list of width functions in our class. And in case of Gsun, in our project, we use Gsun. You cannot use the field name policy. You need to annotate all your chemo case fields with this naked case version if that's what your API is returning. So in other words, what we are doing is moving from this, which is the AutoValue class in Kotlin, so to this, which is a much smaller, concise version and simple that gives us pretty much everything that AutoValue gave in Java. This is not to say that AutoValue is not good. By all means, it's very good if you're using Java, just that if you're moving to Kotlin, data classes offer a much better way to achieve the same result. Two things to say here. First, we are using a parcelized plugin, which is Kotlin's answer to parsable implementation. What is different here in this plugin is that this is a compiler plugin. So again, there is no notation processing happening here. And you can see this on Android Studio. We can go to Tools, Option, and show the Kotlin bytecode. And if you press the Compile button, you get this, which is our data class, the compiled into Java. And you can see everything that we had in our AutoValue-generated class, so all the null checks, all the equals hash code, and so on, and the parcelable implementation. And this was done at compile time by the parcelized plugin. Another thing to say here is that we are using JSON. And JSON doesn't have Kotlin support. And so if we just have this class as it is shown here, and we're trying to parse the content from a JSON response, what happens, for example, if login is not present? JSON doesn't know about non-optional and optional fields in Kotlin. So what it's going to do is assign null to this login field anyway, even though in Kotlin, this is not an expected behavior. So what happens is that the program is going to fail later. It's still going to parse. It's still going to work as a parsing result. But once you need to use that field later in your code, and you don't know exactly where, this will fail in some unexpected ways that you're only going to notice later, or you might not even notice independently on how your code works or which kinds of fields are having no values assigned to them. Which leads us to our next step, which is, so how do we handle serialization? Well, we're using JSON before, which is a classic. And it worked well for us until now. There is Moschi as an alternative, which is faster than JSON. Doesn't have many of its quirks. It's a smaller library as well. So in general, it has advantages over JSON. And more recently, I don't know how many of you attended the talk before lunch where this was shown as well, that more recently, the new version of Moschi also has a code generation for creating the adapter at annotation processing time. So you don't need to rely on Kotlin reflection, which is quite a big library. And so Moschi comes with Kotlin support out of the box. Well, for us, we had JSON all over the place in our code. There was an alternative. So there is an extension from this author, which is a library called JSONValue that works on top of JSON and generates the adapters for us. Moschi has a similar solution if you're looking at an alternative from what Moschi provides, which is called Kotchi. And they both work similarly. So JSONValue, all you have to do is annotate your class with the JSON constructor. And if you have any snake case to camel case conversion to do, you can do so with serialized name. Kotchi works similarly. So you annotate it with JSON serializable. And the camel case or the custom names that you want to change from the response, you also can set up this way. However, there is a third option that we looked into, which is newer and is also provided by Kotlin, which is Kotlin x serialization. Or we're going to refer to it as k serializer here. And k serializer, in principle, works similarly as the other two solutions. So you basically annotate it with serializable on the constructor and custom fields with serial name. But two things make Kotlin x serializer more interesting. First, this is a compiler plugin. It's not an annotation. There is no annotation processing step here. So this is our compiled user class. And you can see that the serializer annotation added this serializer field to our class at compile time. And the second thing that is interesting is that this is multi-platform. So if we look quickly at the source code of the k serializer, we have this at the common. We have this interface at the common level, which is independent of platform, which is this interface expecting a serializer in our class. And note the expect keyword here by Kotlin, which is a way to tell the compiler that we need to have a platform-specific implementation. And if you look at the GVM part of the source code, so we have the actual implementation of this function that returns the serializer that was compiled in our object for us. And you can see an example of this in this app by Jake Wharton. That is an example of Kotlin multi-platform that does search on the Android SDK, both on Android and as a Chrome extension, I think, I believe. And it uses Kotlin serialization as well as coroutines and Kotlin multi-platform. So it's an interesting example to look at. All right. So we looked at these three. There are others, but these two were the more likely candidates to transition from G-SUN in our case. They all had issues with the field name, so we can't have a general field name policy assigned to our classes. So we need to use this serialized name, this custom transformation for fields that use camel case in our project. And ultimately, K Serializer looks very appealing. It's multi-platform. It doesn't rely on reflection. It's a compiler plugin, has more flexible adapters. And when we first looked at it, was it faster? We wondered. And when we first benchmarked this in February, turns out it was not by a lot. We build on top of this work by Zach Sewers. Sorry if I'm pronouncing his name wrong. Where he benchmarked other solutions, but not the case serializer. So we added to it. And it turns out at the time, it was three times as slow in some cases. So our solution at the time was to use reflection plus G-SUN value where it made sense to use it. Since we control our API, we knew where we could rely on reflection from G-SUN. Today, however, looking at this again, so we ran the same benchmarks again. And we can see that the folks at Kotlin did a lot of work to improve the performance. And they're basically just as fast now. So there's basically no much of a difference between them. And if you look at auto-value, just out of curiosity, you can see that auto-value adds a little bit to the performance. But it's not by much. So if you're looking at auto-value for performance, that's not necessarily, you're not going to have much of a gain there. All right. So now we have all the information that we need to actually do the migration. So we know that there are advantages to using data class. And we know it can help simplify our project. However, migration at first didn't look as trivial. Because if you just use the conversion tool from Android Studio, we get an auto-value class converted to Kotlin. And then there's quite a tedious process of renaming the fields and the methods manually to data class. Which when we first tried this turned out to be quite annoying, especially because the Java classes that we had that used those model classes, we needed to rename those manually to prefix them with a get. Because from Java, when you access a data class field, we need to call getUserName, getAge, and so on. So what did we do instead? Well, we wrote an IntelliJ plugin to do this factor for us. And before we go into the code for that, let's see if we can get a quick demo. How do I get there? This is maybe a bit bigger than I expected. But so this is a very simplified version of our abstract class. We're not looking at annotation here just to simplify this. So here we have just an abstract class, but it looks basically like an auto-value class. And here we have a simple usage in Kotlin. Just combines the name with the age and returns a string. And here we have the same usage in Java. Well, it's cutting, but it does the same thing. So it formats and returns username, then comma age. So if you go back to the user class and then we have the plugin action example convert to data class, hopefully, yes. We get now our data class, our abstract class converted to data class. And in Kotlin, they are now a field. They're not any more abstract method being accessed. And in Java, we have the user get name and user get age, which developing this turnout to be simpler than expected. And so the results were quite good. So we were very happy that this worked out and simplified the migration process for us. So how does this plugin work? The entry point for a plugin, in this case, is an action. So the user is tapping on the menu option to make the conversion. So when that happens, we get this call on the action performed event in our plugin. And this is a simple boilerplate code that we can add to start the refactoring. So this is a simple example. So we look into a simple example here, where we first get the current file that is open. And that returns a PSI file. A PSI file is basically an interface that IntelliJ provides for us to navigate through our code in that file. And once we have access to the file, we can navigate the source code via a tree visitor, which then has different methods we can override to visit different elements in our code. So here, for example, every time the visitor encounters a class, it's going to call back on this visit class function. And then what we can do with it is have a write command action where we can write some code or make some replacements or some refactoring or so on in that class. So in our case, we want to convert from abstract class to a data class. What we do here then is check if it's not a data class. And if it's not, again, we're simplifying things here. We're not doing all the checks we should if we're publishing this plugin on IntelliJ. And if it's not, we can add the modifier data keyword to make it a data class. And if it's abstract, we remove the abstract keyword. Next, we have the visit name function callback, where we can also run commands to then change the names of our methods. So here, we want to change the users, like the colors, the name of the colors of our methods. Since we are changing, we have an abstract call method, for example, named age. And we want to make sure that the Java colors are now changed to get age instead of just age. So here's what's happening here. We use this reference search, dot search, to find the usages of this function. And then for each user of this function, we call this handle element rename to get age instead of age. This works well for Java, but for Kotlin, what we want to do is change the type of call. So we are not any more calling a method, we are calling a field. So for Java, for Kotlin, what we need to do is check the language of the element. So if it's Java, we can do the handle element rename that we're doing. If it's Kotlin, however, we can take advantage that this is an abstract syntax tree, basically, of our code. So we can get the parent of the method name that has been called, because the parent represents the name of the method plus its parameters, and replace it with the field name that we are creating in our Kotlin data class. And that's what we are doing here. So we are getting the parent, and we're replacing it with this new expression that we're creating based on the name of the parameter we are adding to our data class. And that's basically the gist of it. If you're interested, the source code for this example, the full one that works like I showed before in the demo, is available at this address. If you're more interested in IntelliJ plugins, here are some more links. I will publish these slides later so you can access them. One last thing that we did with our data class in our project, because we wanted to keep compatibility with Java. We still had Java classes. We're not migrating everything to Kotlin straight away. So we kept the with methods that we had, because in Java, you cannot use copy out of the box for one single transformation. In Java, you have to transform all the methods, if you want, to use copy. So it was easier to just keep the with that we had already for Java classes and wrap copy inside with the information that we wanted to change. And similarly for the create, we wanted to keep the create for the Java users, for the Java colors. So we just annotated them with GVM static so that we could access them from Java. Finally, just a short notice on tests. We had a lot of tests already for parsing our classes. And it was very helpful to ensure that everything was working as intended once we finished the transformation. So it helped us ensure the correct serialization and the serialization of our classes. It helped ensure that the conversion of the methods were correct. And in some cases, it was good to have Moquito now supporting more final classes, because in Kotlin everything is final by default. And in a few cases, we needed that, and it was helpful to have Kotlin supported. So in the end, we had these 46 auto-value classes in our project that we converted to Kotlin. These auto-value classes, they were generating these extra classes and these 500 plus methods. We got rid of four dependencies. We added one, the G-SUN value. Because annotation processing was happening less now, we had less plugins. We were not using auto-value anymore. Our build time improved a lot. The clean build was more than a minute faster. And the incremental builds when changing one of these classes improved a bit, not as much, but still was a significant change. The APK size remained the same, because the auto-value is done at compile time. So we didn't change much on the APK, other than the additional classes that we didn't have anymore. And because of the mixed serialization approach, the startup time increased a bit, but we were OK with it. We are hoping to replace G-SUN with Kotlin X serialization and then measure this again to see how it helps. And with that, I thank you. I think I was a bit early. Sorry, I was a bit fast, maybe. But any questions? No worries. First question? Yeah, so you show how to do, how to migrate from auto-value to data classes. Yeah. I'm working for a library, and I'm used to provide some builders to allow my clients to use the builder in a sort of create the classes in a elegant way. For the data classes in Kotlin, what I have seen is you have to create like a class inside and do everything by your hand. Do you know there is a better way to do it? That's the only way that this person can do it. Other than having a builder for your data class? Yeah, so the way that Kotlin provides, you create a class inside your class. Example, this is how you have the company object to me. Yeah, other than the company in origin, you need to create a class inside your data class. No, you need to create a class called builder. OK. In this class, you're putting all the values to make a builder, and you are making the builder by your hand. But I don't know if I get why you need to do that, because with data classes, you can just call the constructor directly, right? And if you need default values, you can just set default in the data class field. It's because I also have Java code. It's not migrating only. Yeah, but then you need to do something like this, right? You need to have some kind of create function that creates with the defaults, or like some kind of, yeah, you need to do a builder like you do in Java. There is no way. I'm not aware of a way to get out of that. OK, thank you. Thanks. Hi. Thanks for the talk. Just one question. When you use Moji to pass the JSON data, you can use all the features Kotlin provides, like construct the default parameters and stuff. Do you also have this with Kotlin X serialization? Or is it possible to set it up somehow? I believe so. I didn't test Kotlin X for that specifically. I tested this with JSON. And with JSON, it doesn't work. Because actually, JSON is using a trick with the unsafe package of Java. So if you're using the default methods, it's going to fail. Unless you annotate all your fields with default, then it works because it becomes a constructor. Or you can do an extra constructor in your data class to provide it with the default values. Then it works with JSON. But with Kotlin X, I'm not sure. I would say yes, but I didn't test. OK, thanks. Yes. Hi. Thank you for the talk. Just one question. Why haven't you choose Moji instead of JSON? Why haven't you choose JSON? Because we had JSON in the project already with custom adapters in different places. So it would be a bigger change. And we are hoping to migrate to Kotlin X serialization once it becomes more stable. And then we can. So I mean, this is a bigger talk. We are hoping to have a common Kotlin module that we can share between Android and iOS. And we are hoping the case serializer can be part of the solution. And so we didn't want to do a two-step migration. And JSON was already working for us. So we didn't feel the need to migrate to Moji to then migrate second time to case serializer. Yeah, yeah. I got what you mean about this case serializer. Isn't it in beta still? It's in what? Sorry? In beta? Yes. So it's marked as experimental. But I mean, it's already working. And it works already for different use cases. In January, in February, when we finished this migration, we didn't start adopting it because of the performance. So we had concerns with that. But they were quite fast and responsive to do the work to actually improve it and to make it reach the levels of JSON and Moji. And so now we are looking at it again, but probably in another talk. OK, mate, thank you.
migrating one of our apps from AutoValue to them is AutoValue. And AutoValue is a library by has a powerful plugin system that we can add So these are the reasons that they explain. If about the advantages of AutoValue over different things to generate code for you for your class. annotate them with this with method. And for in Java, and if we are migrating our project to well, they are a native answer to value classes. this way of assign your object into different Kotlin data classes. So with the plugin just in our code. So they are not provided by Google. offer a much better way to achieve the same result. Two things to say here. First, we are us. And you can see an example of this in this There are others, but these two were the more not by a lot. We build on top of this work by the conversion tool from Android Studio, we get conversion. So when that happens, we get this example. So we look into a simple example here, Kotlin, however, we can take advantage that this parameters, and replace it with the field name serialization approach, the startup time inside your class. Example, this is how you have