Flutter imports : relative path or package?
FlutterDartFlutter ProviderFlutter Problem Overview
In Flutter, for importing libraries within our own package's lib directory, should we use relative imports
import 'foo.dart'
or package import?
import 'package:my_app/lib/src/foo.dart'
Dart guidelines advocate to use relative imports : >PREFER relative paths when importing libraries within your own package’s lib directory.
whereas Provider package says to always use packages imports :
> * Always use package imports. Ex: import 'package:my_app/my_code.dart'
;
Is there a difference other than conciseness? Why would packages imports would reduce errors over relative imports?
Flutter Solutions
Solution 1 - Flutter
From the same Dart guidelines, further down they give this reason for the relative imports:
> There is no profound reason to prefer the former—it’s just shorter, and we want to be consistent.
Personally, I prefer the absolute method, despite it being more verbose, as it means when I'm importing from different dart files (in other folders), I don't have to work out where the file to be imported is, relative to the current file. Made-up example:
I have two dart files, at different folder levels, that need to import themes/style.dart
:
One is widgets/animation/box_anim.dart
, where the relative path import would be:
import '../../themes/style.dart';
The other is screens/home_screen.dart
with the relative import:
import '../themes/style.dart';
This can get confusing, so I find it better to just use the absolute in both files, keeping it consistent:
import 'package:myapp/themes/style.dart';
And just stick that rule throughout. So, basically, whatever method you use - Consistency is key!
The Linter for Dart package, also has something to say about this, but is more about the Don'ts of mixing in the '/lib' folder:
> DO avoid relative imports for files in lib/. > > When mixing relative and absolute imports it's possible to create > confusion where the same member gets imported in two different ways. > An easy way to avoid that is to ensure you have no relative imports > that include lib/ in their paths.
Solution 2 - Flutter
prefer_relative_imports is recommended in official Effective Dart guide
TLDR; Choose the one you prefer, note thatFirst of all, as mentioned in this answer, Provider do not recommands package imports anymore.
Dart linter provides a list of rules, including some predefined rulesets :
- pedantic for rules enforced internally at Google
- lints or even flutter_lints (previously effective_dart) for rules corresponding to the Effective Dart style guide
- flutter for rules used in flutter analyze
Imports rules
There is actually more than two opposites rules concerning imports :
- avoid_relative_lib_imports, enabled in pedantic and lints rulesets, basically recommend to avoid imports that have 'lib' in their paths.
The two following are the one you mention :
-
prefer_relative_imports, enabled in no predefined rulesets, but recommended in Effective Dart guide in opposition to :
-
always_use_package_imports, enabled in no predefined rulesets. Which means that it is up to you and to your preferences to enable it (be careful, it is incompatible with the previous rule)
Which one should I choose?
Choose the rule you want ! It will not cause any performance issue, and no rule would reduce errors over the other. Just pick one and make your imports consistent across all your project, thanks to Dart linter.
I personnaly prefer using prefer_relative_imports, as it is recommended by Dart team, with this VSCode extension which automatically fix and sort my imports.
Solution 3 - Flutter
Provider do not need packages imports anymore.
This was a workaround to an old Dart bug: https://stackoverflow.com/questions/47121411/flutter-retrieving-top-level-state-from-child-returns-null/47142052#47142052
TL;DR, by mixing relative and absolute imports, sometimes Dart created a duplicate of the class definition.
This led to the absurd line that is:
import 'package:myApp/test.dart' as absolute;
import './test.dart' as relative;
void main() {
print(relative.Test().runtimeType == absolute.Test().runtimeType); // false
}
Since provider
relies on runtimeType
to resolve objects, then this bug made provider
unable to obtain an object in some situations.
Solution 4 - Flutter
This question already has good answers, but I wanted to mention an insanely annoying and hard-to-find problem I experienced with unit testing that was caused by a relative import.
The expect
fail indicator for an exception-catching expect block
expect(
() => myFunction,
throwsA(isA<InvalidUserDataException>())
);
was showing the actual
result as exactly the same as the expected
result, and zero indication of why it's failing.
After massive trial-and-error, the issue was because the expected InvalidUserDataException
(a custom-made class) was being imported to the test file in RELATIVE format vs PACKAGE format.
To find this, I had to compare side-by-side, line-by-line, call-by-call between this test file and another test file that uses the exact same exception expecters (It's lucky, we had this), and just by chance, I happened to scroll to the top of this file's imports and see the blue underline saying prefer relative imports to /lib directory
.
No, they're not preferred; they're necessary, because the moment I changed that to a PACKAGE (absolute) import, everything suddenly started working.
What I learned from this is: Use absolute imports for test files (files outside the lib
directory)
- e.g. inside of
src/test/main_test.dart
- DON'T: use
import '../lib/main.dart'
- DO: use
package:my_flutter_app/main.dart
- DON'T: use
Maybe other people knew this already, but I didn't, and I couldn't find anything online with searches about this issue, so I thought I would share my experience that might help others who got stuck around this.
Does anyone know why this happens?
Edit: For context, this happened while using Flutter 2.1.4 (stable) with Sound Null Safety