AdobeXD / xd-to-flutter-plugin

Generate assets from XD for use in an existing Flutter project
BSD 2-Clause "Simplified" License
952 stars 97 forks source link

Code Segmentation and Injection on Groups #110

Open gskinner opened 3 years ago

gskinner commented 3 years ago

This proposal aims to address two specific issues:

  1. As designs grow, the resulting code becomes increasingly nested and difficult to read.
  2. We can't possibly support every use case developers have, and round trip support is highly unlikely, so we need ways to layer in custom functionality without editing the exported Dart code directly.

To address both these cases, we could add an option for Groups to allow the user to specify alternative ways to generate its code.

  1. Segment the generated code out of the main build method. For example, the user could choose to export it as a separate method, or potentially as a separate Widget (though this latter case could get quite complicated with trying to pass parameters through). We would need to disallow certain reserved names (Ex. build), and check for duplicates on export. This could also potentially allow for a devs to subclass the widget and override specific build methods.
  2. Allow the user to provide code that would be substituted in place of the generated output. This would allow the injected code to be completely custom, and also allow for any visual representation of said code within the design.

Here's a rough mockup of what this could look like: image

While there's an argument for supporting this functionality on other element types (ex. replacing the code for a text field with an input), I think keeping it limited will simplify the UI and implementation, while remaining super easy to work around (ex. simply make the text field a group).

Thoughts/feedback are very welcome.

gskinner commented 3 years ago

For the code injection, it could also be interesting to allow for some kind of child / children injection.

For example something like this could inject the generated output of the whole group: Container(child: <THIS>, ...)

Whereas, this could inject an array of the children: Column(children: <CHILDREN>, ...)

It might be worth ensuring we consider some kind of parameter system for adjusting the output. Maybe even leverage JSON so it's easy to parse? Column(children: <CHILDREN{"layout":"none"}>, ...)

JakubNeukirch commented 3 years ago

For the custom widget code I am just creating component in Adobe XD and name it for example "MySuperTextField" - and just do not export it. Instead I am creating widget in my project with the same name so it is placed right there.

gskinner commented 3 years ago

@JakubNeukirch - ya, that's a smart way to work around it now. I'm hoping to do something a bit more explicit with the proposal above.

gskinner commented 3 years ago

A fourth option for adding a builder parameter would also really open up options for externally modifying the behavior of an exported widget:

image

gskinner commented 3 years ago

After an extensive battle with XD UI, I have the initial implementation of these features working locally, and am hoping to commit them after a bit more testing. Unfortunately, it doesn't look like XD UI supports monospaced fonts in textareas.

image
gskinner commented 3 years ago

I just pushed c6b6ba4e1f1612de9deedc31dfe3e334e1e1be79 which is a monster commit that adds this feature and a bunch of additional updates / refactoring that I tackled as part of it. It seems to be quite functional, and people are welcome to test, but there are still some edge cases that need consideration - specifically how to deal with decorators (ex. onTap, blur, etc), and whether there's a reasonable way to check custom code for syntax errors.

Short description: All groups in XD now present an setting to set their export mode to one of four options:

  1. Export as inline code – this is the default behavior, simply writes the code for that group inline.
  2. Export as build method – this will write the code into a separate build method on the widget, which is called inline. buildMyGroup(context). This reduces the depth of nesting, which makes the generated code more readable, and could potentially be overridden in subclasses.
  3. Replace with builder param – adds a required WidgetBuilder parameter to the widget, and calls it inline. Does not export the code for the group at all. This allows code external to the exported widget to inject whatever it wants at that location.
  4. Replace with custom code – injects the specified Dart code in place of the group. Does not export the code for the group. This allows a group to show a visual representation inside XD for a control, but be completely replaced by custom dart code. For example, you could imagine a group that looks like a slider, that is replaced with the dart code for a slider.
gskinner commented 3 years ago

I just pushed initial support for tags in custom code to let you inject the target item's exported code <THIS> or a list of its children <CHILDREN>. This includes support for enabling group decorators: <THIS{"decorators":true}> and adjusting layout for children: <CHILDREN{"layout":"none|size"}>.

This is very much alpha functionality, and is subject to removal or significant change.