Jetpack Glance widget open app on tap sometimes results in crashes and when it is not, backstack is empty

943 Views Asked by At

I know that it is not the best title in the world but let me explain the problem first,

I have implemented a Glance widget with some items in it and when you press them, the app should be opened and navigated to the specific screen given via deep-link in NavHost. However, sometimes the page navigated via deep-link works and sometimes not. When it is not, got an error something like this:

Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x4d in tid 2486 (DefaultDispatch), pid 2259

I believe this is a segmentation error as I pass the argument from widget to app by using Gson()toJson(itemData). However, I do not understand why this sometimes works as I use the same thing for each item in order to open the app and navigate to the details screen.

The other thing is when the app does not crash with that error, the app directly opens the details page rather than showing the splash screen first and navigating to the detail screen.

In the end, how can I solve that segmentation error during deep-linking and how can I prepopulate the backstack(or just first navigate to splash and then to detail screen)?

For some more information, there is a sample code:

// CODE IN GLANCE WIDGET
@Composable
private fun Item(
    model: ItemData,
) {
    val intent = Intent(
        Intent.ACTION_VIEW,
        "${URL}/${Screens.DetailScreen.passArgument(model.toDetailData())}".toUri() // pass model just converts detailData to string with Gson().toJson()
    )

    Text(
         text = model.title ?: "",
         modifier = GlanceModifier
             .clickable(
                 actionStartActivity(
                     intent
                 )
             )
         )
}
// CODE IN NAVHOST
NavHost(
    // navhost parameters such as route, controller, start destination
    // where start destination set to Splash Screen
){
    composable(/*routeOfSpashScreen*/){SplashScreen}
    .
    . // These dots are some sub-navGraphs
    .
    .
    detailScreenNavGraph()
}
// DETAIL SCREEN SUB-NAVGRAPH
fun NavGraphBuilder.detailScreenNavGraph(
    controller: NavHostController? = null // This is optional and does not relate to problem
) {
    navigation(
        startDestination = Screens.DetailScreen.route,
        route = DETAIL_SCREEN_ROUTE
    ) {
        composable(
            route = Screens.DetailScreen.route + "/{model}",
            arguments = listOf(
                navArgument(
                    name = "model"
                ) {
                    type = DetailDataNavType()
                },
            ),
            deepLinks = listOf(
                navDeepLink {
                    uriPattern = "${URL}/" + Screens.DetailScreen.route + "/{model}"
                }
            )
        ) {
            val model = it.arguments?.getParcelable<DetailData>("model")
            if (model != null) {
                DetailScreen(
                    model,
                    controller = controller ?: LocalNavigationManager.current
                )
            }
        }
    }
}

Any help or advice is appreciated.

1

There are 1 best solutions below

0
On

Since no one is answering and I have solved my problem, I believe a proper explanation is needed if someone gets stuck like me.

  1. What are receivers and how do they work?

Receivers are like singleton classes that control each widget, not only the widget called by. If you have to update the widget, you should have its proper GlanceId; currently, there is no official way to get that id. However, there is a hacky way such as:

// Add this to your callback and to your intent before broadcasting it 
// so you can access the widget's GlanceId
val id = glanceId.toString().filter { it.isDigit() }.toInt()

Since you have the id in your onReceive of your receiver, you can properly update your widget

  1. Why I am having a "Cannot marshall a parcel that contains binder objects" error?

This is because you are using LazyRow or LazyColumn and during drawing, as bitmaps contain smart components named binders which take extra space in your memory during drawing, you exceed the available limit given to the widget for memory. I have tried to reduce the size of the image and compress it as much as possible and it loads with the most disgusting images. However, if you use a Column instead of LazyColumn' you can easily render any image, at least this is what I have experienced.

  1. How are these questions relate to my problem?

As I was trying to click an item on the widget that lazily loaded, I believe that I tried to access a memory space that has been either collected or freed during or after the widget is drawn. The other possibility is there was no memory space for the app to be launched and during launch, the memory is cleaned and I tried to access the deleted memory part. Either way, these are just assumptions and feel free to correct me if I am wrong.

  1. What about the backstack part?

I totally changed the deeplink handling mechanism of the app such as:

if(appIsClosed)
    start splash -> navigate home -> open the desired page from here
    // pass the received intent parameters throughout navigation hierarchy
else
    handle as deep-link to home -> open desired page from here