You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose中使用CompositionLocal全局共享Assets配置遇Context获取问题,求可行替代方案

Solution for Sharing JSON Configuration Globally in Compose with Context Dependency

Great question! The core issue here is that the lambda used to initialize compositionLocalOf runs outside a Composable context—so LocalContext.current isn't accessible at that point. Let's walk through two reliable approaches to fix this and get your config working across your app:

Approach 1: Preload Configuration in Application (Simplest for Static Config)

Since your config is a static JSON in Assets, you can initialize it once when your app starts, then inject it into CompositionLocal.

Step 1: Update Your Configuration Class

Keep your existing Configuration object, but ensure we can initialize it safely early on:

object Configuration {
    lateinit var branding: Branding

    fun init(context: Context) {
        val gson = GsonBuilder().create()
        // Use `use` to auto-close the input stream
        context.assets.open("branding.json").use { inputStream ->
            branding = gson.fromJson(InputStreamReader(inputStream), Branding::class.java)
        }
    }
}

Step 2: Initialize in Your Application Class

Load the config when your app launches (this runs before any Composables are created):

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        Configuration.init(this)
    }
}

Don't forget to register this MyApp class in your AndroidManifest.xml.

Step 3: Provide the Config via CompositionLocal

Define your CompositionLocal and provide it at the root of your Compose tree:

// Define the CompositionLocal (no default init here—we'll provide it explicitly)
val LocalAppConfiguration = compositionLocalOf<Configuration> {
    error("LocalAppConfiguration must be provided via CompositionLocalProvider")
}

// In your Activity's setContent:
setContent {
    CompositionLocalProvider(LocalAppConfiguration provides Configuration) {
        // Your app's root composable here
        MyMainScreen()
    }
}

Step 4: Use the Config Anywhere in Compose

Now you can access the config in any child composable:

@Composable
fun MyMainScreen() {
    val config = LocalAppConfiguration.current
    Text(text = config.branding.appName)
}

Approach 2: Lazy-Load in Composable (For Dynamic/Async Loading)

If you prefer to load the config within the Compose lifecycle (e.g., for dynamic updates or async loading), use a state holder with remember.

Step 1: Create a State Holder Class

This class manages the config state and handles loading:

class AppConfigState(private val context: Context) {
    // Use mutableStateOf to trigger recompositions when the config loads
    var branding by mutableStateOf<Branding?>(null)
        private set

    init {
        // Load sync (safe for assets; use async if loading from network)
        loadConfig()
    }

    private fun loadConfig() {
        val gson = GsonBuilder().create()
        context.assets.open("branding.json").use { inputStream ->
            branding = gson.fromJson(InputStreamReader(inputStream), Branding::class.java)
        }
    }

    // For async loading (e.g., network), use this suspend function instead:
    private suspend fun loadConfigAsync() {
        withContext(Dispatchers.IO) {
            val gson = GsonBuilder().create()
            context.assets.open("branding.json").use { inputStream ->
                val loadedBranding = gson.fromJson(InputStreamReader(inputStream), Branding::class.java)
                withContext(Dispatchers.Main) {
                    branding = loadedBranding
                }
            }
        }
    }
}

Step 2: Provide the State via CompositionLocal

Initialize the state in your root composable and provide it:

val LocalAppConfigState = compositionLocalOf<AppConfigState> {
    error("LocalAppConfigState must be provided")
}

setContent {
    val context = LocalContext.current
    // Use remember to retain the state across recompositions
    val configState = remember { AppConfigState(context) }

    // For async loading, add a LaunchedEffect to trigger the suspend function:
    // LaunchedEffect(Unit) { configState.loadConfigAsync() }

    CompositionLocalProvider(LocalAppConfigState provides configState) {
        MyMainScreen()
    }
}

Step 3: Use the Config in Composables

Handle the nullable state (since loading might take a moment):

@Composable
fun MyMainScreen() {
    val configState = LocalAppConfigState.current
    configState.branding?.let { branding ->
        Text(text = branding.appName)
    } ?: run {
        // Show a loading indicator while config loads
        CircularProgressIndicator()
    }
}

内容的提问来源于stack exchange,提问作者Thomas Cirksena

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 01:49:09