How to Create Dart Packages for Your Flutter Apps
Tags: flutter, dart
Published: 2020-06-01 06:52:16

---
published: true
title:  How to Create Dart Packages for Your Flutter Apps
description: How to create your own Dart packages from existing apps, and other things you need to know.
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/893o7pcpexvwxl7476g1.png
tags: flutter, dart
canonical_url: https://codewithandrea.com/videos/how-to-create-dart-packages-flutter-apps/
---

[This article was originally published on my website.](https://codewithandrea.com/videos/2020-06-01-how-to-create-dart-packages-flutter-apps/)

[Watch the Video Tutorial on YouTube.](https://youtu.be/ZABb7PTkT58)

In this tutorial I'll show you how to create Dart packages for your Flutter apps, so that you can **improve** and **reuse** your code.

Why is this important?

With large applications, it is challenging to keep folders organized, and minimise inter-dependencies between files and different parts of the app.

Dart packages solve this problem by making apps more **modular** and dependencies more **explicit**.

So if you have a single large application, or multiple apps that need to share some functionality, extracting reusable code into packages is the way forward.

## How this tutorial is organized

We will start with a step-by-step guide and convert a sample BMI calculator application to use **internal** packages **within the same project**.

Then, we will talk about:

- Dealing with existing, large apps
- Reusing packages across multiple apps
- Local vs remove (git) packages
- Versioning packages and the humble changelog

Let's get started!

## Example: BMI calculator
<!-- TODO: point to Initial Project commit -->
*To follow along each step, you can download the [starter project here](https://github.com/bizz84/bmi_calculator_app_flutter).*

Suppose we have a single page BMI calculator app, composed of these four files:

```
lib/
    bmi_calculation_page.dart
    bmi_calculator.dart
    bmi_formatter.dart
    main.dart
```

The most interesting functionality is in `bmi_calculator.dart` and `bmi_formatter.dart`:

```dart
// bmi_calculator.dart
double calculateBMI(double weight, double height) {
  return weight / (height * height);
}
```

```dart
// bmi_formatter.dart
import 'package:intl/intl.dart';

String formattedBMI(double bmi) {
  final formatter = NumberFormat('###.#');
  return formatter.format(bmi);
}
```

The UI is built with a single `BMICalculationPage` widget class. This shows two input text fields for the weight and height, and one output text field for the BMI ([full source here](https://github.com/bizz84/bmi_calculator_app_flutter)):


![BMI calculator screenshot](https://dev-to-uploads.s3.amazonaws.com/i/fh17bqumeum43w8yl07v.png)

This app is simple enough that we can keep all files inside `lib`. But how can we reuse the BMI calculation and formatting logic across other projects?

We could copy-paste `bmi_calculator.dart` and `bmi_formatter.dart` on each new project.

But copy pasting is rarely a good thing. If we want to change the number of decimal places in the formatter code, we have to do it in each project. Not very DRY. 🌵

### Creating a new package

A better approach is to create a new **package** for all the shared code.

In doing this, we should consider the following:

- By convention, all packages should go inside a `packages` folder.
- When starting from a single application, it's simpler to add the new package(s) inside the **same** git repo.
- If we need to share packages across multiple projects, we can move them to a new git repo (more on this below).
- We can **keep multiple packages inside a single repository**. The [FlutterFire](https://github.com/FirebaseExtended/flutterfire) **monorepo** is a good example of this, and I recommend we do the same for simplicity.

----

For this example, we'll add a new package and keep it inside the same git repo.

From the root of your project, we can run this:

```
mkdir packages
cd packages
flutter create --template=package bmi
```

This will create a new Flutter package in `packages/bmi`, but the `main.dart` file with the usual `runApp(MyApp())` code is missing. Instead, we have a `bmi.dart` file with some default boilerplate:

```dart
library bmi;

/// A Calculator.
class Calculator {
  /// Returns [value] plus 1.
  int addOne(int value) => value + 1;
}
```

As we don't need the `Calculator` class, we can replace it with the BMI calculation and formatting code from the main app.

The simplest way to do this is to add all the code from `lib/bmi_calculator.dart` and `lib/bmi_formatter.dart` to `packages/bmi/lib/bmi.dart` (*we will see later on how to have multiple files inside a package*):

```dart
// bmi.dart
library bmi;

import 'package:intl/intl.dart';

double calculateBMI(double weight, double height) {
  return weight / (height * height);
}

String formattedBMI(double bmi) {
  final formatter = NumberFormat('###.#');
  return formatter.format(bmi);
}
```

Note that this code depends on `intl`, so we need to add this to the `pubspec.yaml` file **of our package**:

```yaml
dependencies:
  flutter:
    sdk: flutter
  intl: ^0.16.1
```

### Using the new package

Now that we have a `bmi` package, we need to add it as a dependency to **our app**'s `pubspec.yaml` file:

```yaml
dependencies:
  flutter:
    sdk: flutter
  # Used by bmi_calculation_page.dart
  flutter_hooks: ^0.9.0
  # we no longer need to import intl explicitly, as bmi already depends on it
  bmi:
    path: packages/bmi
```

Here we use a `path` argument to tell Flutter **where** to find our new package. This works **as long as the package lives in the same repo**.

---

<!-- ### Updating the main app to use the new package -->

After running `flutter pub get` (from the root of the project), the package will be installed and we can use it, just like we would do with any other Dart package.

So we can update the imports in our `bmi_calculator_page.dart` from this:

```dart
import 'package:bmi_calculator_app_flutter/bmi_calculator.dart';
import 'package:bmi_calculator_app_flutter/bmi_formatter.dart';
```

to this:

```dart
import 'package:bmi/bmi.dart';
```

And viola! Our code works and we can now remove `bmi_calculator.dart` and `bmi_formatter.dart` from the main app project. 🏁

In summary, to extract existing code to a separate package we have to:

- create a new package and move our code inside it.
- add any dependencies to the `pubspec.yaml` file **for the package**.
- add the new package as a dependency to `pubspec.yaml` **for our application**.
- replace the old imports with the new package where needed.
- delete all the old files.

### Bonus: Adding multiple files to a package with `part` and `part of`

This example is simple enough that we can keep the BMI calculation and formatting code in `bmi.dart`.

But as our package grows, we should split the code into multiple files.

So rather than keeping everything in one file like this:

```dart
// bmi.dart
library bmi;

import 'package:intl/intl.dart';

double calculateBMI(double weight, double height) {
  return weight / (height * height);
}

String formattedBMI(double bmi) {
  final formatter = NumberFormat('###.#');
  return formatter.format(bmi);
}
```

We can move the `calculateBMI` and `formattedBMI` methods in separate files, just like we had them at the beginning:

```dart
// bmi_calculator.dart
part of bmi;

double calculateBMI(double weight, double height) {
  return weight / (height * height);
}
```

```dart
// bmi_formatter.dart
part of bmi;

String formattedBMI(double bmi) {
  final formatter = NumberFormat('###.#');
  return formatter.format(bmi);
}
```

Then, we can update `bmi.dart` to specify its `part`s:

```dart
// bmi.dart
library bmi;

import 'package:intl/intl.dart';

part 'bmi_calculator.dart';
part 'bmi_formatter.dart';
```

A few notes:

- Files declared with `part of` should **not** contain any `import`s, or we'll get compile errors.
- Instead, all `import`s should remain in the main file that specifies all the `part`s.

In essence, we're saying that `bmi_calculator.dart` and `bmi_formatter.dart` are `part of` `bmi.dart`.

When we import `bmi.dart` in the main app, all **public** symbols defined in all its parts will be visible.

In other words, our main app just needs to `import 'package:bmi/bmi.dart';`, and have access to all the methods declared in all its parts.

Job done! You can find the finished project for this tutorial [here](https://github.com/bizz84/bmi_calculator_app_flutter).

> Note: Using `part` and `part of` works well if you have **just a few files** in the same folder. One library that uses this extensively is [flutter_bloc](https://pub.dev/packages/flutter_bloc), where it's common to define **state**, **event** and **bloc** classes together.

> For more complex apps it's advisable (and recommended by the Dart team) to **export** library files instead. See [this official guide on creating packages](https://dart.dev/guides/libraries/create-library-packages) for more details.

> **Top tip**: moving code into packages is a great opportunity to move existing tests, or write new ones.

Creating a package for a simple app is easy enough, but how do we do this when we have complex apps?

## Dealing with existing, large apps

For more complex apps, we can **incrementally** move code into self-contained packages.

This forces us to think harder about the dependencies between packages.

But if there are already a lot of inter-dependencies, how do we get started?

A **bottom-up** approach is most effective. Consider this example:

<!-- replace with diagram -->
```dart
// lib/a.dart
import 'b.dart';
import 'c.dart';

// lib/b.dart
import 'c.dart';

// lib/d.dart
import 'a.dart';
import 'c.dart';

// lib/c.dart
// no imports
```

As we can see, `c.dart` doesn't depend on any files.

So moving `c.dart` into a `c` package would be a good first step.

Then, we could move `b.dart` into a separate `b` package, making sure to add `c` as a dependency:

```yaml
# packages/b/pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  c:
    path: packages/c
```

And we can repeat this process until all dependencies are taken care of.

> It's still our job to decide how many packages to create, and what goes in each one.

## Reusing packages across multiple apps

Up until now we've seen how to create new packages within the same project.

If we want to reuse packages across multiple projects, we can move them to a **common** shared repository.

Using the BMI calculator example, we could update the `pubspec.yaml` file to point to the new repository:

```yaml
dependencies:
  ...
  bmi:
    git:
      url: https://github.com/your-username/bmi
      path: packages/bmi
```

I followed this approach when refactoring my [starter architecture project](https://github.com/bizz84/starter_architecture_flutter_firebase), and ended up with 6 new packages that I now reuse in other projects.

_To learn more about to the package dependencies syntax, [read this page](https://dart.dev/tools/pub/dependencies) on the Dart documentation._

## Local vs remove (git) packages

Dart packages make our code cleaner and increase code reuse, but do they slow us down?

As long as we specify dependencies with `path` in our `pubspec.yaml` file, we can edit, commit and push all our code (main app **and** packages) just as we did before.

But if we move our packages to a separate repo, getting the latest code for our packages becomes more difficult:

```yaml
dependencies:
  ...
  bmi:
    git:
      url: https://github.com/your-username/bmi
      path: packages/bmi
```

That's because `pub` will **cache** packages when we use `git` as a source. Running `flutter pub get` will keep the old cached version, even if we have pushed changes to the package.

As a workaround we can:

- comment out our package
- run `flutter pub get` (to delete it from cache)
- uncomment the package
- run `flutter pub get` again (to get the latest changes)

Repeating these steps every time we make a change is not fun. 🤔

> Bottom line: we get the fastest turnaround time by keeping all our code (apps and packages) **in the same git repo**. That way we can specify package dependencies with `path` and always use the latest code.

## Versioning packages

When we import packages from [`pub.dev`](https://pub.dev/), we normally specify which **version** we want to use.

Each version corresponds to a **release**, and we can preview the **changelog** to see what changes across releases (example changelog from [Provider](https://pub.dev/packages/provider#-changelog-tab-)).

This makes it a lot easier to see what changed and when, and can help pinpoint bugs/regressions to a specific release.

As you develop your own packages, I encourage you to also have a changelog. And don't forget to **update the package version**, which lives at the top of the `pubspec.yaml` file:

```
name: bmi
description: Utility functions to calculate the BMI.
version: 0.0.1
author:
homepage:
```

This is a good and useful habit if other devs will use your code, or you plan to publish your package on [`pub.dev`](https://pub.dev).

## Challenge: Refactor your apps

Time to put things in practice with a challenge.

Try extracting some code from one of your Flutter projects, and move it to one of more packages.

And use this as an opportunity to think about dependencies in your own projects.

## Conclusion

Dart packages are a good way to scale up projects, whether you work for yourself or as part of a big organization.

They make code more modular and reusable, and with clearly defined dependencies.

They also make it easier to **split code ownership** with other collaborators.

You should consider using them, whether you're working on a single large app, or many separate apps.

After all, there's a reason we have an entire package ecosystem on [pub.dev](https://pub.dev). 😎

Happy coding!