I'm looking at the multiplatform sample on SqlDeli...
# multiplatform
t
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
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
Heh, you are storing Context in a top level property in the shared module 😀
k
your driver initialization has to be platform specific
j
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
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
yes, expect a function to return a SqlDriver
t
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
Context is required, yes.
r
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
That was also what I was gonna write just now
a
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
It does. There's a jdbc driver
👍 1
a
Thanks, good to know
k
i added platform-specific init methods, which create the driver and pass it along where it needs to go
a
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
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
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
Yes, I need everything in one module 😊
a
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
You mean clients would have to implement that abstraction?
r
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
Thanks.
r
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
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
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
i assume by content provider path, you mean they're putting one in their manifest just to init during app startup?
r
yeah
k
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
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
In my use-case, I don’t think users will ever be configuring something.