Dart Package Tutorial – Getting Started

Learn how to create your first Dart package using test-driven development, generate documentation and publish it to pub.dev. By Agustinus Theodorus.

Leave a rating/review
Download materials
Save for later
Share

Often, there are features you want to include in your app, but writing the code can be tedious or difficult. So, you hop on to pub.dev and get the package that can help you add that feature, drastically saving your time and energy.

Packages help you extract the code you need so that it can be reused in the same or multiple apps. Packages are also a way you can contribute to the open source community.

Wouldn’t it be great to create your very own package and share it with the community? With this tutorial, you can! Along the way, you’ll learn how to:

  • Create your first Dart package.
  • Publish your package to pub.dev.
  • Import your package into your Flutter app.

Getting Started

Start by clicking the Download Materials button at the top or bottom of the page to download the starter project.

In this tutorial, you’ll use Visual Studio Code, but you can also continue with Android Studio or IntelliJ IDEA. You’ll focus more on the Dart package development and end with integrating your package into your Flutter app (Android/iOS).

In the example, you’ll use Genderize.io, an Open API for predicting gender based on names.

Open your project in Visual Studio Code or Android Studio, and install your dependencies. In the terminal, type:

cd flutter
flutter pub get

Press Enter, and check the output:

$ cd flutter
$ flutter pub get
Running "flutter pub get" in flutter...                          2,860ms

Build and run your project.

Starter Genderizio project

The app has an input field to enter a name and a button to genderize it. Enter the name Peter and tap Genderize.

Try to genderize Peter name

As you see, you don’t get a result. This functionality is what you’ll implement later using your newly published Dart package.

Understanding Dart Packages

Dart packages are reusable code published on the Dart package registry. They function as libraries for Dart software development. But there are nuances between Dart packages, specifically those of Flutter as plugins.

Dart Package vs. Flutter Plugin

While both are technically Dart packages, a little nuance differentiates them.

As the name implies, Dart packages are made in pure Dart. You can use Dart packages for both Flutter and server-side Dart. Developing a Dart package is easier than a Flutter plugin because you don’t need to test any platform-specific code. Everything is in Dart!

On the other hand, Flutter plugins are pieces of code that function primarily as part of the mobile app. Flutter plugins usually wrap native Android/iOS code in a Dart package to reuse it within the Flutter app. In this tutorial, you’ll make a Dart package, so your package will be reusable within Flutter or simple Dart scripts.

Knowing When to Create a Dart Package

Flutter has a ton of packages for even the slightest of problems.

For example, if you want to create a native splash, Flutter has a native splash page package ready for you to use. Or, if you want to create launcher images, Flutter has a separate package for that too.

However, when you don’t find a suitable package for your needs, it’s usually because:

  • You’re using a brand-new programming language with little community support.
  • The problem is too technically expensive to implement — say, creating a new machine learning library.
  • It’s a common problem that no one has created a plug-and-play solution for yet.

If you’re experiencing the last issue, you’ve found an excellent opportunity to create a new package and provide a solution to the wider community.

Writing Your First Dart Package

Before writing your Dart package, you must understand the API you’ll use. You’ll write a Dart API wrapper for Genderize.io, which predicts gender based on names. Users can submit names and get an approximate probability of that name’s gender.

The API accepts many parameters, but you’ll only use the name parameter.

Use this API directly to see how it works. Tap the link https://api.genderize.io/?name=peter:

{
  "name": "peter",
  "gender": "male",
  "probability": 0.99,
  "count": 165452
}

You see the results of calling this API. Now, you have to create a wrapper around it in Dart.

Creating a Dart Package

It’s finally time to create your first package. Open the terminal in the root of the starter project, and type:

dart create -t package genderizeio

Press Enter, and check the result:

$ dart create -t package genderizeio
Creating genderizeio using template package...

  .gitignore
  analysis_options.yaml
  CHANGELOG.md
  pubspec.yaml
  README.md
  example/genderizeio_example.dart
  lib/genderizeio.dart
  lib/src/genderizeio_base.dart
  test/genderizeio_test.dart

Running pub get...                     1.9s
  Resolving dependencies...
  Changed 46 dependencies!

Created project genderizeio in genderizeio! To get started, run the following commands:

  cd genderizeio
  dart run example/genderizeio_example.dart

You just created the package! This command uses the Dart template and prepares base package files for you. You must fill them with business logic.

The previous command’s output asks you to run a few more commands, so you’ll do that next. In the terminal, type the following commands:

cd genderizeio
dart run example/genderizeio_example.dart

Here is what’s going on in the commands above:

  1. Changed the working directory to your newly created package.
  2. Run the example project.

Press Enter to execute the commands.

$ dart run example/genderizeio_example.dart
awesome: true

You’ve just executed an example project. It has no special code, so you see the simple message awesome: true. You’ll update this file later to run the Genderizeio package.

Understanding Dart Package Project Structure

The package’s core consists of the following files:

  • lib/genderizeio.dart: Main interface file.
  • lib/src/genderizeio_base.dart: Core business logic file. Everything under the lib/src folder is your private implementation and shouldn’t be imported by consumers directly. You have to export all public classes in the lib/genderizeio.dart file.
  • pubspec.yaml: Dependencies and package metadata file.
  • README.md, CHANGELOG.md: Supporting files and documentation.
  • example/genderizeio_example.dart: The example that imports the library as if it were a package and tests whether the app is running.
  • test/genderizeio_test.dart: For testing the core business logic.
Note: The testing file isn’t essential for releasing a new Dart package, but it’s considered good practice to have a set of tests before deploying your package.

Test-Driven Development of the Dart Package

Note: This section is optional because you don’t need to have tests to publish a Dart package. If you’d like to dive right into package implementation, feel free to skip to the Importing Dependencies section, where you’ll find a project ready.

You’ll use the test-driven development (TTD) process to implement your business logic. It means you must first write your tests. After that, you must write your code so that all the tests pass.

Note: If you’re interested in learning more about TDD, check out the books: Android Test-Driven Development by Tutorials and iOS Test-Driven Development.

Since the package is an API wrapper, you’ll only do unit tests.

In testing, you will:

  1. Create a public interface, GenderizeAPI, to use the package.
  2. Add the method Future GenderizeAPI.send(String name) async to call Genderize.io.
  3. Return the object Genderize with the property gender in case of success or throw an exception in case of error.

Replace test/genderizeio_test.dart with the following code:

import 'package:genderizeio/genderizeio.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'package:http/http.dart' as http;

import 'genderizeio_test.mocks.dart';

@GenerateMocks([http.Client])
void main() {
  group('Genderize.io', () {
    // 1
    final client = MockClient();
 
    // 2
    final genderize = GenderizeAPI(client);
    
    test('Peter is male', () async {

      // 3
      when(
        client.get(Uri.parse('https://api.genderize.io?name=peter')),
      ).thenAnswer(
        (_) async => http.Response(
          '{"name":"peter","gender":"male","probability":0.99,"count":165452}',
          200,
        ),
      );

      // 4
      final result = await genderize.send('peter');

      // 5
      expect(result.gender, 'male');
    });
    
    // 6
    test('API exception', () async {
      when(
        client.get(Uri.parse('https://api.genderize.io?name=')),
      ).thenAnswer(
        (_) async => http.Response(
          '{"error":"Missing \'name\' parameter"}',
          500,
        ),
      );
      final result = genderize.send('');
      await expectLater(
        result,
        throwsException,
      );
    });
  });
}

You’ll see several code issues in the editor. That’s because you haven’t added the dependencies you’ve used. You’ll fix the errors soon by adding the dependencies and generating the mock data.

In the above code, you:

  1. Create an instance of mock http.Client. This class has mocked HTTP functions like get and post generated by build_runner.
  2. Create an instance of an API wrapper based on a mocked http client.
  3. Intercepte the network requests to return the mock data in tests.
  4. Call an API Wrapper with “Peter” as the name parameter.
  5. Test if Peter is male, with a result of “male”.
  6. Test to check whether the wrapper returns an exception in case of error.

Next, you’ll start fixing the errors in the above code.