Hey guys :wave: - *Crash on iOS, only on TestFlig...
# compose-ios
r
Hey guys 👋 - Crash on iOS, only on TestFlight builds We have an iOS application that uses Compose multiplatform on a particular screen. Apparently our local builds are all working, but not TestFlight ones. They crash as soon as we try to show the
ComposeUIViewController
-> stack trace in the 🧵, as far as I can see, it happens on
kfun:androidx.compose.ui.util#trace
call. Is there any known issue on testflight builds? or anything special that it does that could hint at why this happens?
versions used: kotlin = 2.2.10 org.jetbrains.compose = 1.8.2
Copy code
6   libc++abi.dylib               	0x000000019aa92314 __cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*) + 88 (cxa_exception.cpp:152)
7   libc++abi.dylib               	0x000000019aa922bc __cxa_throw + 92 (cxa_exception.cpp:299)
8   TerminalSharedKMP             	0x0000000105468a2c ExceptionObjHolder::Throw(ObjHeader*) + 52
9   TerminalSharedKMP             	0x00000001054689f8 ThrowException + 12
10  TerminalSharedKMP             	0x000000010430d98c kfun:androidx.compose.ui.util#trace(kotlin.String;kotlin.Function0<0:0>){0§<kotlin.Any?>}0:0 + 1084 (Trace.uikit.kt:44)
11  TerminalSharedKMP             	0x000000010430d98c <inlined-out:postponeInvalidation> + 1084 (BaseComposeScene.skiko.kt:87)
12  TerminalSharedKMP             	0x000000010430d98c kfun:androidx.compose.ui.scene.BaseComposeScene#setContent(kotlin.Function2<androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>){} + 3000 (BaseComposeScene.skiko.kt:138)
13  TerminalSharedKMP             	0x00000001043dbcc4 kfun:androidx.compose.ui.scene.ComposeScene#setContent(kotlin.Function2<androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>){}-trampoline + 156 (ComposeScene.skiko.kt:176)
14  TerminalSharedKMP             	0x00000001043dbcc4 kfun:androidx.compose.ui.scene.ComposeSceneMediator.ComposeSceneMediator$setContent$1.invoke#internal + 540 (ComposeSceneMediator.uikit.kt:526)
15  TerminalSharedKMP             	0x00000001043dbcc4 kfun:androidx.compose.ui.scene.ComposeSceneMediator.ComposeSceneMediator$setContent$1.$<bridge-DN>invoke(){}#internal + 604 (ComposeSceneMediator.uikit.kt:523)
16  TerminalSharedKMP             	0x00000001043ec754 kfun:kotlin.Function0#invoke(){}1:0-trampoline + 156 ([K][Suspend]Functions:1)
17  TerminalSharedKMP             	0x00000001043ec754 kfun:androidx.compose.ui.scene.UIKitTransparentContainerView.UIKitTransparentContainerView$runOnceOnAppeared$1.invoke#internal + 208 (UIKitTransparentContainerView.kt:42)
18  TerminalSharedKMP             	0x00000001043ec754 kfun:androidx.compose.ui.scene.UIKitTransparentContainerView.UIKitTransparentContainerView$runOnceOnAppeared$1.$<bridge-DN>invoke(){}#internal + 256 (UIKitTransparentContainerView.kt:41)
19  TerminalSharedKMP             	0x00000001043eb8d4 kfun:kotlin.Function0#invoke(){}1:0-trampoline + 156 ([K][Suspend]Functions:1)
20  TerminalSharedKMP             	0x00000001043eb8d4 kfun:androidx.compose.ui.scene.UIKitTransparentContainerView.runOnAppearedIfEligible#internal + 1144 (UIKitTransparentContainerView.kt:56)
21  TerminalSharedKMP             	0x00000001043ebe0c kfun:androidx.compose.ui.scene.UIKitTransparentContainerView#objc:layoutSubviews + 500 (UIKitTransparentContainerView.kt:64)
22  TerminalSharedKMP             	0x0000000105437f00
a
Cannot say much from this stack trace. It crashes at the init stage of the Compose. Do you have other details about the crash? Also, could you please check if the crash present on different versions of Compose, like
1.9.1
or
1.10.0-alpha03
.
r
Hey @Andrei Salavei thank you so much for looking into it.
Yes, we will try updating the versions.
We have tried a simple demo of Compose coming from test flight build and that one worked, so it must be something unique we are doing in our actual production code.
I will update this thread with our findings 🙏
In the mean time @Andrei Salavei, we have something like this in the code:
Copy code
public class EposBridge(
    private val provider: FlutterKmpProvider, // ℹ️ YES THE APPLICATION IS FLUTTER AND HAS FLUTTER NATIVE PLUGIN HERE - NOT SURE IF IT IS RELEVANT INFO
) {
    private val scope: CoroutineScope by lazy { MainScope() }
    private var eposController: EposSdkController? = null

    public fun startEpos() {
        scope.launch {
            val storeId = provider.getCurrentStoreId() // ℹ️ SUSPEND FUNCTION CALL

            if (storeId.isEmpty()) {
                return@launch
            }

            if (storeId != CurrentStoreHolder.get()) { // ℹ️ SUSPEND FUNCTION CALL
                eposController = null
                CurrentStoreHolder.set(storeId) // ℹ️ SUSPEND FUNCTION CALL
            }

            val controller = getOrCreateEposController()
            controller.startEpos( // ℹ️ SHOWS MAIN COMPOSABLE - see below
                storeId = storeId,
                currency = provider.getCurrentCurrency().defaultCurrency
            )
        }
    }


    private suspend fun getOrCreateEposController(): EposSdkController {
        if (eposController == null) {
            eposController = getEposSdkController(
                isProdEnvironment = provider.isProdEnvironment(), // ℹ️ SUSPEND FUNCTION CALL
                // ... other parameters
            )
        }
        return eposController!!
    }
}
ℹ️ The above
startEpos
call will do something like this:
Copy code
internal actual fun platformStartEposSdk() {
    val controller = UIApplication.sharedApplication.keyWindow?.rootViewController
    val mainController = ComposeUIViewController {
        Column(modifier = Modifier.systemBarsPadding()) {
            EposMainComposable()
        }
    }
    mainController.modalPresentationStyle = UIModalPresentationFullScreen
    controller?.presentViewController(mainController, true, null)
}
a
Wow, that's cool! What I can spot here, that you're using flutter together with compose, and it might be an issue. The problem is that they both using skia, and it's a known issue that two skia libraries cannot work well in the single app. We're working to fix it, but the fix itself takes a lot of time. To check other assumptions, you can try replacing
EposMainComposable()
with some dummy, or just remove this line it completely. If the app still crashes, it most likely the case described above.
👀 1
r
But if this is the case, would it work on non test flight builds?
Notice also that we’re able to have working test flight build with a simpler “initialising” / main composable code
And finally, wasn’t iOS on flutter moving away from skia?
Yeah I confirmed with the team and iOS build should be using impeller now
a
I see, however the conflict may occur in other 3rd party libraries that both skia and impeller use. For debug purposes: • Try to run the app with dummy
EposMainComposable()
• Try to open View Controller with
EposMainComposable()
, but without running Flutter • You can also assemble started project with both, Compose and Flutter and try to present one from another. • Try using new version of Compose
Notice also that we’re able to have working test flight build with a simpler “initialising” / main composable code
Ok, acknowledged - we're keeping attention on every release
But if this is the case, would it work on non test flight builds?
That's unknown area - if it is the case, it's hard to predict how it can or cannot work.
👍 1
r
We are now building two builds: 1. Just update compose and kotlin versions to 1.9.1 and 2.2.20 respectively 2. Does that too ☝️ but also removes the EposMainComposable and replaces it with:
Copy code
LaunchedEffect(Unit) {
    logger.i("MainViewController composed first time")
}
Column(modifier = Modifier.systemBarsPadding()) {
    Text("Hello from iOS!")
}
thank you color 1
So second one, which updates versions and removes
EposMainComposable
, worked.
still building the first one.
but it is either version related (we're hoping 🤞 ) or it's something specific we're doing on
EposMainComposable
🤞 1
And yes, build 1 is crashing.
So it is something on EposMainComposable.
The issue right now is that it is taking a long time to build something we can test 😞 So checking small changes one by one will take forever.
a
Hm... do you have code that parses HTML or something connected with web/urls in the
EposMainComposable
?
Or something that force runs tasks in iOS run loop - that might be the issue.
r
Hm... do you have code that parses HTML or something connected with web/urls in the
EposMainComposable
?
I don't think so 🤔
Or something that force runs tasks in iOS run loop - that might be the issue.
Hmm, I'm not sure.. Do you have an example of what could do that?
you mean the main thread looper? I assume anything showing in the UI would do that?
or do you mean somethings would force the looper to run to completion?
(sorry, I probably miss knowledge here 😅)
Btw, we just were able to test a build that crashes as well where we removed almost all code too, almost like the one that worked, but instead of:
Copy code
LaunchedEffect(Unit) {
    logger.i("MainViewController composed first time")
}
Column(modifier = Modifier.systemBarsPadding()) {
    Text("Hello from iOS!")
}
We did:
Copy code
@Composable
internal fun EposMainComposable(
) {
    var counter by remember { mutableIntStateOf(0) }

    TeyaTheme {
        Scaffold(modifier = Modifier.statusBarsPadding()) { paddingValues ->
            Box(Modifier.padding(paddingValues).fillMaxSize()) {
                Column {
                    Text("Hi, this is EposMainComposable!")
                    Text("Counter $counter")
                    Row {
                        Button(
                            onClick = { counter++ }
                        ) {
                            Text("+1")
                        }
                        Button(
                            onClick = { counter-- }
                        ) {
                            Text("-1")
                        }
                    }
                }
            }
        }
    }
}
So now we are suspecting the
TeyaTheme
. It's in an old module that was used by native android app and was changed to KMP module. It still uses both material 2 and material 3 (probably not very common thing).
a
Hmm, I'm not sure.. Do you have an example of what could do that?
Usually it's something ios-specific, like usage if the
RunLoop
/
NSRunLoop
class, or access to other native API connected with dispatch quqe. They usually break the order of tasks execution and may ruin the app.
Apparently our local builds are all working, but not TestFlight ones
Sorry if the question is not correct,, however - did you try running
release
build locally on device?
So now we are suspecting the
TeyaTheme
.
Curious whats inside... you can send me full implementation in DM for the investigation if you want.
🙌 1
r
Sorry if the question is not correct,, however - did you try running
release
build locally on device?
Yes we are trying to reproduce locally by making our local builds as close to test flight ones as possible, but so far with no luck. All we have tried was not able to reproduce this crash, only test flight builds are crashing 🤷
sent dm, thanks 🙂
I wonder if it could be related to resources? We are using compose resources. Is there any known issue of transient dependencies and compose resources?
Turns out the issue was that resources were not available at runtime 😧 We were able to solve it by manually including the compose-resources folder in XCode in Build phases -> Copy Bundle Resources. We still don't know if this is good enough solution, but it works as a workaround at least. To give a bit more context, we have a Git Repo producing a KMP library that is consumed by a gradle module on the Flutter project which in turn creates a XCFramework of it and consumes it via local cocoapods. Cocoapods podspec is pointing to the resources folder, and as we noticed cocoapods adds a build phase that should be copying those resources to the Bundle, but for some reason that is not working on the configuration we're using for distribution -we're still not sure why.
@Andrei Salavei thank you for taking the time to go through the errors and everything, you guys rock! 💪
a
@Rafael Costa, good investigation! I think as a follow-up: it would worth to configure KMP crashes collection with
setUnhandledExceptionHook()
or https://github.com/touchlab/CrashKiOS (or other 3rd party tool). In this case the cause of the crash will be much more discoverable.
1