Dart Libraries & Keywords

A Flutter Case Study

Lee Phillips
5 min readMay 11, 2023

This case study was inspired by a StackOverflow question that I answered long ago. It recently got a couple of upvotes (over 2 years later!), which convinced me to write this article as a more formal & in-depth educational tool regarding Dart libraries and the related keywords (library, import,export, as, show, hide, part, part of). Let’s dive in!

library

First, what is a Dart library? Well, at it’s core, a library is a collection of code. Libraries are designed to achieve a separation of concerns via modularization & encapsulation. In Dart, a file is, by default, a library. However, the library directive allows you to provide library-level doc comments and metadata annotations. As you may have seen before, it technically allows append a name as well, but this is a legacy feature and is discouraged. The declaration must be at the top of a file.

/// An example library declaration.
@TestOn('browser')
library;

Here, we have named the library example. It is not mandatory to provide a name to the library declaration. The line could read library; and often will, or be omitted entirely. We will see how a named library is used later.

Because of how libraries work in Dart, there is a slight learning curve when coming from other OOP languages though. Variables have library-level privacy as opposed to class-level privacy. This means that private variables declared in a class are still visible to other classes defined in that same file or library. Let’s take a look.

// example.dart

class Example1 {
// private variables start with an underscore(_)
var _value = 42;
var _hasChanged = false;

bool get hasChanged => _hasChanged;

int get value => _value;
set value(int value) {
_value = value;
_hasChanged = true;
}
}

void main() {
final example1 = Example1();

print(example1.value); // 42
example1._value = 31;
print(example1.hasChanged); // false
print(example1.value); // 31
}

Not only can you access _value, but your editor will gladly autocomplete the reference for you. Do note that a class defined outside of this library would not have access to _value. If you are coming from a language like Java or C#, this can be quite jarring, but rest assured, the creators of Dart chose this approach for a reason.

Unlike some OOP languages, such as Java, Dart supports top-level variables & functions, meaning they can be declared outside of a class. By implementing library-level privacy, Dart allows us to create private variables & helper functions that can be used by other top-level functions, as well as any class defined in the library. For example:

// shapes.dart

import 'dart:math';

final _shapes = <Shape>[];

void addShape(Shape shape) => _shapes.add(shape);

double get totalArea => _calculateTotalArea(_shapes);

double _calculateTotalArea(List<Shape> shapes) {
var total = 0.0;

for (final shape in _shapes) {
total += shape.area;
}

return total;
}

abstract class Shape {
const Shape();

double get area;
}

class Rectangle extends Shape {
const Rectangle(this.length, this.width);

final double length, width;

@override
double get area => length * width;
}

class Square extends Rectangle {
const Square(double side) : super(side, side);
}

class Circle extends Shape {
const Circle(this.radius);

final double radius;

@override
double get area => pi * radius * radius;
}

In the preceding terrible example, outside of the library, we only have access to the various shape class definitions, the addShape function, and the totalArea getter.

NOTE: We can also define private classes, as you may have noticed when using StatefulWidget, as well as private constructors.

import, as, show & hide

The import keyword is a familiar one. It allows us to use libraries in other files. We can use relative imports if the library is located in the same package.

import 'package:flutter/material.dart';

// relative import
import '../some/file.dart';

The as keyword allows us to identify a prefix for an imported library. This is primarily to avoid name collisions.

import 'package:example/example.dart' as example;

show and hide allow us to selectively import names from the library. This also helps prevent name collisions, as well as preventing pollution of auto-completion in our editor.

// only import foo
import 'package:example/example.dart' show foo;

// import everything except foo
import 'package:example/example.dart' hide foo;

export

The export directive tells a library to include the namespace of another library when imported. This is most often used to create bottle files that include all necessary libraries for working with a particular feature.

// feature/feature.dart

export 'package:feature/support_class1.dart';
export 'package:feature/support_class2.dart';
export 'package:feature/support_class3.dart';

Now importing feature.dart provides access to all 3 exported support classes without the need to individually import them as well.

part & part of

The part and part of keywords tend to cause the most confusion when it comes to Dart libraries, but once you wrap your head around it, it’s quite simple. Remember that, by default, a Dart file is a library. Therefore, if you want to split your library up across multiple files, you need part and part of. part tells the Dart to include a file as part of the library. Meanwhile part of tells Dart that the file is not a library, but rather part of a library, meaning that it cannot be imported on its own. An example of this that you have probably seen is when using bloc.

// feature_bloc.dart

part 'feature_event.dart';
part 'feature_state.dart';

class FeatureBloc extends Bloc<FeatureEvent, FeatureState> {
...
}
// feature_event.dart

part of 'feature_bloc.dart';

class FeatureEvent {
...
}
// feature_state.dart

part of 'feature_bloc.dart';

class FeatureState {
...
}

The three files are treated as one library, essentially one file. Therefore, when importing feature_bloc.dart, you also have access to the public names contained within feature_event.dart and feature_state.dart. Note that private names are available across all three files.

Conclusion

Thus ends our case study. As you can see, Dart libraries are not as daunting as they may first appear. They may just differ a little from what your used to. Now have a solid grasp of them, go forth and create your own great libraries to share with the community!

If you found this article helpful, please let me know with a clap. Leave comments & questions as well. Don’t forget to follow to keep up with future Flutter & Dart articles, as well other topics. I’ve got some very cool ones lined up to publish soon! Thanks for reading!

--

--

Lee Phillips

Software engineer. Flutter fanatic. I am here to share knowledge & inspire others to create great experiences.