Skip to content

Commit

Permalink
update: new resources API changes (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
danil-pavlov authored Feb 28, 2024
1 parent b599914 commit b8e81c7
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 71 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fun App() {
}
AnimatedVisibility(showContent) {
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Image(painterResource("compose-multiplatform.xml"), null)
Image(painterResource(Res.drawable.compose_multiplatform), null)
Text("Compose: $greeting")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ To use this library:
}
AnimatedVisibility(showContent) {
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Image(painterResource("compose-multiplatform.xml"), null)
Image(painterResource(Res.drawable.compose_multiplatform), null)
Text("Compose: $greeting")
}
}
Expand All @@ -100,6 +100,10 @@ and desktop:
![First Compose Multiplatform app on desktop](first-compose-project-on-desktop-2.png){width=400}
> You can find this state of the project in our [GitHub repository](https://github.com/kotlin-hands-on/get-started-with-cm/tree/main/ComposeDemoStage1).
>
{type="tip"}
## Next step
In the next part of the tutorial, you'll learn new Compose Multiplatform concepts and create your own application from
Expand Down
89 changes: 49 additions & 40 deletions topics/compose-onboard/compose-multiplatform-new-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,24 @@ To get started, implement a new `App` composable:
composable:

```kotlin
@Composable
fun App() {
MaterialTheme {
var timeAtLocation by remember { mutableStateOf("No location selected") }
Column {
Text(timeAtLocation)
Button(onClick = { timeAtLocation = "13:30" }) {
Text("Show Time At Location")
}
}
}
}
```

* The layout is a column containing two composables. The first is a `Text` composable, and the second is a `Button`.
* The two composables are linked by a single shared state, namely the `timeAtLocation` property. The `Text`
composable is an observer of this state.
* The `Button` composable changes the state using the `onClick` event handler.
@Composable
fun App() {
MaterialTheme {
var timeAtLocation by remember { mutableStateOf("No location selected") }
Column {
Text(timeAtLocation)
Button(onClick = { timeAtLocation = "13:30" }) {
Text("Show Time At Location")
}
}
}
}
```

* The layout is a column containing two composables. The first is a `Text` composable, and the second is a `Button`.
* The two composables are linked by a single shared state, namely the `timeAtLocation` property. The `Text`
composable is an observer of this state.
* The `Button` composable changes the state using the `onClick` event handler.

2. Run the application on Android and iOS:

Expand Down Expand Up @@ -209,6 +209,10 @@ time message could be rendered more prominently.

![Improved style of the Compose Multiplatform app on desktop](first-compose-project-on-desktop-7.png){width=350}

> You can find this state of the project in our [GitHub repository](https://github.com/kotlin-hands-on/get-started-with-cm/tree/main/ComposeDemoStage2).
>
{type="tip"}

## Refactor the design

The application works, but it's susceptible to users' typos. For example, if the user enters "Franse" instead of "France",
Expand Down Expand Up @@ -290,12 +294,16 @@ list.
![The country list in the Compose Multiplatform app on desktop](first-compose-project-on-desktop-8.png){width=350}
> You can find this state of the project in our [GitHub repository](https://github.com/kotlin-hands-on/get-started-with-cm/tree/main/ComposeDemoStage3).
>
{type="tip"}
> You can further improve the design using a dependency injection framework, such as [Koin](https://insert-koin.io/),
> to build and inject the table of locations. If the data is stored externally,
> you can use the [Ktor](https://ktor.io/docs/create-client.html) library to fetch it over the network or
> the [SQLDelight](https://github.com/cashapp/sqldelight) library to fetch it from a database.
>
{type="tip"}
{type="note"}
## Introduce images
Expand All @@ -314,44 +322,41 @@ code to load and display them:
are [Japan](https://flagcdn.com/w320/jp.png), [France](https://flagcdn.com/w320/fr.png), [Mexico](https://flagcdn.com/w320/mx.png), [Indonesia](https://flagcdn.com/w320/id.png),
and [Egypt](https://flagcdn.com/w320/eg.png).
2. Move the images to `src/commonMain/resources` so that the same flags are available on all platforms:
2. Move the images to the `composeApp/src/commonMain/composeResources/drawable` directory so that the same flags are available on all platforms:
![Compose Multiplatform resources project structure](compose-resources-project-structure.png){width=300}
> Each platform-specific source set can also have its own resources folder. This allows you to use different images on different
> platforms, if needed.
>
{type="tip"}
3. Change the codebase to support images:
```kotlin
data class Country(val name: String, val zone: TimeZone, val image: String)
@OptIn(ExperimentalResourceApi::class)
data class Country(val name: String, val zone: TimeZone, val image: DrawableResource)
fun currentTimeAt(location: String, zone: TimeZone): String {
fun LocalTime.formatted() = "$hour:$minute:$second"
val time = Clock.System.now()
val localTime = time.toLocalDateTime(zone).time
return "The time in $location is ${localTime.formatted()}"
}
fun countries() = listOf(
Country("Japan", TimeZone.of("Asia/Tokyo"), "jp.png"),
Country("France", TimeZone.of("Europe/Paris"), "fr.png"),
Country("Mexico", TimeZone.of("America/Mexico_City"), "mx.png"),
Country("Indonesia", TimeZone.of("Asia/Jakarta"), "id.png"),
Country("Egypt", TimeZone.of("Africa/Cairo"), "eg.png")
@OptIn(ExperimentalResourceApi::class)
val defaultCountries = listOf(
Country("Japan", TimeZone.of("Asia/Tokyo"), Res.drawable.jp),
Country("France", TimeZone.of("Europe/Paris"), Res.drawable.fr),
Country("Mexico", TimeZone.of("America/Mexico_City"), Res.drawable.mx),
Country("Indonesia", TimeZone.of("Asia/Jakarta"), Res.drawable.id),
Country("Egypt", TimeZone.of("Africa/Cairo"), Res.drawable.eg)
)
@OptIn(ExperimentalResourceApi::class)
@Composable
fun App(countries: List<Country> = countries()) {
fun App(countries: List<Country> = defaultCountries) {
MaterialTheme {
var showCountries by remember { mutableStateOf(false) }
var timeAtLocation by remember { mutableStateOf("No location selected") }
Column(modifier = Modifier.padding(20.dp)) {
Text(
timeAtLocation,
Expand Down Expand Up @@ -383,7 +388,7 @@ code to load and display them:
}
}
}
Button(modifier = Modifier.padding(start = 20.dp, top = 10.dp),
onClick = { showCountries = !showCountries }) {
Text("Select Location")
Expand All @@ -405,6 +410,10 @@ code to load and display them:

![The country flags in the Compose Multiplatform app on desktop](first-compose-project-on-desktop-9.png){width=350}

> You can find this state of the project in our [GitHub repository](https://github.com/kotlin-hands-on/get-started-with-cm/tree/main/ComposeDemoStage4).
>
{type="tip"}

## What's next
We encourage you to explore multiplatform development further and try out more projects:
Expand Down
52 changes: 24 additions & 28 deletions topics/compose/compose-images-resources.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
[//]: # (title: Images and resources)

> This page describes the new revamped version of the resource library available since Compose Multiplatform 1.6.0-beta01.
>
{type="note"}

Compose Multiplatform provides a special library and a Gradle plugin support for accessing resources in common code
across all supported platforms. Resources are static content, such as images, fonts, and strings, that you can use from
Compose Multiplatform provides a special library and Gradle plugin support for accessing resources in common code
across all supported platforms. Resources are static content, such as images, fonts, and strings, which you can use from
your application.

> The library is [Experimental](supported-platforms.md#core-kotlin-multiplatform-technology-stability-levels).
Expand All @@ -15,8 +11,8 @@ your application.

When working with resources in Compose Multiplatform, consider the current conditions:

* Almost all resources are read synchronously in the caller thread. The only exceptions are raw files,
plus all the resources on the JS platform which are read asynchronously.
* Almost all resources are read synchronously in the caller thread. The only exceptions are raw files
and all of the resources on the JS platform that are read asynchronously.
* Reading big raw files, like long videos, as a stream is not supported yet. Use separate files on the user device and
read them with the file system API, for example, the [kotlinx-io](https://github.com/Kotlin/kotlinx-io) library.
* Multimodule projects are not supported yet. The JetBrains team is working on adding this functionality in future releases.
Expand Down Expand Up @@ -49,7 +45,7 @@ To access resources in your multiplatform projects:

![Compose resources project structure](compose-resources-structure.png){width=250}

3. Organize the `composeResources` directory structure following these rules:
3. Organize the `composeResources` directory structure according to these rules:

* Images should be in the `drawable` directory.
* Fonts should be in the `font` directory.
Expand All @@ -59,20 +55,20 @@ To access resources in your multiplatform projects:
## Qualifiers

Sometimes, the same resource should be presented in different ways depending on the environment, such as locale,
screen density, or an interface theme. For example, you might need to localize texts for different languages or adjust
screen density, or interface theme. For example, you might need to localize texts for different languages or adjust
images for the dark theme. For that, the library provides special qualifiers.

All resource types (except for raw files in the `files` directory) support qualifiers. Apply qualifiers to directory
names using a dash:
names using a hyphen:

![Qualifiers in compose resources](compose-resources-qualifiers.png){width=250}

The library supports (in the order of priority) [language](#language-and-regional-qualifiers), [theme](#theme-qualifier),
and [density](#density-qualifier) qualifiers.
The library supports (in the order of priority) the following qualifiers: [language](#language-and-regional-qualifiers),
[theme](#theme-qualifier), and [density](#density-qualifier).

* Different types of qualifiers can be applied together. For example, "drawable-en-rUS-mdpi-dark" is an image for the
English language in the United States region, suitable for 160 DPI screens in the dark theme.
* If a resource with the requested qualifier doesn't exist, a default resource without a qualifier is used instead.
* If a resource with the requested qualifier doesn't exist, the default resource without a qualifier is used instead.

### Language and regional qualifiers

Expand Down Expand Up @@ -100,28 +96,28 @@ You can use the following density qualifiers:
* "xxhdpi" − 480 DPI, 3x density
* "xxxhdpi" − 640dpi, 4x density

The resource is selected depending on a screen density defined in the system.
The resource is selected depending on the screen density defined in the system.

## Resource usage

After importing a project, a special `Res` class that provides access to resources is generated.
After importing a project, a special `Res` class is generated which provides access to resources.
To manually generate the `Res` class, run the `generateComposeResClass` Gradle task.

### Images

You can access drawable resources as simple images, rasterized images, and XML vectors:
You can access drawable resources as simple images, rasterized images, or XML vectors:

* To access drawable resources as `Painter` images, use the `painterResource()` function:

```kotlin
@Composable
fun painterResource(resource: DrawableResource): Painter {...}
```

The `painterResource()` function takes a resource path and returns a `Painter` value. The function works synchronously
on all targets, except for web. For the web target, it returns an empty `Painter` for the first recomposition that is
on all targets except for web. For the web target, it returns an empty `Painter` for the first recomposition that is
replaced with the loaded image in subsequent recompositions.

* `painterResource()` loads either a `BitmapPainter` for rasterized image formats, such as `.png`, `.jpg`, `.bmp`, `.webp`,
or a `VectorPainter` for the Android XML vector drawable format.
* XML vector drawables have the same format as [Android](https://developer.android.com/reference/android/graphics/drawable/VectorDrawable),
Expand Down Expand Up @@ -163,7 +159,7 @@ Store all string resources in the `strings.xml` file in the `composeResources/va

A static accessor is generated for each item in the `strings.xml` file.

To get string resources as a `String`:
To get string resources as a `String`, use the following code:

<tabs>
<tab title= "From composable code">
Expand All @@ -186,7 +182,7 @@ Text(stringResource(Res.string.app_name))
```

</tab>
<tab title= "From a non-composable code">
<tab title= "From non-composable code">

```kotlin
suspend fun getString(resource: StringResource): String
Expand All @@ -208,8 +204,8 @@ coroutineScope.launch {
</tabs>

You can use special symbols in string resources:
* `\n` — for a new line

* `\n` — for a new line
* `\t` — for a tab symbol
* `\uXXXX` — for a specific Unicode character

Expand All @@ -220,7 +216,7 @@ Currently, arguments have basic support in string resources:
```XML
<!-- strings.xml -->
<resources>
<string name="str_template">Hello, %1$s! You have %2$d new messages.</string>
<string name="str_template">Hello, %1$s! You have %2$d new messages.</string>
</resources>
```

Expand Down Expand Up @@ -262,7 +258,7 @@ suspend fun readBytes(path: String): ByteArray

You can place raw files in the `composeResources/files` directory and create any hierarchy inside it.

For example, to access raw files:
For example, to access raw files, use the following code:

<tabs>
<tab title= "From composable code">
Expand All @@ -278,7 +274,7 @@ Text(bytes.decodeToString())
```

</tab>
<tab title= "From a non-composable code">
<tab title= "From non-composable code">

```kotlin
coroutineScope.launch {
Expand All @@ -291,7 +287,7 @@ coroutineScope.launch {

### Remote files

Only files that are a part of the application are considered as resources.
Only files that are part of the application are considered resources.

You can also load remote files from the internet using their URL. To load remote files, use special libraries:

Expand Down
2 changes: 1 addition & 1 deletion topics/tools/fleet.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ Since the `ContentView.swift` file is already open, you can explore Fleet suppor
}
AnimatedVisibility(showContent) {
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Image(painterResource("compose-multiplatform.xml"), null)
Image(painterResource(Res.drawable.compose_multiplatform), null)
Text("Compose: $greeting")
}
}
Expand Down

0 comments on commit b8e81c7

Please sign in to comment.