Skip to content

[dev/kotlin-support] Weird issue decoding structs that have enum properties #8

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
ynnadrules opened this issue Mar 8, 2021 · 3 comments

Comments

@ynnadrules
Copy link

ynnadrules commented Mar 8, 2021

Given a swift struct that contains an enum-typed property (see below), its kotlin @SwiftValue-annotated counterparts, a single instance bridges perfectly back and forth over the bridge, via the JavaDecoder/JavaEncoder, with no loss in data. But when you have an ArrayList of instances of the kotlin data class that represents the swift struct, the value for the enum-typed property is lost and is nil on the swift side. I've chased this issue all the way down through JavaDecoder to where it tries to get the Field ID for the java object for the enum type. It fails to find the Field ID and prints an error (it's just logged because the missing field strategy that gets generated by swift-java-codegen is always .ignored) that says Ignored error: fieldNotFoundException for ....

Here's an example. Consider a scenario where you have a Swift and their Kotlin counterparts like below:

enum Category: String, Codable {
    case sedan, suv
}

struct Car: Codable {
    let description: String
    let category: Category
}

struct CarCollection: Codable {
    let cars: [Car]
}

Consider also their kotlin counterparts:

@SwiftValue
enum class Category(val rawValue: String) {
    sedan("sedan"),
    suv("suv")
}

@SwiftValue
data class Car(var description: String = "", var category: Category = null)

@SwiftValue
data class CarCollection(var cars: ArrayList<Car> = arrayListOf())

If I were to try to passing a CarCollection over the bridge from the kotlin side to the swift side, JavaDecoder would eventually try to decode a Car from the array list of Cars, and furthermore, eventually try to decode the category field on Car. This is where the problem shows up. The JNI bridging code tries to look up the FieldID for the category field on Car. However, when a type like Car that has a property that is an enum (maybe even just a string backed enum, not sure though), is in an array, the decoding process essentially fails to decode the whole object over the bridge because these properties that are enums end up failing to decode. If you were simply trying to decode a singular Car, that wasn't in an array list, everything would work properly.

I haven't been able to pin it down yet, but could there be a state issue with JavaDecoder when it creates JavaArrayContainer and then a JavaObjectContainer or is there something going on deeper in the JNI support code. Or could it be an issue with the bytecode generated by kotlin?

Thanks in advance for looking into this.

cc: @zayass @andriydruk

@andriydruk
Copy link
Member

Hi,

Probably this happened because of a lack of information about your package in JavaCoder.
When you pass any object throw JNI it uses JavaBridgable procotol with this default implementation. You can override this behavior with your own package for container types like Array, Dictionary, Set.

Please check an example of this override in swift-java-codegen. We are looking for a mechanism to resolve this issue in compile-time but for now we recommend to put all SwiftValue in one package and use those overrides

A general recommendation for issues like that: try to reproduce it in swift-java-codegen. If the issue reproduced we will really appreciate it if you create a PR with a failing test for this particular issue

@ynnadrules
Copy link
Author

ynnadrules commented Mar 10, 2021

Probably this happened because of a lack of information about your package in JavaCoder.
When you pass any object throw JNI it uses JavaBridgable procotol with this default implementation.

I believe we have the package set appropriately because the fieldNotFoundExceptions include the full package name:

Swift: Ignore error: fieldNotFoundException("edu.mobiletoolbox.mtbnavigation.kit/FNAMEStepResult.sectionLedu.mobiletoolbox.mtbnavigation.kit/FNAMESection;")

FNAMESection is a string-backed enum.

You can override this behavior with your own package for container types like Array, Dictionary, Set.
We're doing this too based on the swift-java-codegen example.

I'll reproduce it in swift-java-codegen and create a PR with a failing test.

Thanks @andriydruk!

@ynnadrules
Copy link
Author

ynnadrules commented Apr 12, 2021

@andriydruk I finally figured out was breaking string-backed enums for us. I did turn out to be the package name like you thought. It was super subtle:

edu.mobiletoolbox.mtbnavigation.kit...

vs

edu/mobiletoolbox/mtbnavigation/kit....

🤦

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants