androidkotlinandroid-jetpack-composeandroid-widgetandroid-jetpack

How to host and draw installed app widgets in a compose app


I am making an android launcher using jetpack compose and want to add the functionality to use all the installed app widgets on home screen but I just can't find good sources. Can anyone point me in the right direction.

An article on medium and this page is the best reference i could find but even they aren't explaining it completely and I find many errors while following their method.

I also asked Chat GPT to write the code to draw all the installed app widgets but it also doesn't work.

here is the GPT code

class MainActivity : ComponentActivity() {

    private lateinit var appWidgetHost: AppWidgetHost
    private lateinit var appWidgetManager: AppWidgetManager
    private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        appWidgetHost = AppWidgetHost(this, APP_WIDGET_HOST_ID)
        appWidgetManager = AppWidgetManager.getInstance(this)

        requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
            if (isGranted) {
                // Permission granted, continue with widget setup
                println("permission")
                setupWidgets()
            } else {
                setContent{
                    WidgetsScreenExampleTheme {
                        Surface(
                            modifier = Modifier.fillMaxSize(),
                            color = MaterialTheme.colorScheme.background
                        ) {
                            println("no permission")
                            Text(
                                text = "permission not found",
                                style = TextStyle(color = Color.Black, fontSize = TextUnit(10f, TextUnitType.Sp))
                            )

                        }
                    }
                }
            }
        }
        checkAndRequestWidgetPermission()
    }

    private fun checkAndRequestWidgetPermission() {
        val permission = "android.permission.BIND_APPWIDGET"
        when {
            ContextCompat.checkSelfPermission(
                this,
                permission
            ) == PackageManager.PERMISSION_GRANTED -> {
                // Permission already granted, continue with widget setup
                setupWidgets()
            }
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
                // Permission not granted, request it from the user
                requestPermissionLauncher.launch(permission)
            }
            else -> {
                // For versions below Android M, permission is granted at installation time
                // Continue with widget setup
                setupWidgets()
            }
        }
    }

    private fun setupWidgets() {
        // Continue with widget setup here
        setContent {
            WidgetsScreenExampleTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    WidgetsView(
                        Modifier
                            .fillMaxSize(),
                        appWidgetHost,
                        appWidgetManager,
                        APP_WIDGET_HOST_ID
                    )
                }
            }
        }
    }

    companion object {
        const val APP_WIDGET_HOST_ID = 1
    }
}
@Composable
fun AppWidgetHostView(
    appWidgetManager: AppWidgetManager,
    appWidgetHost: AppWidgetHost,
    appWidgetId: Int,
    modifier: Modifier = Modifier
) {
    val appWidgetHostView = appWidgetHost.createView(
        LocalContext.current,
        appWidgetId,
        appWidgetManager.getAppWidgetInfo(appWidgetId)
    )

    DisposableEffect(appWidgetHostView) {
        onDispose {
            appWidgetHost.deleteAppWidgetId(appWidgetId)
        }
    }

    // Measure the app widget size
    appWidgetHostView.measure(
        View.MeasureSpec.makeMeasureSpec(100.dp.value.toInt(), View.MeasureSpec.EXACTLY),
        View.MeasureSpec.makeMeasureSpec(100.dp.value.toInt(), View.MeasureSpec.EXACTLY)
    )

    // Layout the app widget
    appWidgetHostView.layout(
        0,
        0,
        appWidgetHostView.measuredWidth,
        appWidgetHostView.measuredHeight
    )

    // Draw the app widget
    AndroidView(
        factory = { appWidgetHostView },
        modifier = modifier
    )
}
@Composable
fun AppWidgetListItem(appWidget: AppWidget, appWidgetHostId: Int) {
    val context = LocalContext.current
    val appWidgetHost = AppWidgetHost(context, appWidgetHostId)
    val appWidgetManager = AppWidgetManager.getInstance(context)

    val (isExpanded, setExpanded) = remember { mutableStateOf(false) }
//    val remoteViews = appWidgetManager.getAppWidgetViews(appWidget.appWidgetId)

    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
            .background(MaterialTheme.colorScheme.background)
            .clickable { setExpanded(!isExpanded) }
    ) {
        Text(text = appWidget.appWidgetInfo.label)

        if (isExpanded) {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(top = 8.dp)
            ) {
                AppWidgetHostView(
                    appWidgetManager = appWidgetManager,
                    appWidgetHost = appWidgetHost,
                    appWidgetId = appWidget.appWidgetId,
                    modifier = Modifier.fillMaxWidth()
                )
            }
        }
    }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun WidgetsView(
    modifier: Modifier = Modifier,
    appWidgetHost: AppWidgetHost,
    appWidgetManager: AppWidgetManager,
    appWidgetHostId: Int
) {
    val context = LocalContext.current

    val appWidgetIds = appWidgetHost.appWidgetIds
    val appWidgets = appWidgetIds.map { appWidgetId ->
        val appWidgetInfo = appWidgetManager.getAppWidgetInfo(appWidgetId)
        if (appWidgetInfo != null) {
            AppWidget(appWidgetId, appWidgetInfo)
        } else {
            null
        }
    }.mapNotNull { it }

    Column {
        TopAppBar(
            title = {
                Text(text = "Installed Widgets")
            },
            actions = {
                IconButton(onClick = { /* Handle settings action */ }) {
                    Icon(imageVector = Icons.Default.Star, contentDescription = null)
                }
            }
        )

        LazyColumn {
            items(appWidgets.size) { i ->
                AppWidgetListItem(appWidgets[i], appWidgetHostId)
            }
        }
    }
}

If anyone has any feedback or pointer to a good resource, then please provide it.


Solution

  • Thank you for all the helpful comments.

    Yes This works fine. I just wanted to do it entirely inside kotlin as I didn't want to use XML. But I guess It is required according to my understanding.