Compose Multiplatform UI library for WebAssembly/JS target - declarative framework for sharing UIs across multiple platforms with Kotlin.
—
Resource management in Compose Multiplatform for WASM/JS provides an efficient system for loading and managing application assets including images, strings, fonts, and other resources. The system is optimized for web deployment with asynchronous loading, caching, and web-specific optimizations.
Essential function for setting up resource loading in WASM applications.
@OptIn(ExperimentalResourceApi::class)
fun configureWebResources(configure: WebResourcesConfiguration.() -> Unit)Basic Setup:
@OptIn(ExperimentalResourceApi::class)
fun main() {
configureWebResources {
resourcePathMapping { path -> "./$path" }
}
CanvasBasedWindow("MyApp") {
App()
}
}Advanced Configuration:
@OptIn(ExperimentalResourceApi::class)
fun main() {
configureWebResources {
resourcePathMapping { path ->
when {
path.startsWith("images/") -> "./assets/$path"
path.startsWith("fonts/") -> "./static/fonts/${path.removePrefix("fonts/")}"
else -> "./$path"
}
}
}
}Configuration object for customizing resource loading behavior.
class WebResourcesConfiguration {
fun resourcePathMapping(mapping: (String) -> String)
}Load image resources asynchronously.
@Composable
@OptIn(ExperimentalResourceApi::class)
fun painterResource(resource: DrawableResource): PainterBasic Usage:
@OptIn(ExperimentalResourceApi::class)
@Composable
fun ImageExample() {
Image(
painter = painterResource(Res.drawable.logo),
contentDescription = "App logo",
modifier = Modifier.size(100.dp)
)
}Resource Definition:
// Generated resource accessor
object Res {
object drawable {
val logo: DrawableResource = DrawableResource("drawable/logo.png")
val background: DrawableResource = DrawableResource("drawable/background.jpg")
val icon_user: DrawableResource = DrawableResource("drawable/icon_user.svg")
}
}Handle loading, success, and error states:
@OptIn(ExperimentalResourceApi::class)
@Composable
fun AsyncImageExample() {
var imageState by remember { mutableStateOf<ImageLoadState>(ImageLoadState.Loading) }
Box(modifier = Modifier.size(200.dp)) {
when (imageState) {
is ImageLoadState.Loading -> {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
}
is ImageLoadState.Success -> {
Image(
painter = painterResource(Res.drawable.photo),
contentDescription = "Photo",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
}
is ImageLoadState.Error -> {
Icon(
imageVector = Icons.Default.Error,
contentDescription = "Error loading image",
modifier = Modifier.align(Alignment.Center),
tint = MaterialTheme.colors.error
)
}
}
}
}
sealed class ImageLoadState {
object Loading : ImageLoadState()
object Success : ImageLoadState()
data class Error(val exception: Throwable) : ImageLoadState()
}Load localized string resources.
@Composable
@OptIn(ExperimentalResourceApi::class)
fun stringResource(resource: StringResource): StringUsage:
@OptIn(ExperimentalResourceApi::class)
@Composable
fun LocalizedText() {
Text(
text = stringResource(Res.string.welcome_message),
style = MaterialTheme.typography.h4
)
}String Resource Definition:
// Generated resource accessor
object Res {
object string {
val welcome_message: StringResource = StringResource("string/welcome_message")
val button_continue: StringResource = StringResource("string/button_continue")
val error_network: StringResource = StringResource("string/error_network")
}
}Handle strings with parameters:
@OptIn(ExperimentalResourceApi::class)
@Composable
fun ParameterizedString(userName: String, count: Int) {
Text(
text = stringResource(
Res.string.welcome_user,
userName,
count
)
)
}@OptIn(ExperimentalResourceApi::class)
@Composable
fun PluralString(itemCount: Int) {
Text(
text = pluralStringResource(
Res.plurals.items_count,
itemCount,
itemCount
)
)
}Load custom fonts for typography.
@Composable
@OptIn(ExperimentalResourceApi::class)
fun fontResource(resource: FontResource): androidx.compose.ui.text.font.FontUsage:
@OptIn(ExperimentalResourceApi::class)
@Composable
fun CustomFontExample() {
val customFont = FontFamily(
fontResource(Res.font.roboto_regular),
fontResource(Res.font.roboto_bold)
)
Text(
text = "Custom Font Text",
fontFamily = customFont,
fontWeight = FontWeight.Bold
)
}Font Family Creation:
@OptIn(ExperimentalResourceApi::class)
val AppFontFamily = FontFamily(
Font(
resource = Res.font.opensans_light,
weight = FontWeight.Light
),
Font(
resource = Res.font.opensans_regular,
weight = FontWeight.Normal
),
Font(
resource = Res.font.opensans_medium,
weight = FontWeight.Medium
),
Font(
resource = Res.font.opensans_bold,
weight = FontWeight.Bold
)
)Load arbitrary resource files:
@OptIn(ExperimentalResourceApi::class)
suspend fun readResourceBytes(resource: Resource): ByteArray
@OptIn(ExperimentalResourceApi::class)
suspend fun readResourceText(resource: Resource): StringUsage:
@OptIn(ExperimentalResourceApi::class)
@Composable
fun ConfigLoader() {
var config by remember { mutableStateOf<String?>(null) }
LaunchedEffect(Unit) {
config = readResourceText(Res.files.config_json)
}
config?.let { configText ->
// Parse and use configuration
val configData = Json.decodeFromString<Config>(configText)
ConfigDisplay(configData)
}
}src/commonMain/composeResources/
├── drawable/
│ ├── logo.png
│ ├── background.jpg
│ └── icons/
│ ├── user.svg
│ └── settings.png
├── font/
│ ├── roboto-regular.ttf
│ ├── roboto-bold.ttf
│ └── opensans.woff2
├── values/
│ ├── strings.xml
│ └── strings-es.xml
└── files/
├── config.json
└── data.csvNaming Conventions:
user_profile.pngicon_home.svg, icon_settings.svglogo_small.png, logo_large.pngbackground_login.jpg, button_primary.pngString Resources (values/strings.xml):
<resources>
<string name="app_name">My App</string>
<string name="welcome_message">Welcome to the app!</string>
<string name="button_continue">Continue</string>
</resources>Spanish Resources (values-es/strings.xml):
<resources>
<string name="app_name">Mi Aplicación</string>
<string name="welcome_message">¡Bienvenido a la aplicación!</string>
<string name="button_continue">Continuar</string>
</resources>@Composable
fun LocalizedApp() {
val currentLanguage = getCurrentLanguage()
// Resources automatically load based on system language
Text(stringResource(Res.string.welcome_message))
}
fun getCurrentLanguage(): String {
return kotlinx.browser.window.navigator.language
}Resources are automatically cached by the browser:
Critical Resources:
@OptIn(ExperimentalResourceApi::class)
@Composable
fun PreloadCriticalResources() {
LaunchedEffect(Unit) {
// Preload critical images
painterResource(Res.drawable.logo)
painterResource(Res.drawable.splash_background)
// Preload fonts
fontResource(Res.font.primary_font)
}
}Lazy Loading:
@OptIn(ExperimentalResourceApi::class)
@Composable
fun LazyImageGrid(images: List<ImageResource>) {
LazyColumn {
items(images) { imageResource ->
// Images load only when needed
AsyncImage(
resource = imageResource,
contentDescription = null,
modifier = Modifier.fillMaxWidth()
)
}
}
}@OptIn(ExperimentalResourceApi::class)
@Composable
fun RobustImageLoading() {
var hasError by remember { mutableStateOf(false) }
if (hasError) {
// Fallback content
Box(
modifier = Modifier.size(100.dp),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Default.BrokenImage,
contentDescription = "Image not available"
)
}
} else {
Image(
painter = painterResource(Res.drawable.user_photo),
contentDescription = "User photo",
modifier = Modifier.size(100.dp),
onError = { hasError = true }
)
}
}@OptIn(ExperimentalResourceApi::class)
@Composable
fun NetworkAwareResourceLoading() {
var isOnline by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
// Monitor network status
isOnline = checkNetworkStatus()
}
if (isOnline) {
Image(painterResource(Res.drawable.online_content))
} else {
// Show cached or offline content
Image(painterResource(Res.drawable.offline_placeholder))
Text("Offline mode")
}
}DisposableEffect for large resources@OptIn(ExperimentalResourceApi::class)
@Composable
fun LargeResourceManager() {
DisposableEffect(Unit) {
val largeResource = loadLargeResource()
onDispose {
// Cleanup if needed
largeResource.dispose()
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-compose-ui--ui-wasm-js