Skip to main content

Command Palette

Search for a command to run...

You Can’t Solve Problems You’ve Never Seen

Updated
•5 min read•View as Markdown
J
I'm a senior frontend and mobile developer with a decade of experience building things that work - and occasionally fixing things that don't.

When I started learning Flutter, I was completely out of my depth. I was moving from web development, where I’d been building single page applications, to a relatively young and unopinionated framework. There weren’t a lot of best practice guides, and advice didn’t always agree on the best way to solve a problem.

It was a big paradigm shift for me. Navigation stack instead of URLs and browser history. App lifecycles. Platform quirks, and trying to solve platform-specific issues with no Kotlin or Swift experience was a daunting task.

I could certainly build something functional after a few tutorials, but I couldn’t build solutions to problems I’d never seen. And that’s why brownfield work has been the most valuable experience in my career.

Questions I Didn’t Know to Ask

My first real Flutter work wasn’t building something new. It was working in existing apps, what developers call brownfield work. And there was a lot in them I didn’t recognise. What was runZonedGuarded, and what did it do? It turned out that a lot more could go wrong in a Flutter app than I was used to, and this was the last line of defence, catching anything that slipped through everything else so it didn’t fail silently.

When using plugins, there were errors that could hide at the platform level. Rendering issues could replace an entire page with an error screen instead of failing quietly. And don’t get me started on migrating legacy code to null safety. That certainly teaches you why all those layers of error handling are needed when a null assertion sneaks in without a null check.

Seeing the layers in an existing codebase prompted me to ask the questions, and experience working in that code taught me why they were important.

Someone Else’s Code

I think packages are great (no, not the ones from Amazon). Why reinvent the wheel when someone has freely chosen to share their work? But every package you add to your project is now code that you’re responsible for. Like brownfield work, you have to work with something you didn’t create and don’t quite understand.

This makes them an invaluable learning tool, for better or worse. Well-supported packages from highly regarded sources can show you how to solve a common problem. The less polished ones still have value, even if it’s in learning what not to do!

A colleague was looking at a package to meet a client’s design request rather than building something from scratch. When testing, the performance slowly degraded as the list of results grew. Digging into the source, I found it relied on IntrinsicHeight, a widget Flutter’s own documentation warns is expensive and should be avoided where possible. With traditional page navigation, it would have been fine. With infinite scroll, every new batch of results made it worse. I advised removing it, and we looked for another solution. The package wasn’t bad. It just wasn’t built for the way we were using it.

Why Wouldn’t You Update?

Something I was slow to understand was why anyone would pin package versions. Why wouldn’t you update to the latest version with bug fixes, security patches, and new features? I update my laptop, my phone, why wouldn’t I update my code?

Later I realised there were constraints you don’t see in the code. That’s exactly what Architecture Decision Records are for. Language updates and packages would frequently drop support for older Android and iOS versions, which would take the app away from a number of loyal users. We had the analytics to show how many, and for the client, it was too many.

It wasn’t a long-term solution. There was always the risk that a critical security update might force our hand. We would need to update it eventually. But for now, we had to make a compromise.

Brownfield work teaches you that compromises happen all the time. The skill is understanding why they were made, and recognising when it’s time to undo them.

The Clean Slate

Realistically, no one is giving free rein to the newbie. You’ve got to show you can handle yourself in messy, real-world code, where decisions were made for you years before, and there’s no one left who entirely understands it. It’s a lot easier to follow best practice when you’ve got a clean slate and you’re making all the calls.

And the scary truth is that the clean slate is getting rarer. When code generation and AI tools can scaffold a new project in minutes, even more of the work will be understanding and building on code you didn’t write. You’re still the one responsible for the code, and the one who has to fix it when things go wrong. If you don’t understand it, be prepared to spend your afternoons swearing at a chatbot.

Kindred Spirits

Brownfield work isn’t the runner-up prize. It’s where some of the best learning happens, if you’re willing to see it. And sometimes it’s so much more rewarding to find a solution when there’s no easy answer.

For me, it did something more. I was scared of greenfield. Being responsible for a whole project, what if I got it wrong? Brownfield work actually helped me get over that fear. Nobody was writing the “perfect” code I thought was expected of me. If you feel the same way, perhaps you’ll find some kindred spirits in the codebase you work in.

What has brownfield work taught you that a tutorial never could?