I currently have a very small app thats completely...
# compose-ios
c
I currently have a very small app thats completely kmp + compose. I want to launch a web browser on button press. In Android I need a context and I'm kinda lost what the best way to do that is that will be compatible with ios as well. I'm still fairly new to kmp and kmp compose. I've read about it for a while and Ive seen the stuff about expect/actual and how sometimes it's overused and you can instead just use interfaces. What's the smallest amount of work I need to do to just launch an external intent?
FWIW, I tried looking up a few examples how to do this, and most of them basically had me trying to use expect/actual. So if I tried something like
Copy code
expect fun launchExternalBrowser(url: String)
but my issue is that in my android code I need a context. and I can't seem pass that into the method along with the url.
feel like this is a pretty simple case and feeling stumped. I kinda wish there was a way to have expect/actual with different method signatures.
even though i guess thats a crazy idea?
j
that's basically describing an interface with two implementations
the implementation constructors are basically a form of currying additional arguments to provide a unified signature through the interface
(actually maybe it's partial application. i'm never clear on which is which.)
anyway, interface is the way to go
c
thanks. idk y i keep reaching for expect actual. let me see if i can actually get this to work. when you explained it your way... it makes sense. lol.
s
In Swift it’s then
Copy code
func openInBrowser(url: String) {

        #if os(iOS)

            UIApplication.shared.open(URL(string: url)!)

        #endif

        #if os(macOS)

            NSWorkspace.shared.open(URL(string: url)!)

        #endif
    }
I did not try, but it must be possible to do that in iosMain Kotlin code.
I feel too that Androids need for the context object often hinders nice uniform implementations 🙄
Would indeed be nice to access it as global state for the current app.
j
Going to hard disagree there. An interface means you can trivially fake it for things like testing or sample apps where you don't actually want to touch a real integration with the enclosing system.
s
Yes. Strong point. In my case it’s an interface for that exact reason that I want to check with an mock if openInBrowser() was called with the correct URL.
c
Alright. In my shared/commonMain I have
Copy code
interface BrowserLauncher{
  fun launch(url: String)
}

@Composable
fun HomeContent(browserLauncher: BrowserLauncher) {
  Button(onClick = {
    browserLauncher.launch("<https://google.com>")
  }) {
    Text("Lunch Browser")
  }
}
and then I guess I need to add the interface implementations in my android app module and then again in iOSApp module?
2
j
Yep!
c
Awesome! I just got the Android piece working! Going to give iOS a shot now My thoughts: 1. not sure why shared/androidMain and shared/iosMain exist. My impl had to be in my android app module. so just wondering if I could/should blow both of those away? 2. That was wayyy more "manual" dependency injection than I thought I'd have to do. almost feel like there should be an easier way... but I guess maybe my App composable will just have a ton of OS specific implementations of args being passed in.
NOICE
Got the iOS side working too!
That's wasn't so bad. The xcode side of the equation leaves a lot to be desired.
but maybe im just a swift/xcode noob
Interfaces ftw. im now not sure why expect/actual exists. lol
1
p
Expect/actual make it short for classes that are not tied to a platform lifecycle specific dependency, not like the case of context in this case. Classes that you can create in the global scope, for instance a class with no constructor args. In such a case, you don't need to wait for a platform event to get an instance to pass to compose. You just write the
actual
and the compiler generates all the language bindings. I think more often we use instances provided by the platform than global classes. A classic example is Android Context needed for almost everything, so that gives the interface injection a lot of usability.
m
I am wondering why you are not just using LocalUriHandler. It is made for that purpose and can be used directly from common code. It’s just two lines of code. Get the local URI handler once like this:
Copy code
val localUriHandler = LocalUriHandler.current
Then use it like this
Copy code
localUriHandler.openUri("https://…")
to open your URL.
👀 2
v
Copy code
not sure why shared/androidMain and shared/iosMain exist. My impl had to be in my android app module. so just wondering if I could/should blow both of those away?
Your impl can be there and there. But of course it is preferable to have it in the targetMain rather then in the app module. Because that way your KMP module is independent. As for the start question - sure you can create objects to which you delegate doing some stuff (opening different apps). But well, if you need context in some particular actual function for android you can always simply get it from DI.
r
not sure why shared/androidMain and shared/iosMain exist. My impl had to be in my android app module. so just wondering if I could/should blow both of those away?
Yeah I think the idea here is that your shared module can be re-used across multiple android/ios apps. Think about your shared code as a library that exports code for each platform. The consumer just imports the dependency in common main (or directly in the case of android/ios apps) and automatically gets all the common as well as platform specific code.
👍 1
Interfaces ftw. im now not sure why expect/actual exists. lol
The other difference between expect/actual and interfaces is that expect/actual is compile-time binding. This can have perf implications, though the slower dynamic binding of interfaces is not an issue for most use cases.
v
I think expect/actual exists so you you are not limited only to OOP. Kotlin is not only OOP language.
c
very interesting discussion everyone. i think overttime i will learn what makes more sense to put in which folders. I want to also try@Michael Paus solution. But for now I created the interface in shared/commonMain and then in top level androidApp module and iOSApp module I wrote that specific impls for each platform. That felt like magic!
🎉 1
p
@Colton Idle can you share the code with us plz