The original link: blog.jetbrains.com/kotlin/2021…

The release of Compose Multiplatform marks another step in developing unified UI support using Kotlin!

Jetpack Compose for Android 1.0

  • Compose for Desktop and Compose for Web have been upgraded to Alpha, and their versioning is now consistent under the Compose Multiplatform, You can use the same artifacts to build Android, Desktop, and Web UI.

  • The IDE management application JetBrains Toolbox App has been migrated to Compose for Desktop.

  • The new IntelliJ IDEA and Android Studio plug-ins enable component previews for Compose for Desktop via the @Preview annotation.

  • Compose for Desktop now uses the composable API Window by default, providing new support for adaptive Window sizes, unified image resources, and new platform support for Linux on ARM64, allowing it to run on targets like Raspberry Pi.

  • Compose for Web extends the DOM and CSS apis further.

In addition, The Compose Story Outlines The Compose path and shares more information about The declarative multi-platform user interface!

Unified Desktop, Web, and Android UI development

Jetpack Compose is a responsive development UI framework for building a native user interface for Android. JetBrains extends the Compose framework to new platforms based on Google’s Jetpack Compose.

With the Compose Multiplatform, developers can build user interfaces using the same API for Desktop and Web development as for Android Jetpack Compose.

Using the mechanism provided by the Kotlin Multiplatform, you can now target any combination of the following in the same project:

  • Android (Jetpack Compose)
  • Desktop
  • Web

For example, Compose for Desktop and Compose for Web used different artifacts. Now they will be unified under one Gradle plugin and component. This means it’s easier to develop Android, Desktop, and Web user interfaces based on Compose.

With the Alpha update, the API provided by the Compose Multiplatform is close to its final form, and we are fully committed to developing support for Compose and expect to reach version 1.0 by 2021. Learn more about COMPOSE’s multi-platform

Compose in Production: JetBrains Toolbox for use

Compose is now used in several production applications on JetBrains, starting with the JetBrains Toolbox App, which is the management application for the JetBrains IDE with more than 800,000 monthly active users.

In their latest release, the team has fully converted the application’s implementation to Compose for Desktop, and during the migration from the Electron based UI, the team noticed a number of advantages for Compose:

  • Memory consumption is significantly reduced, especially when the application is running in the background
  • The installer size is reduced by about 50%
  • The overall rendering performance of the application is significantly improved

Victor Kropp, head of the JetBrains Toolbox team, also shared his thoughts on Compose for Desktop in the post:

Compose for Desktop, while still in its early stages, has proven to be a great choice for Toolbox apps. With the support of colleagues in the development framework, the entire UI can be rewritten in almost no time. This allows us to unify the development experience, so that from business logic to UI, from application to server, Toolbox is now 100% Kotlin.

New IntelliJ IDEA and Android Studio plug-in for Compose Multiplatform

A new IDE plug-in has also been released with this release to support development efforts: The Compose Multiplatform plug-in for IntelliJ IDEA and Android Studio is released with the new version of the framework and provides additional functionality to help with user interface development.

The first release includes a long-awaited feature: the ability to preview Compose for Desktop and Android components directly in the IDE, without even launching the application.

To display a Preview of the @Composable function with no arguments, add the @Preview annotation to its definition, which adds a small binding diagram that you can use to switch the Preview pane of the component:

This new preview will help developers shorten development cycles and make it easier to turn ideas into real designs and layouts based on Compose.

To find and install the new plug-in, search for “Compose Multiplatform IDE Support” in the plug-in marketplace, or go directly to the plug-ins page below: Install the Compose Multiplatform Plug-in

Compose for Desktop added functionality

In addition to taking a big step toward upgrading Compose for Desktop to Alpha, this release also includes improvements to its API and support for new platforms.

A composable window API by default

For the Milestone 4 desktop release, we launched an experimental API: Window, MenuBar, and Tray. These new apis all use the same @Composable concepts of state management, behavior, and conditional rendering as other components in the application.

In this release, these composable versions are now the default way to manage Windows, menu bars, and tray ICONS, replacing the old window API.

If you haven’t tried these new apis yet, or just want to learn more about the behavior and functionality they offer, check out the updated Compose for Desktop tutorial for window and tray management.

Adaptive window size

Sometimes we want to display something as a whole without knowing in advance what it will display, which means we don’t know its optimal window size.

To make it easier to develop these UI scenarios, we have introduced an adaptive WindowSize feature. By setting the WindowSize of one or both dimensions of the window to dp. Unspecified, the Compose for Desktop will automatically adjust the initial size of the window in that dimension to fit its content:

fun main(a) = application {
   val state = rememberWindowState(width = Dp.Unspecified, height = Dp.Unspecified) //automatic size
   Window(
       onCloseRequest = ::exitApplication,
       state = state,
       title = "Adaptive",
       resizable = false
   ) {
       Column(Modifier.background(Color(0xFFEEEEEE))) {
           Row {
               Text("label 1", Modifier.size(100.dp, 100.dp).padding(10.dp).background(Color.White))
               Text("label 2", Modifier.size(150.dp, 200.dp).padding(5.dp).background(Color.White))
               Text("label 3", Modifier.size(200.dp, 300.dp).padding(25.dp).background(Color.White))
           }
       }
   }
}
Copy the code

Together with removing Windows (defined by application Windows undecorated = true), we believe that this new approach to creating dynamically sized Windows opens up additional possibilities for user interfaces of various shapes and sizes.

Additional functionality for combining window menus

Desktop applications often have rich and complex window menus. Additional apis were added to allow the creation of rich menus.

They can be structured, enriched with ICONS, shortcuts, and mnemonics, and integrated with the logic of widely used check boxes and radio lists (radio buttons) :

@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun MenuBarScope.FileMenu(a) = Menu("Settings", mnemonic = 'S') {
   Item(
       "Reset",
       mnemonic = 'R',
       shortcut = KeyShortcut(Key.R, ctrl = true),
       onClick = { println("Reset") }
   )
   CheckboxItem(
       "Advanced settings",
       mnemonic = 'A', checked = isAdvancedSettings, onCheckedChange = { isAdvancedSettings = ! isAdvancedSettings } )if (isAdvancedSettings) {
       Menu("Theme") {
           RadioButtonItem(
               "Light",
               mnemonic = 'L',
               icon = ColorCircle(Color.LightGray),
               selected = theme == Theme.Light,
               onClick = { theme = Theme.Light }
           )
           RadioButtonItem(
               "Dark",
               mnemonic = 'D',
               icon = ColorCircle(Color.DarkGray),
               selected = theme == Theme.Dark,
               onClick = { theme = Theme.Dark }
           )
       }
   }
}
Copy the code

Support for context menus

Compose for Desktop Alpha supports default and custom context menus and can be triggered with a right mouse click.

For selectable text and text fields, the framework provides a default set of context menu items for users to copy, paste, cut, and select.

@OptIn(ExperimentalComposeUiApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class)
fun main(a) = singleWindowApplication(title = "Context menu") {
   DesktopMaterialTheme { //it is mandatory for Context Menu
       val text = remember {mutableStateOf("Hello!")}
       ContextMenuDataProvider(
           items = {
               listOf(ContextMenuItem("Clear") { text.value = "" })
           }
       ) {
               TextField(
                   value = text.value,
                   onValueChange = { text.value = it },
                   label = { Text(text = "Input")})}}Copy the code

Cursor change behavior and pointer icon API

Starting with this version of Compose for Desktop, when you hover over a text field or optional text, the mouse pointer now automatically becomes a text selection cursor, indicating that text selection is available and making the application feel more native.

For your own components, you can also use the newly added pointerIcon modifier to adjust the behavior of the mouse pointer, which can be changed when you hover over a particular component.

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun ApplicationScope.pointerIcons(a) {
   Window(onCloseRequest = ::exitApplication, title = "Pointer icons") {
           Text(
               modifier = Modifier.pointerIcon(PointerIcon.Hand),
               text = "Hand icon!")}}Copy the code

Mouse click modifier

To make it easier to address mouse buttons and keyboard modifier keys that are clicked or pressed when the mouse is clicked, a new API with a. MouseClickable modifier has been introduced.

Adding this modifier to the component allows you to specify the callback that receives the MouseClickScope, which provides complete information about the event:

@ExperimentalDesktopApi
@Composable
fun ApplicationScope.mouseClickable(a) {
   Window(onCloseRequest = ::exitApplication, title = "mouseClickable") {
       Box {
           var clickableText by remember { mutableStateOf("Click me!") }

           Text(
               modifier = Modifier.mouseClickable(
                   onClick = {
                       if (buttons.isPrimaryPressed && keyboardModifiers.isShiftPressed)  {
                           clickableText = "Shift + left-mouse click!"
                       } else {
                           clickableText = "Wrong combination, try again!"
                       }
                   }
               ),
               text = clickableText
           )

       }
   }
}
Copy the code

Please note that this API is not final and is currently under ongoing development and may change in the future.

Unified image resources and Painter

In the process of further stabilizing the API for Compose for Desktop, you can now use a unified painterResource, Instead of splitting graphics resources into svgResource, imageResource, and vectorXmlResource:

@Composable
fun ApplicationScope.painterResource(a) {
   Window(onCloseRequest = ::exitApplication, title = "Image resources") {
       Column {
           Image(
               painter = painterResource("sample.svg"), // Vector
               contentDescription = "Sample",
               modifier = Modifier.fillMaxSize()
           )
           Image(
               painter = painterResource("sample.xml"), // Vector
               contentDescription = "Sample",
               modifier = Modifier.fillMaxSize()
           )
           Image(
               painter = painterResource("sample.png"), // ImageBitmap
               contentDescription = "Sample",
               modifier = Modifier.fillMaxSize()
           )
       }
   }
}
Copy the code

. We will also window icon attributes from Java awt. The Image is changed to androidx.com pose. The UI. The graphics. The painter. The painter, so in addition to the future of raster graphics, you can also use based on vector icon:

fun vectorWindowIcon(a) {
   application {
       var icon = painterResource("sample.svg") //vector icon
       Window(onCloseRequest = ::exitApplication, icon = icon) {
           Text("Hello world!")}}}Copy the code

Support for Linux on ARM64

In this release, in addition to the existing x86-64 support, Compose for Desktop adds support for Linux running on arm64-processor-based devices.

In general, you can now write uIs for the following platforms using Compose for Desktop:

  • MacOS on X64 and ARM64
  • Linux on X64 and ARM64
  • X64 Windows

For Compose for Web

In addition to Compose for Desktop, Compose for Web has also been upgraded to Alpha, and both have adjusted their versioning schemes and release cycles, as well as extending the available functionality for style and event management through their DSLS.

Extended CSS API

We continue to refine and refine the API to specify style rules through CSS, and this latest version adds better support for arithmetic, setting properties, and animation in the type-safe DSL.

Arithmetic operations of CSS units

You can now perform any operation on a CSS value. If you perform an operation on two values of the same unit, you will get the new value of the same unit, as shown in the following example:

val a = 5.px
val b = 20.px
borderBottom(a + b) // 25px
Copy the code

CSS API for setting properties

Extended type-secure access to all the most commonly used CSS properties to cover all the CSS properties supported by most modern browsers.

This means that in most cases you can get support directly from the type-safe API, but for more exotic properties, or properties that are not yet supported, you can also assign values to functions that get keys and values directly from the property:

borderWidth(topLeft = 4.px, bottomRight = 10%) // type-safe access!

property("some-exotic-property"."hello-friend") // raw property assignment
Copy the code

Animation API

In order to make the Compose based user interface more dynamic, the option to create CSS animations from a type-safe DSL is now available:

object AppStyleSheet : StyleSheet() {
   val bounce by keyframes {
       from {
           property("transform"."translateX(50%)")
       }

       to {
           property("transform"."translateX(-50%)")}}val myClass by style {
       animation(bounce) {
           duration(2.s)
           timingFunction(AnimationTimingFunction.EaseIn)
           direction(AnimationDirection.Alternate)
       }
   }
}
Copy the code

If you want to explore these apis more on your own, be sure to check out our newly added examples, which show some more advanced CSS animations and DOM manipulation capabilities.

Event hierarchies, event listeners, and new input types

Handling events, especially those emitted by the input component, is one of the key parts of responding to changes in the Compose application.

This version simplifies access to event properties, makes it easier to define event listeners, and provides different input types.

Event type hierarchy

Most previous event-based apis required direct use of nativeEvent or eventTarget to access the value of the event of interest.

Starting with this version of Compose for Web, you can now access a SyntheticEvent, whose subtype makes it easier to access the properties associated with the emitted event.

  • SyntheticMouseEventOpen coordinates;
  • SyntheticInputEventExpose text values;
  • SyntheticKeyEventOpen the click button

A few examples:

Div(attrs = {
   onClick { event -> // SyntheticMouseEvent
       val x = event.x
       val y = event.y
   }
})
Copy the code

These new event types are designed to directly provide access to the same properties that are available in Native events without having to access the target of nativeEvent or direct events.

The input

In regular HTML, different input types, from text fields to checkboxes, all share the same tag — input.

To make it easier to use these different input types in the Kotlin DSL and to provide more hints, a number of additional functions are introduced for creating different types of input:

TextInput(value = "text", attrs = {
   onInput { } // all these components have attrs same as HTMLInputElement
})
CheckboxInput(checked = false)
RadioInput(checked = false)
NumberInput(value = 0, min = 0, max = 10)
DateInput(value = 2021-10-10")
TelInput(value = "0123456")
EmailInput()
// and other input types
Copy the code

Event listener

Further unifies the functions that listen for events of different input types.

The input type specific function onCheckBoxInput for the input listener has been removed and can now be used directly with onInput or onChange, which means that you no longer need to search for correctly named callbacks:

Input(type = InputType.Text, attrs = {
   onInput { event ->
       val inputValue: String = event.value
   }
})

Input(type = InputType.Checkbox, attrs = {
   onInput { event ->
       val isChecked: Boolean = event.value
   }
})
Copy the code

Compose Multiplatform Alpha!

Whether it’s Web, desktop, Android, or all three, we want you to try Compose Multiplatform!

We expect Compose Multiplatform 1.0, our first stable release, to be released later this year, so now is an ideal time to try to evaluate The Compose Multiplatform for your production applications.

There are many resources available:

  • For Desktop applications, you can find the latest information on how to get started in the Compose for Desktop tutorial.

  • For browser applications, the Compose for Web tutorial will help get them up and running.

  • The sample application presentation includes other examples to examine, including Web, desktop, and multi-platform applications.

Pre-release Notes

Compose Multiplatform is currently in the Alpha phase, and while most of the apis are now very similar to their stable shape, keep in mind that some of the apis may still change to ensure that the final version provides the best possible development experience.

As we approach a stable release, we will continue to rely on user feedback to help achieve this goal!