Everyone have used import
to use classes from other files, but Dart also offers export
which allows access to files from other classes in a slightly different way.
export
is widely used in Dart packages for creating APIs. It determines the visibility of classes or files to the client, similar to the public and private access modifiers in Java, but implemented differently in Dart.
However, in this blog, we'll be using export
for a different purpose—a trick for which this substack is created. 😊
We’ll use export
for incremental migration of classes or widgets in Flutter.
Let's say we have a widget called UserAvatar
in a file called user_avatar.dart
:
And this UserAvatar
is being used in 50 files across our app, such as chat_list.dart
and task_assignee.dart
.
Below is the import structure diagram.
New Design Requirements
Now, we have completely new design requirements for UserAvatar
, so replacing them all at once is not ideal. This is because it would be difficult to review pull requests and there would be a lot of testing for QA. Therefore, we are doing this migration incrementally, one screen or widget at a time.
One simple way to do this is to create the NewUserAvatar
in the user_avatars.dart
file and replace all places where UserAvatar
is used with NewUserAvatar
.
If you can do this, then it is great. But if you have a team of more than two people, then chances are that you might end up in a lot of merge conflicts. This usually happens when:
The
user_avatars.dart
file is too large and contains basic utility widgets, which everyone is making changes to.Someone working on a different feature in files where
user_avatars.dart
is used, such aschat_list.dart
andtask_assignee.dart
.
One solution that comes to mind is to create a new_user_avatars.dart
file and add a new NewUserAvatar
widget there, and replace it everywhere where the old UserAvatar
is used.
This approach has one issue, though. We must import new_user_avatars.dart
in all of those files. And as I mentioned above, someone might be building a feature in parallel on those files, we might get conflicts.
And believe me, most of the annoying merge conflicts happen in imports only.
And this is where export
helps. Instead of importing the new_user_avatars.dart
file, we export
it from the user_avatars.dart
file and just replace UserAvatars
with NewUserAvatars
in all those files. Let me show you.
First, we create new_user_avatars.dart
:
And then we use export
in the user_avatars.dart
file:
And now, replace it in all usage files:
And this is how our final structure looks like:
That’s it.
We've used export
to migrate classes in Dart, making it easy to replace all UserAvatars
with NewUserAvatars
without changing imports. Once all UserAvatars
have been replaced, we can remove that from user_avatars.dart
if needed.
Few Things to Note
Obviously
NewUserAvatar
is not a meaningful name. I kept it here for simplicity. You need to have a better name for this.We can't completely avoid conflicts. It's likely that if someone is editing the
user_avatars.dart
file, we may have a conflict with other imports or in ourexport
line. But the good news is that it'll only happen in one file. By taking this approach, we've reduced the potential for conflict from fifty files to just one, making it much easier to resolve and fix.You cannot
export
non-null safe file to null-safe file. It will give a lint error.
export
is also useful to create barrel files, which I learned from The Boring Show Ep 58.
Conclusion
To conclude, export
helps us to migrate classes gradually without affecting existing code. This will result in fewer merge conflicts when creating Pull Requests.
I hope this blog was useful for you. If you have any queries or think there's a better way to do this, please let me know in the comments. This will help me learn with all of you.
You got confused and confused me:
either user_avatar.dart or user_avatars.dart