How To Organize Your Widgets In Flutter Apps

Flutter category image

Introduction

This article is about how to organize your widgets in Flutter apps in small, medium, and large apps. Not all of them work in every case and some of them require more attention than others. The results also depends on what you want have.

  • A clear and well-organized structure
  • A simple file structure
  • As few files as possible
  • As few imports as possible

You cannot have everything but I can give you strategies to reach all those goals … but not at the same time.

Let’s jump right in!

The “Chaos” option

While only viable for really small projects, the Chaos option is probably the easiest one to follow. You simply put all widget files in the lib folder of your project.

Plaintext
lib
lib/main.dart
lib/home_screen.dart
lib/data_service.dart
lib/header_bar.dart
lib/footer_bar.dart
lib/intro.dart
lib/primary_button.dart
lib/secondary_button.dart
...

The benefit is clear: No folder structure to find the correct place for a file. Every file contains one widget. Simple and easy to understand.

This obviously only works for the smallest of projects because you will quickly end up with many files in your lib folder. The project tree in your IDE/code editor will be useless very fast and you have to rely on search features.

For beginners it’s a good choice. For team projects it’s not suited at all.

The “Feature” option

If you follow the Clean Architecture approach, you have a folder structure based on features. The approach is then to add a shared folder to all feature folder that contains widgets that are used within that feature. This is what the Feature option is all about.

For widgets that are used across features, you would add a shared folder at the root level.

Plaintext
lib
lib/feature1
lib/feature1/data
lib/feature1/data/...
lib/feature1/domain/
lib/feature1/domain/...
lib/feature1/presentation
lib/feature1/presentation/some_screen.dart
lib/feature1/presentation/...

# A shared folder for widgets used in feature1 only
lib/feature1/presentation/shared
lib/feature1/presentation/shared/header_bar.dart
lib/feature1/presentation/shared/...
lib/feature2
lib/feature2/data
lib/feature2/data/...
lib/feature2/domain/
lib/feature2/domain/...
lib/feature2/presentation
lib/feature2/presentation/some_other_screen.dart
lib/feature2/presentation/...

# A shared folder for widgets used in feature2 only
lib/feature2/presentation/shared
lib/feature2/presentation/shared/footer_bar.dart
lib/feature2/presentation/shared/...

# A global shared folder for widgets used across features
lib/shared
lib/shared/content_container.dart
lib/shared/...

This structure is viable for all project sizes. It’s clear for everyone to what a widget belongs.

The downside is that you and/or your team need to stick to that structure. There are no easy ways to enforce this for Dart projects. When you work on feature3 and import a widget from feature2, it should be moved to the global shared folder. However, there are no built-in tools to help you with that.

Other programming languages like C# offer reflection or packages like ArchUnitNET to create unit tests for that purpose. In Dart, this is not possible in an easy way. So it also depends on you and the team to keep this structure clean.

For small or hobby projects, the maintenance effort of this structure might be an overkill.

The “Private” option

When you want to have fewer widget files, my recommendation is to use private classes inside your widgets. You can combine widgets with the same purpose in one file and make them private (except the parent one). With that approach you can reduce the number of classes by a lot.

Let’s say you have the following widgets:

  • member_card.dart
  • member_card_header.dart
  • member_card_content.dart
  • member_card_footer.dart

They obviously belong together. So my advice would be to put MemberCardHeader, MemberCardContent, and MemberCardFooter in the member_card.dart file, make them private, and use them from inside.

But why private? 💡

Private widgets can only be used in the same file. With that you can make sure that the widget is not used anywhere by accident. If it was a public one, you could import it in any file which renders your folder structure and namespaces useless in my opinion.

But there is a drawback: It only works for widgets that are not used anywhere else. As soon as you use a widget in two or more different widgets, this approach fails.

You might maybe have unit tests with type checks (expect(find.byType(MemberCardFooter), …)). These won’t work anymore. However, you can replace them with find.byKey. But this opens up the task of managing keys in your code…

The “Barrel file” option

To tackle the number of import statements at the beginning of your Dart files, you can use barrel files. A barrel file contains many or all imports and exposes them again. This means that you only need to import the barrel file. Your import section will be much shorter with this approach.

You can do this manually or use one of many packages that help you with that like barrel_files, barreler, or barrel generator.

Most IDEs and code editors support code folding. This feature lets you collapse a region of a code file so that you don’t need to scroll over it. Visual Studio Code supports this for Dart files.

Code folding in Visual Studio Code for import statements
Code folding in Visual Studio Code for import statements

I consider this a good alternative for barrel files with much less effort.

You can also configure Visual Studio Code to automatically collapse the import region by default. Go to File > Preferences > Settings > type “folding” in the search bar and active the option Folding Imports By Default.

Default import folding setting in Visual Studio Code
Default import folding setting in Visual Studio Code

In my opinion code folding is a much better approach than the barrel files.

The “God file” option

It’s like the Barrel option but on steroids. The benefits of the God File are that you can reduce your imports AND you can reduce the number of widget files.

A God file (I came up with the name, it’s not a real thing) contains widgets so you only have to import this one file. By moving all widget classes to one file, you also reduce the number of files in your project by a lot.

Create a file widgets.dart and place all your reusable widgets inside. In your screen or page, you simply import this widgets.dart file and have access to all classes inside.

Obviously, the file will get large quickly. To battle this, you can split it up into buttons.dart, cards.dart, or whatever elements make sense in your specific app case. This is a compromise between large widget files and number of widget files in your project.

Another benefit is that imports are a no-brainer. You can’t mess it up here.

Conclusion

In this article I gave you some tips on how to organize your widgets in a Flutter app. The outcome depends on what you want to achieve. Fewer widget files, an enforced folder structure, or just keep it simple? Pick your choices from these options and create your own rules!


Want More Flutter Content?

Join my bi-weekly newsletter that delivers small Flutter portions right in your inbox. A title, an abstract, a link, and you decide if you want to dive in!

Become A Firebase Expert!

Take a deep dive into Firebase and learn how to really handle their services. This ebook makes you an instant Firebase professional!

Flutter โค๏ธ Firebase

Get started with Firebase and learn how to use it in your Flutter apps. My detailed ebook delivers you everything you need to know! Flutter and Firebase are a perfect match!