Working on understanding the overall setup for KMM...
# multiplatform
r
Working on understanding the overall setup for KMM, currently working on the Android portion. Two questions I have (which I believe are related): • What's the difference between the
/androidApp
portion and the
/shared/src/androidMain
? • Is it possible for an
expect
declaration in the shared code (
/shared/src/commonMain
) to find its
actual
implementation in the
/androidApp
portion?
n
/shared/src/androidMain
is the android-specific part of your shared module. This module will be provided as a dependency to your android application (
/androidApp
). You can see the shared module as a library or an SDK that your application relies on.
👍 1
expect / actual
are resolved at compile time and must be in the same module, so the answer to your question is no. However you can declare interface in your shared module that you implement in the application
👍 1
r
So is there a "rule of thumb" about what platform-specific things are put into the shared module using
actual
and which are put into the android application itself?
a
As far as I know, people usually put platform specific code using 'expect/actual'. For example getting currenTime. Android and iOS have their own API of getting it. So you declare an expect fun getCurrentTime() in shared, and compiler force you to implement the "actual" portion of this function on each of yours build targets (iOS/Android/js). As a result you end up using platform specific code in shared module without knowing what platform you are. Similar result can be achieved with interfaces, with main difference that compiler would not force you to implement the interface on per build target level. But using interface provides a slightly better testing.
r
That doesn't answer my question, though. Which platform-specific things are usually done with expect/actual, vs which platform-specific things are usually done with the interface.
a
While this also doesn't answer your question, expect/actual can be used for functions as well as classes, that can't be done with interfaces. I think it's mostly a matter of personal preference which one you pick.
p
So is there a “rule of thumb” about what platform-specific things are put into the shared module using 
actual
 and which are put into the android application itself?
expect / actual
is useful when you want to use platform specific functionality in your shared code. So when you have some feature that you need on both platforms and it depends on some platform specific functionality, that’s a prime candidate for
expect / actual
. So for example if you want to build some really advanced caching logic for both platforms you’d probably want to share the code, therefore it wants to live in
shared
module. The caching module needs a K/V store? Oh, we have that on both platforms (SharedPreferences & UserDefaults), let’s wrap that those few function calls in
expect / actual
and we can put everything in
shared
. Cool! If you have something that you only do on android with no iOS counterpart or some feature where 90% of the code would live in the
expect / actual
anyways, that’s probably a candidate for the
androidApp
module. I would not blow up the
shared
module with functionality that you don’t share between platforms.
👍 1
🙌 1
In short: If a feature you want to share between platforms needs platform specific api, you
expect / actual
, otherwise you don’t.
And on the topic of
expect / actual
vs
interface
: I’d always go with
expect / actual
until there’s a good reason not to. It’s more explicit and you’re more “safe” since the compiler helps you out.
a
@Paul Weber I remember having issues to properly test classes signed with expect/actual when they are dependency of my test subject. Except of that, completely agree with you
n
Something really powerful you can do with
expect / actual
is allow usage of native types instead of Kotlin types in your shared code. For example: Common
Copy code
public expect class Timestamp
internal expect fun Timestamp.toInstant(): Instant
Android
Copy code
public actual typealias Timestamp = Instant
internal actual fun Timestamp.toInstant(): Instant = this
iOS
Copy code
public actual typealias Timestamp = NSDate
internal actual fun Timestamp.toInstant(): Instant = TODO()
The
toInstant()
extension function allows shared code to use
Instant
internally, while iOS is using
NSDate
. This makes usage of your library more native-like, which can be important when providing the library to 3rd parties
💡 1