Migrate your composed { } modifiers

3-steps recipe for migrating custom composed modifiers

Compose lint checks used in the project raised warning that composed { } implementation is no longer recommended because of performance issues it causes. Without further investigation, accepted that statement as true and started working on replacing them with Node implementation.

Steps:

  1. A Modifier receiver function that will create the ModifierNodeElement implementation and appended to the Modifier chain list. This function shoul act as a factory of the ModifierNodeElement.

  2. A ModifierNodeElement that has responsibility to create instance of the Node and updating it, this implementation should not hold any state. This class is immutable, consequentially, it should be a data class . update(node) function is called when inputs have changed, the parameter is the current instance of the node and has to be updated with the new inputs.

  3. A Node implementation, this is the functional implementation. This is where you hold and update the state. Cordially put, this is where you put everything you had in a remember{ } block in thecomposed { } block.

Interface for your Node

You may need to know to make right choice which implementation of Node you need to use for your functionality. Namely, if your node has to draw, you should implement DrawModifierNode . For improving accessibility, or maybe you have custom test tags, SemanticsModifierNode is best choice. Layout and measuring related operations are covered in LayoutModifierNode , LayoutAwareModifierNode and GlobalPositionAwareModifierNode .
Most common use case is implementing exotic touch behaviors, you can do so with PointerInputModifierNode .

Worth noting is CompositionLocalConsumerModifierNode interface, it allows you to access compositional locals using currentValueOf, e.g currentValueOf(LocalContext.current), if you have used compositional locals in your composed { } block.

For library developers, it is recommended to make modifiers delegetable by implementing Delegetable interface, so that other modifiers that implement DelegatingNode can use your custom modifier.

Before

This is pretty straight forward example, where we draw a Path behind the content. In the composed{ } block we remember the Path to reuse it.

After

  1. First we have the Modifier.curve function that works as factory for the our custom modifier.

  2. Next is our ModifierNodeElement implementation that will create and update our Node implementation. This new modifier will be called on the next composition of modifier tree, if the inputs have changed and will update our Node .

  3. Finally, CurveNode is actual class that draws and contains the state that was remembered previously in the composed {} block.
    The complexity has increased for the price of the performance.