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:
A
Modifier
receiver function that will create theModifierNodeElement
implementation and appended to theModifier
chain list. This function shoul act as a factory of theModifierNodeElement
.A
ModifierNodeElement
that has responsibility to create instance of theNode
and updating it, this implementation should not hold any state. This class is immutable, consequentially, it should be adata 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.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 aremember{ }
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
First we have the
Modifier.curve
function that works as factory for the our custom modifier.Next is our
ModifierNodeElement
implementation that will create and update ourNode
implementation. This new modifier will be called on the next composition of modifier tree, if the inputs have changed and will update ourNode
.Finally,
CurveNode
is actual class that draws and contains the state that was remembered previously in thecomposed {}
block.
The complexity has increased for the price of the performance.