TL;DR
I work on a startup that is trying to merge the gap between design & developer workflows using Flutter, where we open-sourced a tool called Parabeac-Core. We learned that users of Parabeac-Core need the exported code to map in a way that is more similar to how we structure projects as a developer. One big problem we found with a tool like Parabeac-Core was the different architectures/design patterns that developers use, making the generated code incompatible or messy. Also, because Parabeac-Core has plugin capabilities that we call ‘eggs’, it only adds further complexity to the problem. For example, if somebody created a “Sign In with Google”, button as an egg, the built-in logic would need to be written differently for each development team based on the standards their team put in place. This post goes over how we thought & are thinking about the problem in relation to Parabeac-Core.
Context
If you’re not already familiar with Parabeac-Core, you can visit the repo here. To put it simply, it is a tool that takes Sketch or Figma files & converts them into a Flutter project.
As developers, we fantasize about converting a design into front-end code that we can use. The only problem though is ‘quality code’ is difficult to accomplish because it means different things to different developers. We are trying to take it a step further by continuously iterating the quality, capabilities, & configurability of this tool. In this post, I go over support for popular Flutter state management systems like BLoC & Provider.
Idea
When looking at how designers often describe different variations of the same element, we wanted to map these variations to states that are available to the developer. This feature would allow designers to construct basic state machines for any of their UI elements without changing their existing workflow too much or none at all.
For example, the designer could have UI for light and dark mode and through an egg, the app could go from light to dark mode if the time is after 7 pm but before 8 am. Here, the developer would typically transition between 2 states that point to 2 variations (light & dark) based on the time. This is simple for a developer to write, but complex for a generator to write. If the development team is using BLoC, this code will look one way, and if they’re using Provider, it will look another way. Now consider writing an egg that transitions these two states based on time. Depending on the state management system the developer team is using, even the code transitions will look quite different for each system.
Luckily, we’ve been able to accomplish writing the boilerplate variations & states problem with the latest release v1.3. We’ll introduce a solution to eggs and transitions in a future release.
Implementation
Designers typically create ‘variations’ of design elements by using the "/" in their naming. In the image below, we created an AppButton with 2 variations, default & dark for dark mode.
Solution Part 1: Generating Boilerplate
Where a designer expresses the variations and states in their design, we added recognition to the individual states to have the info needed to generate the boilerplate code for state management. The generated boilerplate code changes will be based on the specified state management configuration (Provider, BLoC, etc.).
The proposed process for this step is straight toward, as shown by the previous flow chart diagram.
- The designer expresses its variations and states through typical naming conventions.
- Set the State Management Configuration of choice. (Provider, BLoC, etc.)
- Aggregate variations and states inside a single PBIntermediateNode. (The standard intermediary node we convert to from a design file)
- Finally, generate the Flutter code through a ‘GenerationConfiguration’ that is responsible for the generation of state-related code.
GenerationConfiguration
The following images represent the set of classes and the states in the generation phase of Parabeac-Core.
The first image depicts the most important classes that influence the output structure of this phase.
FileStructureStrategy
FileStructureStrategy
defines how the dart files should be arranged. For example, when following the BLoC structural pattern, the developer might want to include a directory per bloc, its states, and its events. Furthermore, each developer might have their preferred way of arranging these files. The FileStructureStrategy allows for that customizability, what code goes in what file and directory. BLoC and Provider contain their strategy to define the output structure.
Middleware
The Middleware
class allows for the modification of any node being processed in the generation phase. We leverage this class to detect nodes with different UI variations(representing the result set for any given application state). When we detect a node with this information, we use the configured middleware to generate its corresponding code. In the case of the BLoC, its middleware would create the bloc, its different states, and its events (you can see an example of BLoC’s middleware at the bottom of the article).
Stitching Everything Together
The GenerationConfiguration is the class that just ties everything together; it contains the FileStructureStrategy that defines the output and the Middleware that creates additional classes.
Implementation
After building the initial architecture, the final implementation of creating a state management configuration became extremely easy.
You can learn how to make a state management configuration in our wiki here but you can also reach out to us on our Discord if you need any help! I’d love any feedback on the implementation, thanks for reading!
Top comments (0)