Hi there :wave: Sorry for posting a bit long messa...
# koin
t
Hi there 👋 Sorry for posting a bit long message here. Now I'm struggling with an issue that I can't get an instance with params via Koin on my iOS app (KMP project). Anyone knows any clues to solve the issue? Let me share some code snippets below. [shared/commonMain] Added the following function to get injected instances on iOS modules.
Copy code
object KoinDependencyResolver : KoinComponent {
    @OptIn(BetaInteropApi::class)
    public fun resolve(
        objCObject: ObjCObject,
        qualifier: Qualifier? = null,
        parameters: List<Any?> = emptyList(),
    ): Any {
        val clazz = when (objCObject) {
            is ObjCClass -> getOriginalKotlinClass(objCClass = objCObject)
            is ObjCProtocol -> getOriginalKotlinClass(objCProtocol = objCObject)
            else -> null
        } ?: error("Could not find any corresponding Kotlin classes of $objCObject.")

        return getKoin().get(
            clazz = clazz,
            qualifier = qualifier,
            parameters = { parametersOf(parameters) }.takeIf { parameters.isNotEmpty() },
        )
    }
}
Then made the following plain object for injection.
Copy code
// a sample class to be injected
data class SampleData(val name: String, val age: Int)

// call it in the iOSApp entrypoint through a wrapper function like KoinIOS.startKoin()
val appModule = module {
    ...
    factory { (name: String, age: Int) ->
        SampleData(name, age)
    }
}
[iosApp] Trying to get the injected instance in ContentView with the following function.
Copy code
func get<T: AnyObject>(qualifier: Koin_coreQualifier? = nil, parameters: [Any] = []) -> T {
    return KoinDependencyResolver.shared.resolve(objCObject: T.self, qualifier: qualifier, parameters: parameters) as! T
}

// in ContentView, both didn't work
private let sampleData: SampleData = get(parameters: ["Smith", 30])
private let sampleData: SampleData = get(parameters: ["Smith", 30 as NSNumber])
The error I got on the injection.
Copy code
🔴 (koin) * Instance creation error : could not create instance for '[Factory: 'my.app.sample.SampleData']': org.koin.core.error.NoParameterFoundException: Can't get injected parameter #1 from DefinitionParameters[[Smith, 30]] for type '<http://kotlin.Int|kotlin.Int>'[kotlin.Exception]
Supplement: The following instance injection with only String param succeeded.
Copy code
// in shared/commonMain
data class SampleData(val name: String)

// in iosApp
private let sampleData: SampleData = get(parameters: ["Smith"])
👀 1
https://kotlinlang.org/docs/native-objc-interop.html#nsnumber could be related to this topic, but even after casting to KotlinInt it fails.
Copy code
private let sampleData: SampleData = get(parameters: ["Smith", KotlinInt(int: 30)])
a
yeah for now usage from iOS/ObjC is limited. I see that you try to share directly a dedicated
resolve
function
Any better update on your side?
t
I appreciate your comment. Unfortunately there's no good news from my side. But, what I found after some more experiments is that passing 2 string parameters also fails due to a similar exception. Let's say preparing the sample data class with first and last names by modifying the previous example.
Copy code
// a sample class to be injected
data class SampleData(val firstName: String, val lastName: String)

// call it in the iOSApp entrypoint through a wrapper function like KoinIOS.startKoin()
val appModule = module {
    ...
    factory { (firstName: String, lastName: String) ->
        SampleData(firstName, lastName)
    }
}
And passing 2 string data from Swift code also fails. At first, I thought the main cause came from the limitation of Kotlin / Objective-C interoperability on primitive types, but there might be some other reasons.
Copy code
// in ContentView, it didn't work
private let sampleData: SampleData = get(parameters: ["John", "Smith"])
Ref: 🪵
Copy code
iosApp[84960:9029428] :red_circle: (koin) * Instance creation error : could not create instance for '[Factory: 'my.app.sample.SampleData']': org.koin.core.error.NoParameterFoundException: Can't get injected parameter #1 from DefinitionParameters[[John, Smith]] for type 'kotlin.String'[kotlin.Exception]
Function doesn't have or inherit @Throws annotation and thus exception isn't propagated from Kotlin to Objective-C/Swift as NSError.
It is considered unexpected and unhandled instead. Program will be terminated.
org.koin.core.error.InstanceCreationException: Could not create instance for '[Factory: 'my.app.sample.SampleData']'
Uncaught Kotlin exception:     at 0   shared                              0x1062e73cf        kfun:kotlin.Exception#<init>(kotlin.String?;kotlin.Throwable?){} + 123 (/opt/buildAgent/work/b5c630f73501b353/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:25:70)
    at 1   shared                              0x1064c6e5f        kfun:org.koin.core.error.InstanceCreationException#<init>(kotlin.String;kotlin.Exception){} + 123 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/error/InstanceCreationException.kt:23:67)
    at 2   shared                              0x1064c8217        kfun:org.koin.core.instance.InstanceFactory#create(org.koin.core.instance.ResolutionContext){}1:0 + 1703 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/instance/InstanceFactory.kt:58:19)
    at 3   shared                              0x1064c7a47        kfun:org.koin.core.instance.FactoryInstanceFactory#get(org.koin.core.instance.ResolutionContext){}1:0 + 103 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/instance/FactoryInstanceFactory.kt:38:16)
    at 4   shared                              0x1064e1b03        kfun:org.koin.core.instance.InstanceFactory#get(org.koin.core.instance.ResolutionContext){}1:0-trampoline + 67 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/instance/InstanceFactory.kt:40:5)
    at 5   shared                              0x1064d2daf        kfun:org.koin.core.registry.InstanceRegistry#resolveInstance(org.koin.core.qualifier.Qualifier?;kotlin.reflect.KClass<*>;org.koin.core.qualifier.Qualifier;org.koin.core.instance.ResolutionContext){0§<kotlin.Any?>}0:0? + 343 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/registry/InstanceRegistry.kt:110:69)
    at 6   shared                              0x1064daf4b        kfun:org.koin.core.scope.Scope.resolveFromRegistry#internal + 323 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt:321:39)
    at 7   shared                              0x1064da3ab        kfun:org.koin.core.scope.Scope.resolveFromContext#internal + 1503 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt:311:16)
    at 8   shared                              0x1064d9713        kfun:org.koin.core.scope.Scope.stackParametersCall#internal + 871 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt:281:20)
    at 9   shared                              0x1064d9363        kfun:org.koin.core.scope.Scope.resolveInstance#internal + 867 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt:259:16)
    at 10  shared                              0x1064d8857        kfun:org.koin.core.scope.Scope.resolveWithOptionalLogging#internal + 1019 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt:232:20)
    at 11  shared                              0x1064d833f        kfun:org.koin.core.scope.Scope#get(kotlin.reflect.KClass<*>;org.koin.core.qualifier.Qualifier?;kotlin.Function0<org.koin.core.parameter.ParametersHolder>?){0§<kotlin.Any?>}0:0 + 323 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt:215:16)
    at 12  shared                              0x1064c148b        kfun:org.koin.core.Koin#get(kotlin.reflect.KClass<*>;org.koin.core.qualifier.Qualifier?;kotlin.Function0<org.koin.core.parameter.ParametersHolder>?){0§<kotlin.Any?>}0:0 + 259 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/Koin.kt:138:36)
...
a
Passing Class seems ok, but refs for parameter injection is well bridged with Kotlin I guess 🤔 We need more investigation on that
t
Yeah, by packing all properties into 1 data class, the following code worked as expected in Swift even if a primitive type (Int) was included!
Copy code
// a sample class to be injected
data class SampleData(val properties: Properties) {
    data class Properties(val firstName: String, val lastName: String, val age: Int)
}

// call it in the iOSApp entrypoint through a wrapper function like KoinIOS.startKoin()
val appModule = module {
    ...
    factory { (properties: Properties) ->
        SampleData(properties)
    }
}

// in ContentView, no errors
private let sampleData: SampleData = get(parameters: SampleData.Properties(firstName: "John", lastName: "Smith", age: 30))
However, I got the following exception if I tried to get any property (either firstName, lastName or age) in Swift. Seems like the exception might be related to the interoperability among languages though 🤔
Copy code
// throws an exception
print(sampleData.properties.firstName)
Ref: 🪵
Copy code
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Swift.__SwiftDeferredNSArray firstName]: unrecognized selector sent to instance 0x600000215200'
*** First throw call stack:
(
	0   CoreFoundation                      0x00000001804b910c __exceptionPreprocess + 172
	1   libobjc.A.dylib                     0x0000000180092da8 objc_exception_throw + 72
	2   CoreFoundation                      0x00000001804cebf4 +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0
	3   CoreFoundation                      0x00000001804bd40c ___forwarding___ + 1268
	4   CoreFoundation                      0x00000001804bfb2c _CF_forwarding_prep_0 + 92
	5   iosApp.debug.dylib                  0x0000000102f6361c $s6iosApp11ContentViewV8loadDatayyF + 88
	6   iosApp.debug.dylib                  0x0000000102f634b8 $s6iosApp11ContentViewV4bodyQrvgyycfU_ + 28
	7   SwiftUICore                         0x00000001d3ef65f8 $sIeg_ytIegr_TR + 20
	8   SwiftUICore                         0x00000001d3ef65
It turned out that accessing to any property succeeded if initializing the corresponding object by my own like this.
Copy code
let sampleData1: SampleData = get(parameters: SampleData.Properties(firstName: "John", lastName: "Smith", age: 30))
let sampleData2 = SampleData(properties: SampleData.Properties(firstName: "John", lastName: "Doe", age: 30))

print(type(of: sampleData1)) // prints SharedSampleData
print(type(of: sampleData2)) // prints SharedSampleData

print(type(of: sampleData1.properties)) // prints __SwiftDeferredNSArray 😮
print(type(of: sampleData2.properties)) // prints SharedSampleDataProperties

print(sampleData1.firstName) // crashes
print(sampleData2.firstName) // succeeds
Moreover, if I prepare a dedicated class that extends
KoinComponent
as suggested in the official doc, it also worked as expected.
Copy code
public object SampleDataComponent : KoinComponent {
    public fun provide(properties: SampleData.Properties): SampleData {
        val sampleData: SampleData by inject { parametersOf(properties) }
        return sampleData
    }
}
Then I noticed the received `parameters`'s type is
kotlin.native.internal.NSArrayAsKList
in my
KoinDependencyResolver
, which caused the crash above 🤦‍♂️
Was able to fix the issue by converting the received
parameter
in my
KoinDependencyResolver
!
Copy code
As-Is
parameters = { parametersOf(parameters) }.takeIf { parameters.isNotEmpty() },
To-Be
parameters = { parametersOf(*parameters.toTypedArray()) }.takeIf { parameters.isNotEmpty() },