Recently Kotlin 1.3.70 was released, adding many improvements and fixes for Kotlin. One of the most exciting improvements is the one mentioned below:
Support for multiple Kotlin frameworks in a single application
Previously, there was a known issue that an application could not use more than one dynamic Kotlin/Native framework because Obj-C classes defined in runtime were conflicting, coming from different instances of runtime. We’ve fixed this in 1.3.70, so now an application can use multiple Kotlin/Native frameworks. Any common dependencies are present in the frameworks under different Swift modules (and with different Obj-C prefixes).
This is an exciting update! Prior to 1.3.70 you had to have all of your Kotlin code in one framework, essentially having a monolithic library. Now this update supports multiple frameworks which opens the door for library developers to publish KMP built frameworks for usage in iOS.
This is a great feature, but as your mind starts to wander about the possibilities of having multiple frameworks you may begin to have some questions about the specifics of implementing these frameworks. You may be thinking things like:
- Does this include static frameworks? The notes only mention dynamic frameworks
- Are there any naming restrictions? What if I have multiple classes or modules with the same name?
And for that matter..
- How well do the modules talk to each other? Do the multiple frameworks work together easily?
We had these questions too and we decided to do some research to try and find answers. So here are some of the results to our questions about multiple Kotlin frameworks.
Does this include static frameworks?
Answer: yes and no. While asking around the Kotlin slack (Which you can join via this link if you haven’t already https://surveys.jetbrains.com/s3/kotlin-slack-sign-up), and researching some of the existing Github issues, we’ve found out that the 1.3.70 supports static libraries…when they’re set to release. Currently multiple debug static frameworks are not supported in 1.3.70, as mentioned in this comment on the Kotlin-Native Repo in GitHub. (https://github.com/JetBrains/kotlin-native/issues/2423#issuecomment-591504566).
Note: Multiple debug static frameworks should work in Kotlin 1.4
Just to confirm this, and do some testing ourselves, we tested a bunch of different configurations to see what does and doesn’t work. Our findings are below:
|Library 1||Build Type||Library 2||Build Type||Result|
*Fails with both named and unnamed libraries with code:
- runtime assert: runtime injected twice
- Using simple framework importing, no cocoapods used.
- Statics set using isStatic in gradle
- binaries.getFramework(“DEBUG”) or binaries.getFramework(“RELEASE”) for build type
- Kotlin 1.3.70
So the only combination that fails is two debug static frameworks. Note that this will build successfully, however it will get a runtime assert of runtime assert: runtime injected twice when running.
Are there any naming restrictions?
Answer: Yes, but that’s true for any kind of framework
As you can imagine, you cannot have two frameworks with the same name. This would obviously confuse the compiler, even when using the baseName variable in gradle. That being said you can have same-named classes in multiple libraries. If XCode gives you an Ambiguous use of ___ error, you can clarify by referencing it by its library.
i.e. framework1.MyClass vs framework2.MyClass.
How well do the modules talk to each other?
This question is a little open ended, so to clarify we wanted to know what classes you can pass between your two Kotlin frameworks. The answer is actually more straightforward than you may think.
Answer: Essentially, simple types and built-in classes like String work. Other types do not.
The reason for this is because these built-in classes are using default Swift/Obj-C classes, so there’s no duplication. This does not work for custom classes because of how they’re defined in the framework. Custom classes described in one library are not identical, at a binary level, to that same class referenced in the second library. They are not interchangeable. Let’s look at an example.
(You have two Kotlin modules, K1 and K2. Both create their own iOS framework. Both K1 and K2 have a dependency on the Kotlin library BizLib. In BizLib is a class called Person. To iOS, they are different, and you cannot pass a K1.Person into K2)
So when you try to pass in a class from one framework to another, you get this type of error:
Cannot convert value of type K2Person to expected argument type K1Person
So you cannot pass custom objects into different frameworks, even if they’re referenced in the Kotlin code.
As before, we did some testing of our own. We made some examples and saw what compiled and what gave errors. Here are our findings:
|Arrays||Do Not Work||Error*|
|Lists||Works||Works unless the Element type is incompatible|
|Mutable Lists||Works||Works unless the Element type is incompatible|
|Maps||Works||Works unless the Key or Value type is incompatible|
|MutableMaps||Works||Works unless the Key or Value type is incompatible|
|String||Works||If a string is passed into the Map it is converted to an NSString|
|Boolean||Works||Can be compared successfully from both libraries|
|Int||Sometimes Works||For some reason, if an Int is passed into a list or map, it is converted to a “KotlinInt” and is not interchangeable. This is weird because just the int itself works fine.|
*The error in row 1 is:
Cannot convert value of type ‘Shared.KotlinArray<Shared.KotlinInt>’ to expected argument type ‘SecondLib.KotlinArray<SecondLib.KotlinInt>’
- Functions that return simple types (i.e. string, bool, int) work between frameworks
We tested various classes you may use, and came up with some interesting results. As mentioned earlier built-in class seems to work successfully, as well as simple types. This is because there’s already existing swift classes, thus not making new and duplicate classes. The strange finding is that simple types in collections are in a boxed form, but those are classes specific to the framework. For example both classes appear as Lib1.KotlinInt and Lib2.KotlinInt when in a collection.
While you can have multiple frameworks, because the same dependency between frameworks won’t be compatible, having many Kotlin iOS frameworks won’t really be practical. It’ll make more sense to have very few or one Kotlin iOS framework that is composed of multiple features. However, in cases where your modules are very different, it may make sense to have multiple Kotlin iOS frameworks. The bottom line is, you can have multiple Kotlin frameworks, and while it is not as flexible as some had hoped, it’s another configuration you can consider as you expand your Kotlin Multiplatform codebase.
So those were our findings! While there is still a lot to explore with using multiple Kotlin frameworks we think this helps clear up a lot of things. We now know what does and doesn’t work at the moment when working with multiple frameworks, at least as of 1.3.70. With 1.4 being worked on right now some of these answers may change for the better. We’ll be watching for these changes and looking into binary size considerations, debugging, and other issues soon.