Hello guys, I would like to know how you guys orga...
# multiplatform
e
Hello guys, I would like to know how you guys organize your classes when implementing native logic for each platform. For example, in my case, I was porting my android application to compose multiplatform. I was having a
MainActivity
with several fragments, and I was for now using
AndroidView
to inflate those fragments. It means that I was actually keeping the fragments, with its functions for UI in it, and now I reached the point that I wanted to implement those sections on iOS. For that, I wanted to get ride of the fragments, and just create
Compose
functions that I can import directly on iOS. So I will keep the
MainActivity
to include the composables inside, and a
UIViewController
that will do the same on iOS. However I am realizing now that for some screens, I will need to use native code for some UI interactions, like the login in FB and Google dialogs, opening the mail client, etc. If for example I have a kotlin file called
SplashScreen.kt
where I have the
@Composable
functions, where can I define those platform specific functions? One alternative would be to implement them in the Activity/UIViewController and pass them as arguments to the composable. I think that I should use
expect/actual
though, but I don鈥檛 know what is a good practice for that in terms of where I should put those
expect
functions. Thank you in advance!
m
I would suggest to start by applying basic, manual DI to decouple that native code from the composables. Turn those functions into interfaces and make them work. Then, move the interfaces to the common code. Then, add an object to hold the interface implementation, but in the common code. At this point, android should still work, the composables from common should be able to call those functions, and the platform implementation for said functions would be fulfilled, manually, at some point within the android sourceset. So, you can start implementing the interfaces in the iOS sourceset, and work out any difference between them. Once iOS is functional, you will have a proper, stable contract, and you would also worked out stuff like "how to import the facebook iOS SDK", and you can move from interfaces to expect/actual declarations, instead of jumping right in without a proper understanding on how to distribute and link your common and platform code.
馃檶 1
e
@McEna What I don鈥檛 know is where to place those files. I am already familiar with the
expect/actual
approach, but I understand that can not put the
expect
functions inside the
Screen
file, dou you know if there is any convention or standard about where to place them? In the
ui
directory together with the screen itself? SHould it be called something like
Screen_Impl
or something similar?
m
expect/actual are akin to visibility modifiers (private, public, etc). So, they can be applied to functions, and to classes. So, you can have properties of types declared as
expect
inside a class from the common sourceset
A mindset that helped me was to consider KMP as "platform-level dependency injection". Usually, we design our code so we can hot-swap some parts of via interfaces or subclasses. KMP is the same, but it allows you do it across several sourcesets, which work like flavors. So, you just have to apply patterns related to DI, like composition
馃挴 1
If you were operating within an android project, without the
expect/actual
modifiers, and you had several submodules consuming a particular body of code which in turns needs to change between flavors/build types/variants, how would you shape the code so you can do that?
you would wrap the references to the shared code with a signature, so you can refer to it using a type, then you pass around objects of that type. It is the same with KMP. It is just that you have a modifier that marks the function/class as one that must be redefined within each active sourceset, like the
interface
declaration, but platform-wide.
I'm not aware of any conventions about where to put the
actual
implementations. Personally, I keep them as part of each layer. So, if i have some
ui
that needs redefining, I will have the common part in the
ui
package from the common sourceset, and the same package for the implementation in the platform sourcesets.
For example: I want to display a list where each cell contains an image from the internet, using compose multiplatform. In order to learn a bit around, I want to avoid using Coil or any other library. So, how to shape the code so I can inject a platform specific function within a composable?
Notice that you could remove the expect/actual declaration, and this could still work across different sourcesets.
PlatformBoundImageLoader
could be an
interface
instead of an
expect
class, and you would have to remember to implement and inject the right instance across every sourceset. Instead of this, you would pass an specific implementation of
PlatformBoundImageLoader
(in fact, that's the initial implementation of the image loading process).
Finally, we add the android and iOS implementations.
Now, you can notice some parts of the code where implemented without using
expect/actual
, so they are a bit verbose and convoluted, but at this point, you know how to inject platform code inside common code, and you can start refactoring those parts (which is what I started to do, hence the expect/actual declarations).
It is entirely possible you may need to have several implementations of a platform-specific code; for example, maybe your app is meant to use Firebase for google play and whatever Huawei uses for app gallery, and production/debug version for Apple's app store. In those cases, you would likely not use
expect/actual
, but rather regular interfaces. Same thing when structuring the rest of your code. Disassemble the problem as usual, then use
expect/actual
if it makes sense, and at the level it does, either for functions, or classes.
e
Thank you very much for all the detailed explanation! 馃槃