How to handle dynamic layout in Protopie
Did you ever miss the auto layout in Protopie? You are not alone. If you want to create a large stack of tiles with dynamic content and different heights, you are in for a bumpy ride until their auto layout feature is done (they changed the feature request to ‘in work’ recently, yay). Here is my approach.
Topics that will be explained in this post:
- Storing local JSON and how to read it
- Applying ‘manual’ auto layout
- Building and stacking the list
Storing local JSON and how to read it
There are multiple blog articles that explain how to get live data into Protopie. In this case, for reproducibility purposes and because live data/Connect does not work with the protopie app, we will fall back to a JSON we download beforehand and store inside the prototype. It still considerably speeds up work if you want to test a vast amount of requests in a new layout, as replacing the JSON is a simple copy and paste.
Here is the JSON I will use for demonstration purposes: https://gist.github.com/simonschmidt-idealo/dd3db92d9f0b488c995e9c2278412dd5
And here is the finished pie for you to compare against: https://cloud.protopie.io/p/ce216a2c7d7c998b08a3c135
{
"data": {
"search": {
"items": [
{
"category": "Lautsprecher",
"name": "Sonos Era 100 Black",
"mainDetails": [
{
"value": "2.0 Stereo"
},
{
"value": "Bluetooth 5.0"
},
{
"value": "mitFreisprechfunktion"
},
{
"value": "Line-In (3,5mm Klinke)"
}
],
"price": 21900,
"image": "https://ip.keycdn.com/example.jpg?width=300&quality=70"
},
// excerpt, you need the full json linked above
Layout first
We start by building our tile according to the data provided. In this case, we have a category, name, userReviews, main details and price. Checking the complete JSON we see the main details having 1 to 4 entries which we need to accomodate for. Those will serve as our example of dynamic content.
Let’s start with creating a tile component and a simple layout. Here we add all the necessary text fields and all possible 4 main detail entries.
Naming in Protopie
I recommend naming all layers consistently to avoid confusion. The same is true for variables. Keep in mind both are case-sensitive when used in functions. This is a source for mistakes, you can make your life easier by sticking to lowercase.
Make sure to set Text Resize to Auto Width or Auto Height, depending on how you want your text field to scale depending on data length.
When your layout is complete, add the data.
Creating data variables
Storing data inside data variables made error searches more efficient to me in contrast to parsing the data right away into the text field. Create a data variable for each data snippet we need and make sure to set them all to type text, otherwise, they won’t accept the data. Create another variable for the JSON, in my example, called currentJSON, and paste the complete JSON inside the tiny text field.
Make sure you add all those variables inside the component and not in the scene, otherwise, the component cannot access them.
Parsing the JSON
If you are unfamiliar with code, this might seem like a daunting task, but it’s really simple. Protopie has a function to do that for you called parseJson(). To read a value from the JSON, you go through each curly bracket, separated by a dot. If you encounter a rectangular bracket (array), its time to decide for one of the entries inside it. Arrays start counting at zero instead of one. To select the first entry of the main details, you need to put in a function:
// reads first entry of items and first entry of mainDetails
parseJson(currentJSON, "data.search.items.0.mainDetails.0.value")
Reminder parsing JSON is case sensitive – mainDetails has a capital letter inside, which might happen in any API.
Clustering events
Let me quickly swerve into the topic of how to structure Protopie. I found it to be good practice to cluster similar functionality into Receive events, creating a single source of truth for everything you do. This makes testing easier, avoids repetition in case you need to access the same function again, and reduces mistakes. And best of all: you can collapse everything when you are done. In short: as soon as you want to write the same event a second time — combine it with a new Receive event.
Let's cluster all our data readings into a Receive event called setData. Don’t forget to name your event layers as well. Now parse the JSON for each text field into a variable.
Reading data
Now read all the data stored in variables into the text fields. You only need to add a Text Event -> Function -> data_variablename each time. As the price is stored as a 5-digit number we need to apply additional styling:
data_price/100 + " €"
Read only the first item for now (index 0), we will add the others later.
Applying ‘manual’ Auto Layout
The fun part: Each cell needs to grow in height depending on the content. As there is no native function for stacking/auto layout in Protopie yet, we need to calculate this ‘by hand’. Fear not, it's pretty easy, albeit a bit repetitive. At the end of our setData cluster, we add another send event, let's call it handleMainDetails and a corresponding receive event.
No data — collapse
Group each main detail text and select clip sublayers on the bottom right. Inside your handleMainDetails add a condition for each main detail to check if it's empty and if it is — scale it to zero. This not only collapses the view but also hides any potential leftover content.
Use duplicate to save you some work for the rest of the main details, but don’t forget to adjust the references.
Stacking elements
Whenever the main details are missing, we need to move all elements below accordingly. To do this, we add another Send/Receive event called autoLayout. Inside, align each item below the title with the item above it. E.g., we read the values for the title Y position and add the title height to get the bottom edge of the title.
As we want to accommodate for the gap above grp_maindetail1 as well, we need to add a variable holding the distance between main details and price, in this case, it's 15. Let’s call it gap_abovemaindetails. This variable is added to the title bottom edge in the function to position grp_maindetail1. Make sure you select the container group instead of the text field for this.
`grp_maindetail1`.y+`grp_maindetail1`.height+gap_abovemaindetails
Do this for all main details accordingly, same for price.
Calculating cell height
Now add another Send/Receive to determine the resulting cell height. I assume you have prepared all the gaps as variables now. In short, the function of this could be
gap_topEdge+
`category`.height+
`title`.height+
gap_abovemaindetails+
`grp_maindetail1`.height+
gap_maindetails_between+
`grp_maindetail2`.height+
gap_maindetails_between+
`grp_maindetail3`.height+
gap_aboveprice+
`price`.height+
gap_bottomEdge
This nicely snuggles the cell around its content.
Building and stacking the list
Up until now, we have only built one cell component. How does the component know which cell is inside the layout? We need a new variable in the component for this. Let’s call it tile_index. It’s important to check the Make Overridable here, as this is how we will feed the tile position in the list from a scene into a component.
Let’s revisit all data for variable assignments in the component and add our new tile_index to the reading like this:
parseJson(currentJSON, "data.search.items."+tile_index+".price")
Do this for all variables.
Scene preparation
We are almost done! Switch to the scene now and stack 6 of your tile components into a scroll view (by grouping and activating vertical scroll).
Then click on each component and assign the correct tile_index override number to each. As arrays count from zero, the top most can stay at zero. The second gets a 1, and so forth. This should look pretty good already, all cells should fill their respective content and adjust their heights. However, you might notice the gaps between the cells are still wonky.
Stacking the cells
Define the target gap between the tiles and put it in a variable named gap_tile. Then create a start event, add a Send/Receive just for the sake of it, and name it stackCells. Inside the Receive create one Move event for each cell, starting from the second (assuming the first already sits in the correct place).
`tile 1`.y + `tile 1`.height+gap_tile
Do this for each cell, and you will notice that — it won’t work.
This is due to the recalculation of the layout, which needs a minimum amount of time after each move. We can solve this by adding a 0.01 start delay for each cell moved.
Outcome
You should now have a neatly aligned list of cells that change height dynamically depending on the content fed by JSON. I hope the techniques used made sense to you and helped you in achieving even more complex prototypes. As soon as auto layout arrives in Protopie this whole process will hopefully get a whole lot easier. I cannot (and could not) wait for it. Thank you for reading.
Do you love agile product development? Have a look at our vacancies.