https://kotlinlang.org logo
#multiplatform
Title
# multiplatform
t

taso

04/10/2020, 1:59 PM
I'm looking at the multiplatform sample on SqlDelight. It only has the common code in the common lib. Platform specific (Android/iOS) drivers are added in top level app module. My question is that how would this work on a Multiplatform library. Let's say we have a library where the persistence is an implementation detail. Is it possible to keep it all in the "common" module and all users will automagically get the correct version?
j

John O'Reilly

04/10/2020, 2:04 PM
If I'm understanding question correctly then that's what's being done in following for example https://github.com/joreilly/PeopleInSpace
`app`module doesn't know anything about SQLDelight or in general how data is being persisted
a

Arkadii Ivanov

04/10/2020, 2:11 PM
Heh, you are storing Context in a top level property in the shared module 😀
k

Kris Wong

04/10/2020, 2:17 PM
your driver initialization has to be platform specific
j

John O'Reilly

04/10/2020, 2:22 PM
yes, but still contained within common module (using expect/actual mechanism)
re. Context, I started using that approach a good while back.....probably a bit hacky and def open to suggestions if cleaner way of doing this
a

Arkadii Ivanov

04/10/2020, 2:29 PM
Saving context in app's onCreate has a few cons: 1. The dependency on context is hidden, 2. Integration testing is much harder, 3. It may crash if used from a BroadcastReceiver, 4. Brakes hot reload
I always prefer injecting stuff from app
k

Kris Wong

04/10/2020, 2:32 PM
yes, expect a function to return a SqlDriver
t

taso

04/10/2020, 3:22 PM
Context is mandatory to instantiate the Android driver, right?
I wonder if I should expect the users to pass the
SqlDriver
?
Another question: Could you support Android and jvm at the same time?
a

Arkadii Ivanov

04/10/2020, 3:29 PM
Context is required, yes.
r

russhwolf

04/10/2020, 3:30 PM
Depending on what level of abstraction you're going for, you could have the user pass a context and generate driver from that. But then need to expose different API for Android then for other platforms.
You could also do the contentprovider hack where you just add a no-op contentprovider and use its context globally
t

taso

04/10/2020, 3:31 PM
That was also what I was gonna write just now
a

Arkadii Ivanov

04/10/2020, 3:31 PM
So would put database in a separate module and depend on this module and on a driver. Clients should have the database in an app scope.
If you depend only on context then you will have to store driver somewhere which is also not so good.
I'm not sure if SQLDelight supports plain JVM
r

russhwolf

04/10/2020, 3:34 PM
It does. There's a jdbc driver
👍 1
a

Arkadii Ivanov

04/10/2020, 3:36 PM
Thanks, good to know
k

Kris Wong

04/10/2020, 3:38 PM
i added platform-specific init methods, which create the driver and pass it along where it needs to go
a

Arkadii Ivanov

04/10/2020, 3:39 PM
So you have to call it before use. This works in most of the cases. However has same issues same as saving context in a static field.
Why not to use proper DI? If you need something, then depend on it.
t

taso

04/10/2020, 3:45 PM
In an app sure. That's what I would do. Library is different. I want to keep it as implementation detail.
2 reasons: - easier usage - if we want to migrate from SqlDelight to something else, I don't want users to deal with that
When you both have jvm and jvmAndroid in the same module, does the Gradle metadata automatically figure that out?
r

russhwolf

04/10/2020, 3:47 PM
There are scenarios where the IDE gets confused, but Gradle generally can handle it
Here's a sample that uses both, if it helps https://github.com/russhwolf/multiplatform-hello
Though maybe it's split across modules in a way that doesn't help you
t

taso

04/10/2020, 3:53 PM
Yes, I need everything in one module 😊
a

Arkadii Ivanov

04/10/2020, 3:53 PM
Then abstract from SQLDelight. Even better. Create your own interface and depend on it. Then SQLDelight will be implementation detail. Clients will use whatever they want. E.g. a shared module with SQLDelight database. Or Room on Android.
t

taso

04/10/2020, 3:53 PM
You mean clients would have to implement that abstraction?
r

russhwolf

04/10/2020, 3:54 PM
Actually, the multiplatforms settings sample does have all in one module if you want to look at that. https://github.com/russhwolf/multiplatform-settings/blob/master/sample/shared/build.gradle.kts
🎉 1
t

taso

04/10/2020, 3:54 PM
Thanks.
r

russhwolf

04/10/2020, 4:01 PM
re context, lifecycle, abstraction: I think it generally makes sense for a library to provide initializer methods that may be platform-specific. Can also add teardown methods if lifecycle is a concern. Then client apps can integrate it into whatever DI strategy they have. But ideally the initializer just exposes high-level stuff and not library implementation details. So you could take a context in there, and if there's concern about it being long-lived (which there might not be, if it's application context), then null it out in a teardown method and document that as best practice
t

taso

04/10/2020, 4:48 PM
That sounds like a better architecture but to be honest none of the common libraries are doing that including at least 4 from AndroidX & Firebase.
r

russhwolf

04/10/2020, 6:04 PM
yeah most of the google stuff is going the contentprovider path these days. As long as it's an application-level thing and there's no lifecycle concerns I think that's reasonable. It's a bit of a tricky balance between wanting things to be configurable (maybe a user only wants this thing in-memory for a certain application flow and wants to be able to clear it out later) and wanting it to be simple
k

Kris Wong

04/10/2020, 6:22 PM
i assume by content provider path, you mean they're putting one in their manifest just to init during app startup?
r

russhwolf

04/10/2020, 6:22 PM
yeah
k

Kris Wong

04/10/2020, 6:23 PM
fairly clever, but modules typically need some kind of configuration
i don't see it as a big win
not to mention it takes away control of initialization order
r

russhwolf

04/10/2020, 6:28 PM
You can do both. Have some default that works out-of-the-box and then extra config API for the people who need it. WorkManager does this, where you can pass dependencies to your workers by disabling the default content provider initializer and supplying your own instead.
t

taso

04/10/2020, 8:45 PM
In my use-case, I don’t think users will ever be configuring something.
4 Views