Designing scalable Compose APIs

Android Developers
16 May 202419:53

Summary

TLDRSimona Milanovic's session on designing scalable Compose APIs emphasizes the importance of creating high-quality, use-case-based APIs in the Jetpack Compose framework. She discusses best practices for planning, structuring, and naming components like FilterChip, leveraging Kotlin conventions, and maintaining API consistency. The talk also covers accessibility, testing, and documentation to ensure long-term API stability and usability, guiding developers towards better design practices.

Takeaways

  • 📚 The core of Jetpack Compose is the composable function, which is essential for building UI and establishing a scalable API.
  • 🛠 When creating Compose APIs, it's important to have guidelines that ensure code scalability, consistency, and intuitiveness, as well as to guide best practices.
  • 💡 Start by identifying a single problem to solve with a new API, adhering to the principle of 'one component, one problem' for clarity and ease of use.
  • 🔄 For variations of a component with similar UI but different purposes, consider building layered, higher-level APIs that are more constrained and offer fewer customization options.
  • 📝 Naming conventions in Compose are crucial; use PascalCase for composable functions returning Unit, and standard Kotlin conventions for functions returning values.
  • đŸš« Avoid implicit or obscure inputs in APIs; instead, use explicit parameters to enhance readability, adjustability, and ease of use.
  • 🎹 Modifiers are key in Compose for defining the appearance and behavior of composables; ensure every UI-emitting component has a modifier parameter for customization.
  • 🔄 Prefer stateless composables that accept state as input and are controlled by the caller, which improves reusability and testability.
  • 🔧 Parameters should be ordered logically, starting with required parameters indicating the main purpose, followed by modifiers and optional parameters with defaults.
  • 📜 Use default parameter values and nullability to convey meaningful API descriptions, distinguishing between default, empty, and absent values.
  • đŸ§© Slot parameters or slot content allow for flexibility within components, enabling users to insert custom content while the component handles its main responsibilities.
  • 📋 Ensure API documentation is thorough, including capabilities, parameters, expectations, and usage examples, and maintain backward compatibility for long-term stability.

Q & A

  • What is the basic building block in Jetpack Compose?

    -The basic building block in Jetpack Compose is the composable function, which is used to write UI components that call other functions.

  • Why is it important to establish guidelines for writing Compose code?

    -Establishing guidelines for writing Compose code is important for creating high-quality, scalable code that is easier to evolve with minimum friction, consistent across the Compose ecosystem, and follows known patterns to guide others towards better practices.

  • What are the three parts of the best practices and guidelines for developing idiomatic Compose APIs?

    -The three parts are: planning for your components, leveraging Kotlin and naming conventions to define a solid structure of your API, and verifying and maintaining these APIs.

  • What should be the approach when considering creating a new API, like a FilterChip?

    -When considering creating a new API, start by determining if the API is meant to solve a single problem and ensure it is use-case based, making it easy and clear to use.

  • What is the concept of layered higher-level APIs in Compose?

    -Layered higher-level APIs in Compose are more constrained and provide specific behavior and defaults with fewer customization options. They are usually built on top of extracted lower-level components that define a common surface and expectations for all layers.

  • How should naming be approached for composable functions in Compose?

    -Composable functions should be named using PascalCase and a noun, with or without prefixes, to establish a persistent identity across recompositions and reinforce the declarative model of describing UI with Compose.

  • Why is it recommended to use explicit parameters for defining an API's purpose and requirements?

    -Explicit parameters make it easy to present, adjust, preview, test, and use the API. They also ensure the API is readable from the start and easier for users to understand at a glance.

  • What is the significance of modifiers in Compose and how should they be used in API parameters?

    -Modifiers define the composable behavior and appearance. They should be at the top of the mind when defining API parameters, as Compose users already have expectations around their use. Every component that emits UI should have a modifier parameter that is the first optional parameter, has a default no-op value, and is the only parameter of its type.

  • How should the order of parameters be determined when defining an API?

    -The order should prioritize readability and ease of use, starting with required parameters indicating the main purpose, followed by the modifier for customization, optional parameters with default values, and a trailing composable lambda for nested content if needed.

  • Why is it preferable to use stateless composables in API design?

    -Stateless composables that hold no state of their own but instead accept it as input are more reusable and testable, as the caller dictates the changes and the component follows, a practice known as state hoisting.

  • How can an API be made more testable and what are some considerations for testability?

    -An API can be made more testable by avoiding the use of platform-specific resources, providing explicit parameters instead of implicit ones, exposing all possible states as parameters, and using interactionSource for complex states. It's also important to ensure that the API can be easily verified in isolation.

  • What are some key considerations for API documentation and backward compatibility?

    -Documentation should clearly communicate the API's capabilities, purpose, parameters, expectations, and provide usage examples following KTDoc guidelines. For external usage, ensuring backward compatibility is crucial for long-term stability and maintenance.

  • How does the concept of 'one component, one problem' benefit API design in Compose?

    -'One component, one problem' ensures that the API is focused and use-case based, making it easier to understand and use, and reducing the complexity of the component.

Outlines

00:00

🛠 Building Scalable Compose APIs

Simona Milanovic introduces the concept of designing scalable APIs in Jetpack Compose, emphasizing the importance of creating high-quality, intuitive, and maintainable code. The session covers the basics of composable functions and the role of API developers in establishing guidelines for scalable UI development. It outlines the structure of the talk, which includes planning components, leveraging Kotlin and naming conventions, and maintaining APIs. The guidelines are presented as strict rules for framework contributions and as recommendations for app development, tailored to team and app requirements.

05:00

🎯 Design Philosophy and Naming Conventions

This paragraph delves into the design philosophy behind creating new Compose components, such as FilterChip, and the decision-making process involved. It discusses the principle of 'one component, one problem' and the considerations for creating variations of a component. The paragraph also covers naming conventions for composable functions, the use of prefixes for different types of components, and the importance of parameters in defining an API's purpose. Modifiers are highlighted as a key concept in Compose for defining the behavior and appearance of components.

10:02

📝 API Structure and Parameter Ordering

The focus shifts to the structure of APIs, detailing the order and types of parameters that should be used. It explains the significance of explicit and descriptive parameters for API readability and user understanding. The paragraph discusses the role of modifiers in API parameters, the importance of state management within components, and theæŽšćŽ‡ of stateless composables for better reusability and testability. It also touches on event handling through lambda parameters and the avoidance of passing state as direct API parameters.

15:03

🌐 Accessibility, Testing, and Documentation

The final paragraph addresses the importance of accessibility and testing in API design. It discusses how to handle semantic properties for screen readers and the use of modifier semantics for accessibility. The paragraph also covers the testability of APIs, suggesting ways to maximize it by avoiding platform-specific resources and using explicit parameters. The importance of documentation and maintaining backward compatibility for long-term API stability is highlighted, concluding with the goal of creating maintainable, scalable components that guide users towards better practices.

Mindmap

Keywords

💡Jetpack Compose

Jetpack Compose is a modern toolkit for building native Android UI, enabling developers to create more intuitive and declarative code. It is central to the video's theme as it discusses designing scalable APIs within this framework. The script mentions it as the basis for the composable functions that form the building blocks of UI in Android development.

💡composable function

A composable function in Jetpack Compose is a basic unit of UI that can be composed to create more complex UIs. The video emphasizes the importance of these functions in API development, noting that they can call other functions, making the developer an API developer by default.

💡API developer

An API developer is someone who designs and implements application programming interfaces (APIs). The video discusses the role of API developers in creating high-quality, scalable Compose code, whether for personal projects, teams, or external libraries.

💡scalability

Scalability in the context of the video refers to the ability of Compose code to grow and evolve with minimum friction. The script stresses the importance of writing code that is maintainable and can adapt to future changes, which is a key aspect of designing Compose APIs.

💡consistency

Consistency in the video's context means ensuring that the Compose code is uniform and predictable across the ecosystem. It is one of the guidelines for writing high-quality Compose code, making it more intuitive for users and aligning with known patterns.

💡FilterChip

FilterChip is an example component discussed in the script to illustrate the process of creating a new API. It represents a concise option for users from a limited selection and is used to demonstrate the principles of API design, such as solving a single problem and considering variations.

💡layered API

A layered API in the video refers to higher-level APIs that are built on top of lower-level components, providing more specific behavior and defaults with fewer customization options. The script uses the example of different variations of FilterChip to explain the concept of layered APIs.

💡state

State in the video pertains to the internal state of a composable that may change and impact the UI. It discusses the importance of managing state within components, either statefully or statelessly, and the practice of state hoisting to make APIs more reusable and testable.

💡accessibility

Accessibility in the context of the video is about making the UI usable for people with disabilities. The script talks about the importance of considering accessibility when designing APIs, ensuring that they can provide the necessary information to assistive technologies.

💡testability

Testability refers to how easily an API can be tested, especially in isolation. The video emphasizes the importance of designing APIs that are testable, which is a good indicator of a well-crafted API and contributes to their long-term stability and maintenance.

💡documentation

Documentation in the video is crucial for explaining the API's capabilities, purpose, parameters, and usage examples. It is highlighted as a necessary component for supporting different users and ensuring that the API is user-friendly and maintainable.

Highlights

Simona Milanovic introduces the session on designing scalable Compose APIs.

The fundamental building block in Jetpack Compose is the composable function, emphasizing the role of API developers in UI creation.

Establishing guidelines for high-quality, scalable Compose code is crucial for evolving apps with minimal friction.

Guidelines should be consistent across the Compose ecosystem to enhance intuitiveness and encourage good design practices.

The session covers best practices for idiomatic Compose API development based on experience with Compose libraries.

Guidelines are strict rules for Compose framework contributions and recommended for library development.

For app development, guidelines are considered recommendations that should be tailored to specific app and team needs.

The example of a FilterChip component is used to illustrate the process of creating a new API in Compose.

APIs should be designed to solve a single problem, ensuring clarity and ease of use.

When variations of a component are needed, it may be a signal to create separate components or layered APIs.

Layered higher-level APIs are more constrained and provide specific behavior and defaults with fewer customization options.

Naming conventions in Compose are discussed, emphasizing the use of PascalCase for composable functions returning Unit.

Parameters are essential in defining API purpose and requirements, with explicit and descriptive inputs being preferred.

Modifiers are key in defining composables and should be leveraged in API parameters for customization.

The order of parameters is important for API readability and ease of use, with required parameters listed first.

Default parameter values and nullability are used to signal the absence of value, impacting API usability.

Styling and customization options should have default values provided for independent API use.

Slot parameters or slot content are used to specify open spaces within a component for child content.

State management within components should prefer stateless composables, enhancing reusability and testability.

Accessibility and testing requirements are crucial for a reliable and maintainable API, ensuring long-term stability.

Documentation and backward compatibility are important for external API development to support different user needs.

The session concludes with a well-structured chip API example that embodies the discussed principles and practices.

Transcripts

play00:00

[MUSIC PLAYING]

play00:05

SIMONA MILANOVIC: Hi, I'm Simona.

play00:06

Welcome to this session on designing scalable Compose APIs.

play00:10

The basic building block in Jetpack Compose

play00:12

is the composable function.

play00:14

So when building UI, you write many functions, and functions

play00:17

that call other functions.

play00:19

And this makes you an API developer.

play00:22

Whether you're developing on your own

play00:24

in bigger teams and apps or for external libraries,

play00:27

it's important to establish guidelines

play00:28

for writing high-quality Compose code that is more scalable,

play00:32

making it easier to evolve with minimum friction,

play00:35

more consistent across Compose ecosystem,

play00:38

making it more intuitive and following

play00:40

known patterns, and that guides others towards better practices

play00:43

by setting the right expectations

play00:45

and encouraging good designs.

play00:47

We will cover best practices and guidelines

play00:49

for developing idiomatic Compose APIs

play00:52

based on our own experience of creating the Compose libraries.

play00:55

This comes in three parts--

play00:57

how to think about and plan for your components, how to

play01:00

leverage Kotlin and naming conventions,

play01:02

and define a solid structure of your API,

play01:05

and, finally, how to verify and maintain these APIs.

play01:08

The guidelines covered in this talk

play01:10

should be considered as strict rules for Compose framework

play01:13

contributions.

play01:14

For library development, it is recommended

play01:16

to follow these guidelines as much as

play01:18

possible to meet the expectations of your API users.

play01:22

If you're building an app, consider these guidelines

play01:24

as recommendations.

play01:25

But also consider what's best suited for your app and team

play01:28

requirements.

play01:29

Let's go through how we would decide

play01:31

to create a new API using the example of a FilterChip

play01:35

component.

play01:36

If you get an idea for a new component,

play01:38

start by asking if this API is meant to solve a single problem.

play01:42

We needed a component that would represent

play01:44

a concise option for the user from a limited selection.

play01:48

So we came up with a FilterChip component

play01:50

to solve this problem--

play01:51

one component, one problem, solved in one place.

play01:55

This ensures an API is use case-based, making

play01:58

it easy and clear to use.

play02:01

If you need the FilterChip to do more,

play02:03

like representing suggestions or quick actions,

play02:06

this is a signal that these additional problems

play02:09

should be solved in a different place, which leads

play02:12

to the next design question.

play02:14

We quickly found the need for more variations of FilterChip

play02:17

that have a similar UI, but also different purposes, representing

play02:21

generated suggestions, smart, quick actions, and concise user

play02:25

input.

play02:26

How do we approach this?

play02:27

Do we create more customization options on FilterChip

play02:31

so users can transform it completely?

play02:33

Or do we create variations of it as completely separate

play02:36

components?

play02:38

If your app designs have a consistent requirement

play02:40

for more opinionated but somewhat similar

play02:43

variations of a component, it's a good signal

play02:45

to build new components as layers on top.

play02:48

Layered, higher-level APIs like these are more constrained

play02:52

and provide specific behavior and defaults and fewer

play02:55

customization options.

play02:57

Layers are usually built on top of extracted lower-level

play03:00

components, like a generic chip, which

play03:03

define a common surface and expectations for all layers.

play03:06

Simpler, lower-level components like this

play03:09

should be more open for customizations.

play03:12

This means that when going from lower-level

play03:14

to higher-level APIs, there is an increase

play03:17

in opinionated behavior and reduction

play03:19

of open customizations.

play03:21

And that's how we opted for new layers of suggestion, assist,

play03:25

and input chips.

play03:27

Layering decisions are closely coupled

play03:29

with design requirements.

play03:30

So make an informed choice where to draw the line.

play03:34

While creating a filterable API like FilterChip

play03:36

might be justified, what about with ChipGroup, a layout that

play03:40

would rearrange chips in a selected orientation

play03:43

and apply some extra styling?

play03:45

Let's break this down.

play03:47

Arranging chips vertically or horizontally is easy,

play03:50

and you can already achieve that with existing column and row

play03:53

composables.

play03:54

The styling can be handled via applied modifiers

play03:57

and decorations.

play03:58

So there are no custom behaviors coming from the ChipGroup API.

play04:02

We can achieve everything using existing building blocks.

play04:05

This is a good signal we don't need a new API for this.

play04:09

A new API should justify its existence.

play04:11

It takes work to create, maintain, and learn how

play04:14

to use one.

play04:15

Choose between creating a new component

play04:17

or delegate to existing.

play04:20

Now that the FilterChip has been solidified

play04:22

as a concept, let's look at how best to structure it.

play04:25

Naming is a good place to start.

play04:27

Let's explain the different naming types in Compose.

play04:31

A composable function returning unit

play04:33

should be named using pascalcase and a noun,

play04:35

with or without prefixes.

play04:38

This is why FilterChip comes with a capital F,

play04:40

as it omits UI that can be added to the composition and returns

play04:44

unit by default. So it follows the naming rules for classes.

play04:48

This helps the element establish persistent identity

play04:51

across recompositions and reinforces the declarative model

play04:55

of describing UI with Compose.

play04:57

To differentiate between the unit returning types,

play05:00

for composables that return values,

play05:01

you should use the standard Kotlin conventions

play05:03

for function naming, starting with lowercase.

play05:06

Composable functions should either emit content

play05:09

into the composition or return a value, but not both.

play05:13

Any composable function that internally uses remember

play05:16

and returns a mutable object should be prefixed with

play05:19

remember to clearly signal its nature and potential

play05:23

of persisting through recompositions.

play05:26

Components without a prefix can represent

play05:28

basic components that are ready to use and somewhat decorated.

play05:31

Consider using a basic prefix for APIs that are

play05:34

bare bones with no decoration.

play05:36

This is a signal that can be wrapped

play05:38

with more decoration, as the API is not

play05:40

expected to be used as is.

play05:42

One level or layer above are the opinionated APIs,

play05:46

such as the FilterChip and InputChip,

play05:48

signaling that there are more stylistic variations.

play05:51

Prefer prefixes and names that are mostly

play05:54

derived from the component's use case or purpose

play05:57

instead of using generic company name, module, or feature

play06:00

prefixes.

play06:02

Composition locals provide global-like values

play06:04

scoped to a specific subtree of composition.

play06:07

To signal this, they should hold a descriptive name

play06:10

and use local as prefix.

play06:12

Your API is only as functional as the inputs it can access.

play06:16

Parameters are crucial to defining the API

play06:18

purpose and requirements.

play06:21

When thinking about your API structure,

play06:23

be explicit and descriptive.

play06:25

Explicit parameters make it easy to present the API,

play06:29

adjust it, preview, test, and use.

play06:31

Descriptive inputs ensure your API is readable from the start

play06:35

and easier for users to understand at a glance.

play06:39

Avoid implicit obscure inputs as much as possible via composition

play06:43

locals or similar mechanisms as these

play06:45

may get hard to track where the customization comes from.

play06:48

Instead, open the API up and use explicit parameters,

play06:53

which also make it easy for users

play06:54

to add more layers of customization.

play06:58

An essential Compose concept are modifiers

play07:00

and how they define composables they're paired with.

play07:04

Components are responsible for their own internal behavior

play07:07

and appearance, while modifiers are in charge of well modifying

play07:10

the external ones.

play07:11

Modifiers should be at the top of your mind

play07:13

when defining API parameters.

play07:16

Compose users already have expectations

play07:18

around where and how to use modifiers.

play07:20

So make sure you leverage that in your APIs.

play07:23

This is why every component that emits UI should have a modifier

play07:27

parameter that is-- of modifier type,

play07:29

so that any modifier can be passed;

play07:31

is the first optional parameter, so it can be set without a name

play07:35

parameter and has consistent positioning in the contract; has

play07:39

a default no-op value, so no functionality is lost when users

play07:42

provide their own modifiers appended to the existing chain;

play07:46

is the only parameter of type modifier,

play07:49

as one should be enough for a single component.

play07:51

The need for more could signal you

play07:53

should rethink other parameters or break your API up,

play07:57

which is also why this modifier should only

play08:00

be applied once to the root outermost

play08:03

layout in the component.

play08:05

If modifiers describe the composable behavior

play08:08

and appearance, you might ask, why do we

play08:10

need explicit parameters?

play08:11

How do we tell the difference?

play08:13

Every API has crucial core parts that

play08:15

represent its behavior and UI.

play08:18

These should be explicit parameters as an indication

play08:21

of the API main purpose.

play08:23

Parameters should also add behavior or decoration that

play08:26

cannot otherwise be added via modifier.

play08:29

If, however, there's a noncore modifier-supported

play08:32

customization, then you can use a modifier.

play08:36

When defining the order of parameters,

play08:38

think about the API readability and ease of use.

play08:41

Its signature is an implicit user instruction for its purpose

play08:45

and customization options.

play08:47

Consider listing the API parameters

play08:49

in the following order--

play08:51

required parameter first, indicating the main purpose

play08:55

of your API, set without default values

play08:57

so they can be used without named parameters.

play09:00

Then comes the modifier, making the API customizable

play09:03

and aligning it with Compose expectations,

play09:06

then the optional parameters, with default

play09:08

values that can be overridden by the user,

play09:10

or not, requiring the usage of named parameters.

play09:13

Additionally, you should group parameters

play09:16

that are semantically similar, like putting textual inputs

play09:18

together, click events, styling, et cetera.

play09:22

A trailing composable lambda named content,

play09:24

representing the nested slot content of the API

play09:28

if it requires one--

play09:29

this is a common concept we'll cover later.

play09:32

Using default parameter values and nullability

play09:35

to signal absence of value can be a meaningful API description.

play09:39

So choose with care.

play09:40

There's a difference between a default value, an empty value,

play09:44

and an absent one.

play09:46

Nullable parameters hint that this API feature is available

play09:49

but might not be required at all for your use case.

play09:52

The choice is yours.

play09:53

But a null value is not the same as an empty one.

play09:56

Empty defaults signal.

play09:58

This feature is required and can be used with an empty value,

play10:01

or it can be overridden with a more concrete one, based

play10:04

on the use case.

play10:06

All other default values should be nonnull, meaningful,

play10:09

and clear to the user.

play10:10

They need to be publicly available

play10:12

so the component gives the same consistent result in any place.

play10:17

A subset of your parameters will most likely

play10:19

target styling and customization options.

play10:22

A good practice is to provide default values

play10:24

for these parameters so the API is independent and can

play10:27

be used as is.

play10:29

If the default styling values are short and predictable,

play10:32

it might be enough to keep them as simple inline constants.

play10:35

However, some APIs, like the chip,

play10:37

can have a long list of customizations,

play10:40

different shapes, colors and borders

play10:42

between enable, disabled, selected, et cetera.

play10:45

If you find that your API requires

play10:47

a lot of default values of similar type,

play10:49

you could use component defaults to map them externally

play10:52

in a single place.

play10:54

Ensure these objects are publicly

play10:56

available so the component can be used anywhere, like adding

play10:59

them to your theming.

play11:01

Styling can be conditionally set based on different states,

play11:04

like how the chip sets a different background color based

play11:07

on the selected state.

play11:09

Handling conditions like this can also

play11:10

be delegated to the defaults object.

play11:14

Another Compose concept which you've likely used a lot

play11:17

are slot parameters, or slot content.

play11:20

These are composable lambda parameters

play11:22

that specify an open space inside the parent component

play11:25

to fill with child ones.

play11:26

For chips, we want the user to be able to insert a text, image,

play11:30

icon, or any other content.

play11:33

For a single slot, position it as the last content parameter

play11:36

of a composable so it ensures consistency across Compose

play11:40

and that it could be used as a trailing lambda.

play11:43

If your API requires multiple slots,

play11:45

like the chip icon and label, you

play11:47

can provide multiple parameters of such type.

play11:51

By using slots, the chip doesn't care

play11:53

what's being passed as its nested content.

play11:55

It focuses only on handling its main responsibilities,

play11:58

like the choice selection, click handling, et cetera,

play12:01

giving more flexibility to the user.

play12:04

This also simplifies the main API contract

play12:07

by taking only the information needed for it

play12:09

and delegating everything else to the slots.

play12:13

The chip API now has a relevant name, carefully

play12:16

defined inputs and styling, and nested content.

play12:19

But we also need a mechanism to manage

play12:21

the changes happening inside and outside the component.

play12:24

This is where state comes in.

play12:27

We need the chip composable to have

play12:28

an enabled and disabled state, a property that could change

play12:32

frequently and impact the UI.

play12:34

Changes in composables can be handled in two ways--

play12:37

statefully, with the composable owning and handling

play12:40

its own state, via APIs like remember or mutableStateOf,

play12:44

or statelessly, where you rely on the composable being

play12:48

called and recomposed with different inputs

play12:51

and rendered in different state.

play12:53

Wherever possible, prefer the stateless composables that

play12:56

hold no state of their own but instead accept it as input

play13:00

and are controlled by the caller.

play13:03

This makes your API more reusable and testable,

play13:05

as the caller is dictating the changes,

play13:07

and your component just follows.

play13:09

Extracting the state control like this

play13:12

is called state hoisting, a common Compose practice.

play13:16

The enabled state is now hoisted and outside of the API control,

play13:19

but the chip still needs a way of handling clicks and signaling

play13:22

it to the caller.

play13:24

Events such as onClick should be accepted as lambda parameters

play13:27

and passed accordingly.

play13:29

Extracting events like this ensures

play13:31

your API is more reusable, testable, and previewable.

play13:35

But avoid passing state mutable or immutable

play13:38

as a direct parameter of an API.

play13:41

Passing state like this means there

play13:42

could be multiple owners of it, both the caller

play13:45

and the composable, which complicates control,

play13:47

creates multiple sources of truth.

play13:49

Instead, prefer explicit inputs.

play13:53

Structuring your API is a big part of the design process.

play13:56

But we still have to ensure it satisfies

play13:58

other important requirements, like accessibility and testing,

play14:01

that it has the right documentation, and backward

play14:04

compatibility.

play14:05

We want the API to be set up to be solid and reliable long-term.

play14:10

To make your API supportive of different users

play14:12

with different needs, ensure it is able to reliably

play14:16

and logically transform what's shown on the screen

play14:18

to a more fitting format.

play14:20

Compose uses semantic properties to pass information

play14:23

to accessibility services.

play14:25

Lower-level APIs like clickable modifier,

play14:28

or composables like image and text,

play14:30

have a predefined way of handling this.

play14:32

They either provide good defaults

play14:34

or explicitly ask for information.

play14:37

When creating higher-level APIs, you

play14:40

might need to specify and request

play14:41

more information to understand how to describe UI to the user.

play14:46

If your API is required to have an explicit semantic set,

play14:49

you should request a nonoptional accessibility parameter.

play14:53

A good example is the image composable

play14:55

that has a required parameter, contentDescription,

play14:58

to signal what is necessary.

play15:00

To apply this to the chip, you could

play15:02

specify a contentDescription parameter

play15:04

and apply it via the modifier semantics to the root component.

play15:09

When designing APIs, it's important to make an informed

play15:12

decision on what to build into the implementation versus what

play15:15

to ask from the user.

play15:17

Keep in mind that users can also provide their own modifier

play15:20

semantics and try to override the default setup.

play15:24

Aside from the explicit parameters,

play15:26

you could also rely on slotted content semantics

play15:28

as more logical choice.

play15:30

APIs with slots don't necessarily

play15:32

need to set the text for accessibility services to read.

play15:36

Where it is contextually appropriate,

play15:39

the slotted content semantics, like icons, content description,

play15:42

or text from a text composable, can be merged

play15:45

into the parent semantics.

play15:47

This is called semantics merging for accessibility

play15:50

and is often used in Compose.

play15:53

To have the chip merge all of its children semantics into one,

play15:56

you can set a modifier semantics with mergeDescendants as true.

play16:01

This will force children descendants to collect and pass

play16:03

the data to your component to be treated as a single entity.

play16:08

A rule of thumb here is that simple components typically

play16:11

require one to three semantics, whereas more complex components

play16:14

require a richer set of semantics

play16:16

to function correctly with screen readers.

play16:19

When developing a new component, you

play16:21

can compare your API to an existing composable.

play16:24

Find the one that is most alike, and copy its semantics

play16:27

implementation to start with.

play16:29

And then continue fine-tuning it according to your own use case.

play16:34

When creating a new API, you should

play16:36

think about how it will be used for building UI

play16:38

as well as for testing UI.

play16:40

Checking how testable it is in isolation

play16:43

is a good indicator of a well-crafted API, which

play16:46

also makes it more testable as part of a larger

play16:49

composite component.

play16:50

Let's look at ways to maximize API testability.

play16:55

Does the component require platform-specific resources

play16:58

to work?

play16:59

For example, does it need a specific activity or context?

play17:02

If so, this could limit its testability because in tests,

play17:05

and also in previews, you might not

play17:08

have access to these resources.

play17:10

Here we require access to context

play17:12

to launch an activity as a click event.

play17:16

A better alternative would be to provide a click

play17:18

lambda as a parameter and delegate this responsibility

play17:22

to the caller.

play17:23

Implicit resources like context or composition locals

play17:26

can have impact on testability.

play17:28

So you should always aim to use alternatives

play17:31

through explicit parameters.

play17:33

However, if you need composition locals for styling purposes,

play17:37

make sure to put good defaults in place.

play17:40

When testing your API, you should

play17:42

be able to easily verify all possible states.

play17:45

For example, we should test all chip variations

play17:48

for selected and enabled states.

play17:50

Exposing these as explicit state parameters

play17:53

gives us access to change and verify in tests as we like.

play17:57

While selected and enabled states are simple to invoke,

play18:00

there are some more complex system-specific ones,

play18:02

like the pressed state.

play18:04

To make testing easier for these states,

play18:06

you can hoist and use interactionSource.

play18:09

This lets you emit and verify interactions

play18:12

at tests, such as the PressInteraction.

play18:15

Having the modifier parameter allows users to pass

play18:18

the testTag, which also helps identify the element

play18:22

in tests when other matchers are not specific enough to find it.

play18:27

For long-term stability and maintenance of your API,

play18:30

consider writing appropriate documentation.

play18:33

And if you're developing for external usage,

play18:35

ensure backward compatibility is supported.

play18:38

Documentation should follow [? KTDoc ?] guidelines,

play18:41

should clearly communicate the APIs capabilities and purpose,

play18:45

describe its parameters and expectations, as well as

play18:47

usage examples.

play18:49

For in-depth backward compatibility guidelines,

play18:52

refer to the official documentation.

play18:55

This brings us to one good-looking and well-structured

play18:58

chip API that is planned around solving a single problem,

play19:02

has different variations as opinionated components,

play19:05

chose its name carefully and descriptively,

play19:08

has the right explicit, mandatory,

play19:10

and optional parameters, well ordered with the right defaults,

play19:14

accepts slot as subcontent, is adept at handling state,

play19:18

supports accessibility and testing requirements,

play19:21

and is user-friendly with good documentation.

play19:25

Since in Compose, everyone is an API developer,

play19:28

we hope this equips you to create

play19:29

components that are more maintainable, scalable,

play19:32

consistent across Compose ecosystem,

play19:34

and that guide users towards better practices.

play19:38

Thanks so much for watching.

play19:39

[MUSIC PLAYING]

Rate This
★
★
★
★
★

5.0 / 5 (0 votes)

Étiquettes Connexes
Compose APIUI DesignScalable CodeAPI DevelopmentJetpack ComposeKotlin Best PracticesUser InterfaceAPI GuidelinesCode ScalabilityFrontend Development
Besoin d'un résumé en anglais ?