Widgets vs helper methods | Decoding Flutter

 


As a Flutter developer, you’re used to implementing features by wrapping parts of your UI in new widgets.

Flutter Widgets

For example, maybe you start with a text widget.

And then you think, “Well, actually this will eventually have an image and a subtitle as well.

Better use a ListTile.

But, of course, that’s not quite right yet.

A little elevation and rounded corners are always nice. Time to wrap it in a Card.

Better, but this also needs to be Dismissible.

Easy enough. There’s a widget for that.

Eventually, your UI looks pretty good, which is when you remember that this item will actually be rendered inside of a loop.

And now you’ve arrived at one of the time-honored Flutter developer moments, breaking up an unwieldy build method.

The first step in this process requires you, the human developer to make a judgment call.

You must identify which code blocks are good candidates for re-use.

Attributes that lend themselves toward re-use are: repeated sequences of code and discrete sections of your actual UI.

Often, these two things go hand in hand.

In our example, it’s the Dismissible widget and all of its children that should be separated. It both repeats in our code and renders a contained section of our UI.

Great!

The second step is, of course, to actually move the code. And this is the crux of what you and I are chatting about right now, because there are two possibilities before you with quite different implications.

The first option is to move the code into a method on the same widget.

Flutter Helper method

This is often called the “helper method” option If you’re anything like me, you probably started doing this when you first encountered a big unwieldy built method.

The second option is similar, but instead of moving your code block into a method on the same widget, you move it into an entirely new widget.

Similar but not the same.

Given these two options, which do you normally use?

Helper methods or separate widgets?

Again, if you’re anything like me, you evaluated your options and thought, “Well, shoot, helper methods pass all of my variables for free, and surely a helper method is more performant than an entirely new class.”

I’ll use helper methods.

If that’s you, I’ve got good news and bad news.

The good news is that you’re still a great developer and your users love your apps.

The bad news, sadly, is that the answer here has been counter-intuitive.

And there are important reasons to prefer separate widgets.

Let’s dive into it.

First and foremost, remember that when setState is called within a widget, its entire build method is rerun.

This means that if a user toggles a favorite icon in the corner of a large piece of your UI, having rendered that icon in a helper method will require Flutter to rebuild the entire wrapping widget.

You may have heard that there are multiple trees that Flutter uses to build your UIs, and the widget tree is only the top one.

Creating a few more widgets in that top tree

is hardly ever going to slow down your app, but unnecessarily rebuilding whole sections of your UI can cause those deeper trees: the element tree

and the render object tree to waste precious CPU time.

And what’s worse, consider what happens if you animate your icon between states.

Now your app is unnecessarily redrawing expensive UI chunks 60 times per second for the duration of the animation.

when all it had to do was animate the pixels inside the icon.

So, instead of refactoring that favorite icon into a helper method, use an entirely new widget up front, so that when it toggles states, Flutter is able to precisely target what it re-renders.

And for truly best results, use const constructors wherever possible as that allows Flutter to short-circuit the most amount of unnecessary work.

Congrats!

Now your app is running faster and everyone’s happy.

It’s time to write some tests for your UI.

So you open up that widgets file again to remind yourself what to capture in a widget test.

One obvious candidate is that the favorite icon we’ve been talking about: changes color as expected.

Which piece of code would you rather write a test for?

The helper method version?

Or the separate widget version?

The separate widget version couldn’t be simpler, whereas the helper method version requires you to reconstruct every dependency

needed by _expensiveWidget1(), and _expensiveWidget2(), and they were expensive, so it’s probably a lot.

As if that’s not enough, there are other scenarios where you can accidentally hold on to a stale BuildContext.

Consider this code which uses a Builder.

but accidentally uses a different name for one of the builtContexts, allowing further nested code to use an old, unreliable builtContexts.

Refactoring this into a separate widget can make the bug impossible.

Now our MyIconButton only has access to one BuiltContext, and it’s always the correct one.

Yes, it’s true that creating new widgets severs that free connection back to all of the available attributes, Except, now that we’ve explored it more.

It never really was free, was it?

It always came at the cost of performance, testability and occasionally, even accuracy.

When I was speaking about this issue with Remi Rousselet, the creator of Provider, Riverpod, Freezed, and many other packages, he summarized a situation like this: “Classes have a better default behavior.

the only benefit of methods is having to write a tiny bit less code.

There’s no functional benefit.”

And that’s it,.

*

Post a Comment (0)
Previous Post Next Post