On one of the projects, during the migration to Jetpack Compose, we needed to convert a large number of icons from XML ( Android drawable) and SVG (Figma design system) to ImageVector. The existing solutions didn't quite fit our needs due to their inconvenient workflow, numerous bugs, poor output code, and in some cases, even being paid (after 5 icons 😄).
Additionally, with the release of Compose 1.7.0, Google discontinued support for material icons.
- Support conversion from SVG and XML
- Custom kotlinpoet generator with streamlined code formatting:
- code alignment and formatting
- remove redundant code by default (e.g.
public
keyword) - remove unused imports (e.g.
kotlin.*
package) - skip default ImageVector parameters
- support generation as backing property or lazy property
- optional trailing comma and explicit mode
- customize code indent
- Ability to create your unique project icon pack (+nested packs if necessary)
- High performance (6k icons processing ~5sec)
- IntelliJ IDEA / Android Studio plugin
- CLI tool
- Gradle plugin and Web app (🚧 coming soon 🚧)
- Two conversion modes: Simple and IconPack
- Support for Drag&Drop files/directories and pasting content from clipboard
- Easy option to add more icons into existing project icon pack
- Export generated ImageVector to clipboard or file (depends on the mode)
- Fully customizable setting for generated icons
- Build-in ImageVector previewer for any icons without compilation ✨
- The plugin is completely built using Compose Multiplatform and Tiamat navigation library
More exclusive features under development, stay tuned 🌚
Note
One-click solution to convert SVG/XML to ImageVector (requires only specifying the package).
- Rename icon
- Preview current ImageVector
- Copy generated ImageVector to clipboard
Demo:
simple_mode_demo.mp4
Note
Facilitates creating an organized icon pack with extension properties for your pack object
, previewing the list of
icons, and batch exporting them to your specified directory.
Demo:
iconpack_mode_new_demo.mp4
Note
Instead of importing icon pack settings, the plugin provides a direct way to import an already created icon pack from a Kotlin file.
Important
Editing features are limited for now; you can only load an existing pack and add more nested packs.
Demo:
iconpack_mode_existing_demo.mp4
We personally find it very useful to have a previewer for ImageVector (such we have for SVG or XML). Previewer available for any ImageVector formats (backing or lazy property, legacy google material icons) without compose @Preview annotation and project compilation.
Previewer actions:
- Change icon background (pixel grid, white, black)
- Zoom in, zoom out icon without loosing quality
- Draw as actual size
- Fit icon to window
Demo:
imagevector_previewer.mp4
- IntelliJ IDEA 2024.1 and later
- Android Studio Koala and later
Important
K2 mode is available starting from IntelliJ IDEA
2024.2.1 (more details)
-
Find plugin inside IDE:
Settings > Plugins > Marketplace > Search for "Valkyrie" > Install Plugin
-
Manually: Download the latest release or build your self and install it manually using Settings -> Plugins -> ⚙️ -> Install plugin from disk...
Precondition: IntelliJ IDEA with installed Plugin DevKit
Run ./gradlew buildPlugin
to build plugin locally. Artifact will be available in idea-plugin/build/distributions/
folder
or run plugin in IDE using: ./gradlew runIde
CLI tools can be easily integrated into scripts and automated workflows, allowing you to convert icons from specific source with predefined settings.
brew install ComposeGears/repo/valkyrie
Download latest CLI tool from releases.
Unzip the downloaded archive and run the CLI tool from bin
folder in the terminal
./valkyrie
You should see this message
A simple example of how to get the latest version of the CLI tool. It can be executed on CI/CD with predefined parameters.
#!/bin/bash
TARGET_DIR="valkyrie-cli"
ASSET_NAME="tmp.zip"
LATEST_CLI_RELEASE_URL=$(curl --silent "https://api.github.com/repos/ComposeGears/Valkyrie/releases/latest" \
| jq -r '.assets[] | select(.name | startswith("valkyrie-cli")) | .browser_download_url')
curl -L -o "$ASSET_NAME" "$LATEST_CLI_RELEASE_URL"
mkdir -p "$TARGET_DIR"
unzip -o "$ASSET_NAME" -d "$TARGET_DIR"
rm "$ASSET_NAME"
cd "$TARGET_DIR/bin" || exit
./valkyrie svgxml2imagevector -h
A part of the CLI tool that allows you to create an icon pack with nested packs.
Usage:
./valkyrie iconpack [<options>]
Demo:
cli_iconpack.mp4
A part of the CLI tool that allows you to convert SVG/XML files to ImageVector.
Usage:
./valkyrie svgxml2imagevector [<options>]
Demo:
cli_svgxml2imagevector.mp4
Run ./gradlew buildCLI
to build minified version of CLI tool. Artifact will be available in
cli/build/distributions/valkyrie-cli-0.11.0-SNAPSHOT.zip
.
Backing property | Lazy property |
package io.gh.neting.ccposegears.valkyrie.backing.outlined
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import io.gh.neting.ccposegears.valkyrie.backing.BackingIcons
val BackingIcons.Outlined.Add: ImageVector
get() {
if (_Add != null) {
return _Add!!
}
_Add = ImageVector.Builder(
name = "Outlined.Add",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color(0xFF232F34))) {
moveTo(19f, 13f)
lineTo(13f, 13f)
lineTo(13f, 19f)
lineTo(11f, 19f)
lineTo(11f, 13f)
lineTo(5f, 13f)
lineTo(5f, 11f)
lineTo(11f, 11f)
lineTo(11f, 5f)
lineTo(13f, 5f)
lineTo(13f, 11f)
lineTo(19f, 11f)
lineTo(19f, 13f)
close()
}
}.build()
return _Add!!
}
@Suppress("ObjectPropertyName")
private var _Add: ImageVector? = null |
package io.gh.neting.ccposegears.valkyrie.lazy.outlined
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import io.gh.neting.ccposegears.valkyrie.lazy.LazyIcons
val LazyIcons.Outlined.Add: ImageVector by lazy(LazyThreadSafetyMode.NONE) {
ImageVector.Builder(
name = "Outlined.Add",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color(0xFF232F34))) {
moveTo(19f, 13f)
lineTo(13f, 13f)
lineTo(13f, 19f)
lineTo(11f, 19f)
lineTo(11f, 13f)
lineTo(5f, 13f)
lineTo(5f, 11f)
lineTo(11f, 11f)
lineTo(11f, 5f)
lineTo(13f, 5f)
lineTo(13f, 11f)
lineTo(19f, 11f)
lineTo(19f, 13f)
close()
}
}.build()
} |
Source SVG icon:
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#e8eaed">
<path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>
ImageVector output:
Valkyrie | composables.com |
package io.gh.neting.ccposegears.valkyrie
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val Add: ImageVector
get() {
if (_Add != null) {
return _Add!!
}
_Add = ImageVector.Builder(
name = "Add",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f
).apply {
path(fill = SolidColor(Color(0xFFE8EAED))) {
moveTo(19f, 13f)
horizontalLineToRelative(-6f)
verticalLineToRelative(6f)
horizontalLineToRelative(-2f)
verticalLineToRelative(-6f)
horizontalLineTo(5f)
verticalLineToRelative(-2f)
horizontalLineToRelative(6f)
verticalLineTo(5f)
horizontalLineToRelative(2f)
verticalLineToRelative(6f)
horizontalLineToRelative(6f)
verticalLineToRelative(2f)
close()
}
}.build()
return _Add!!
}
@Suppress("ObjectPropertyName")
private var _Add: ImageVector? = null
|
import androidx.compose.runtime.Composable
import androidx.compose.foundation.Image
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
private var _Add: ImageVector? = null
public val Add: ImageVector
get() {
if (_Add != null) {
return _Add!!
}
_Add = ImageVector.Builder(
name = "Add",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f
).apply {
path(
fill = null,
fillAlpha = 1.0f,
stroke = null,
strokeAlpha = 1.0f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1.0f,
pathFillType = PathFillType.NonZero
) {
moveTo(0f, 0f)
horizontalLineToRelative(24f)
verticalLineToRelative(24f)
horizontalLineTo(0f)
verticalLineTo(0f)
close()
}
path(
fill = SolidColor(Color(0xFFE8EAED)),
fillAlpha = 1.0f,
stroke = null,
strokeAlpha = 1.0f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1.0f,
pathFillType = PathFillType.NonZero
) {
moveTo(19f, 13f)
horizontalLineToRelative(-6f)
verticalLineToRelative(6f)
horizontalLineToRelative(-2f)
verticalLineToRelative(-6f)
horizontalLineTo(5f)
verticalLineToRelative(-2f)
horizontalLineToRelative(6f)
verticalLineTo(5f)
horizontalLineToRelative(2f)
verticalLineToRelative(6f)
horizontalLineToRelative(6f)
verticalLineToRelative(2f)
close()
}
}.build()
return _Add!!
}
|
other available gradle commands:
-
run tests:
./gradlew test
-
check code style:
./gradlew spotlessCheck
-
apply formatting:
./gradlew spotlessApply
Thank you for your help! ❤️
Developed by ComposeGears 2024
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.