Archie
12/16/2021, 2:11 PMMarkRS
12/16/2021, 3:17 PMArchie
12/16/2021, 3:21 PMMarkRS
12/16/2021, 3:23 PMArchie
12/16/2021, 3:23 PMAny
. I might be wrong though. I would avoid it if possible.MarkRS
12/16/2021, 3:25 PMArchie
12/16/2021, 3:26 PMrusshwolf
12/16/2021, 4:50 PMMarkRS
12/16/2021, 4:55 PMrusshwolf
12/16/2021, 4:59 PMinterface CommonFoo
with AndroidFoo
and IosFoo
implementations. If you initialize in platform code you can pass a Context to Android but just reference it as CommonFoo
in common without knowing about Context.kpgalligan
12/16/2021, 5:19 PMfun startDogSDK(context: Context): DogSDK
, and for iOS it's fun startDogSDK():DogSDK
. Then we have an expect function that makes platform-specific factories, which will have access to that Context
. For example, to create a Settings
instance we need the Context
, so
single<Settings> {
val sp: SharedPreferences = get<Context>().getSharedPreferences("DogSDKSettings", Context.MODE_PRIVATE)
AndroidSettings(sp)
}
Archie
12/16/2021, 5:25 PMMultiplatformSettings
and if I understand it correctly this is exactly what you are doing in there as well. I am trying to copy your pattern 😄This is a common thing going back for years just on Android, in my view. You need that Context, but unless you do something like set a static global in Application.onCreate, getting the context is rough. So far, in general, I’d say the same issue isn’t as common on iOS.I totally agree with this.
kpgalligan
12/16/2021, 5:27 PMContext
that makes things weird. Anyway, need to publish some docs on our current "best practice" around this, but we see the same pattern over and over.russhwolf
12/16/2021, 5:28 PMArchie
12/16/2021, 5:29 PMSounds complicated, but it’s not so bad, and it’s not unbounded. It’s basically Android’s@kpgalligan Looking forward for the post. Thank you very much! ❤️that makes things weird. Anyway, need to publish some docs on our current “best practice” around this, but we see the same pattern over and over.Context
kpgalligan
12/16/2021, 5:30 PMrusshwolf
12/16/2021, 5:30 PMArchie
12/16/2021, 5:31 PMf you can think of it as a dependency injection problem and not a KMP-specific thing, it’s easier to see how the patterns you’re already used to from Android (or whatever else) can applyI am starting to realize that now.
// Android
val androidInstance = LibraryClass(context, appId)
// Ios
val iosInstance = LibraryClass(appId)
I guess this is a just a very small thing anyway but having to reference to “appId” twice just bugs me.russhwolf
12/16/2021, 5:54 PMget()
call instead. YMMV on whether that makes the dependency graph hard to follow. If you want to handroll it, you could have a common function like CustomDependencyContainer.getAppId()
that you delegate to. Probably not worth it for appId, but might be nice if it's some other more complex dependency that you don't want to define initialization logic for twice.Archie
12/16/2021, 6:03 PMIf you want to handroll it, you could have a common function likeThis is interesting but wouldn’t that just replace:that you delegate to. Probably not worth it for appId, but might be nice if it’s some other more complex dependency that you don’t want to define initialization logic for twice.CustomDependencyContainer.getAppId()
// Android
val androidInstance = LibraryClass(context, get<CustomDependencyContainer>.getAppId())
// Ios
val iosInstance = LibraryClass(get<CustomDependencyContainer>.getAppId())
Did I understand it wrong?russhwolf
12/16/2021, 6:04 PMget()
Archie
12/16/2021, 6:04 PMkpgalligan
12/16/2021, 6:29 PMwhere code is available on one platform but not on the otherExample?
Archie
12/16/2021, 6:43 PMsetUserNotificationCategories(...)
for iOS but doesn’t have this on the Android side.
There are other definitions as well other than this.kpgalligan
12/16/2021, 6:44 PMArchie
12/16/2021, 6:48 PMPaul Woitaschek
12/19/2021, 8:03 AMArchie
12/25/2021, 6:28 AM// Common
expect class SomeClass
// Android
actual typealias SomeClass = SomeClassAndroid
// iOS
actual typealias SomeClass = SomeClassIos
but the problem is that I cannot access the properties/functions in the SomeClass
when using it inside common…
// common
val someClass = SomeClass()
someClass.someFunction(...) // I don/t have this definition in common, ERROR!
I thought of two things to do.
1. Declare an interface SomeClassInterface
, declare the properties and functions in this interface and create a platform specific implementation wrapping the original class:
// common
interface SomeClassInterface {
fun someFunction(...)
}
// Android
class SomeAndroidWrapperClass : SomeClassInterface {
private val someClass = SomeClassAndroid()
override fun someFunction(...) {
someClass.someFunction(...)
}
}
// iOS
class SomeIosWrapperClass : SomeClassInterface {
private val someClass = SomeClassIos()
override fun someFunction(...) {
someClass.someFunction(...)
}
}
2. Another approach I thoguh of is to keep the original typealias and simply declare the functions/properties as expect extension functions
// Common
expect class SomeClass
expect fun SomeClass.someFunction(...)
// Android
actual typealias SomeClass = SomeClassAndroid
actual fun SomeClass.someFunction(...) = someFunction(...)
// iOS
actual typealias SomeClass = SomeClassIos
actual fun SomeClass.someFunction(...) = someFunction(...)
My Question is whether, there is a better approach than these two? or if there is none, which of the two approach would be better to go through with?
Thanks in advance guys. Really appreciate all the help.Paul Woitaschek
12/25/2021, 7:22 AMpublic expect class UUID
@Serializer(forClass = UUID::class)
public object UUIDSerializer : KSerializer<UUID> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("com.yazio.shared.uuid.UUIDSerializer", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: UUID) {
encoder.encodeString(value.value)
}
override fun deserialize(decoder: Decoder): UUID {
val stringValue = decoder.decodeString()
return uuid(stringValue)
}
}
/**
* @throws [kotlin.IllegalArgumentException] if the value is no valid uuid
*/
public expect fun uuid(value: String): UUID
public expect val UUID.value: String
public expect fun randomUUID(): UUID
public expect fun String.asUUIDorNull(): UUID?
public actual typealias UUID = java.util.UUID
public actual fun uuid(value: String): UUID {
return UUID.fromString(value)
}
public actual val UUID.value: String get() = toString()
public actual fun randomUUID(): UUID {
return UUID.randomUUID()
}
public actual fun String.asUUIDorNull(): UUID? {
return try {
uuid(this)
} catch (ignored: IllegalArgumentException) {
null
}
}
public actual data class UUID(val uuid: String) {
init {
freeze()
}
}
public actual fun uuid(value: String): UUID {
return requireNotNull(value.asUUIDorNull()) {
"Could not parse $value"
}
}
public actual val UUID.value: String get() = uuid
public actual fun randomUUID(): UUID {
return NSUUID().toUUID()
}
public actual fun String.asUUIDorNull(): UUID? {
return try {
NSUUID(this)
} catch (e: NullPointerException) {
// Kotlin does not support optional initializers.
null
}?.toUUID()
}
private fun NSUUID.toUUID(): UUID {
return UUID(UUIDString.lowercase())
}