8

My flutter project depends on several local flutter and dart packages to keep things separated and clean. My folder structure is like this:

main-flutter-project
│  lib
|  test
│  pubspec.yaml
│
└── local-packages
│   └── dart-package-1
│   │     pubspec.yaml
│   │
│   └── flutter-package-1
│   │     pubspec.yaml
│   │
│   └── flutter-package-2
│         pubspec.yaml
...

Each local package is self contained and can be maintained without touching the main project.

This structure means that I have many pubspec.yaml files where I have to keep the dependencies updated. When I use e.g. the bloc libaray bloc: ^7.2.1 in say 5 packages, I have to update the version in each pubspec file separately when a new version is released.

Is there a possibility to specify those shared package dependency versions in only one place where the other pubspec.yaml files refer to?

I've seen this e.g. with Maven where you can specify a property <junit.version>4.12</junit.version> and access it from somewhere else <version>${junit.version}</version>.

tmaihoff
  • 2,867
  • 1
  • 18
  • 40

1 Answers1

20

We were solving a similar problem.

AFAIK, there's no built-in or recommended way to do this, so we were inventing some hacks.

In our case, we have core package that has some shared functionality and common dependencies, if you don't have it, you can still create an artificial one, let's say, shared_dependencies package, and specify all the shared dependencies there.

Now, let's say, package foo depends on shared_dependencies package, and there's dependency bar defined in shared_dependecies package that foo needs to use. There are some ways to do that:

  1. Import dependency directly. Since foo depends transitively on bar, you can just write import package:bar/bar.dart and it will work. It's not the best way though:

    • it's bad practice to import transitive dependency (there's even a linter rule for that);
    • auto-import won't work;
  2. Export package in shared_dependencies package. I.e. shared_dependencies.dart can contain the following lines:

    export 'package:bar/bar.dart'
    

    That means that in your foo package you can just write import 'shared_dependencies/shared_dependencies.dart' and get access to bar content.

    Pros:

    • auto-imports work.

    Contras:

    • if you export several packages, there can be name conflicts (you'll have to hide some names in export);
    • if foo package depends on one bar package only, it could be weird to import all shared_dependencies.
  3. Export in separate libraries of shared_dependencies package. You can group some related packages together in different files, e.g.:

    bar.dart:

    export 'package:bar/bar.dart'
    

    bloc.dart:

    export 'package:bloc_concurrency/bloc_concurrency.dart';
    export 'package:flutter_bloc/flutter_bloc.dart';
    

    In that case, if you need bar package in foo, you write import 'package:shared_dependencies/bar.dart'; if you need bloc, you write import 'package:shared_dependencies/bloc.dart'. Auto-imports work as well.

  4. Add direct dependency to foo package, but don't specify version constraints:

    bar:
    

    This basically means that you need any bar package, but since foo also depends on shared_dependencies, its constraints will be taken into account. This may be needed if you're using some executables from bar package, as there's a limitation in Dart SDK that doesn't allow to run executables in transitive dependencies.

In our project, we ended up using 2 for the most commonly used packages, 3 for other packages, and 4 for packages with executables that we need to run.

Kirill Bubochkin
  • 5,868
  • 2
  • 31
  • 50
  • Thank you very much for that detailed answer. That definetly helps a lot! Smart approach, providing it via a shared dependencies package. I will reward you the bounty as soon as possible – tmaihoff Jan 20 '22 at 21:42
  • one question to `3.`: Just putting a comment above one or more export statements groups them together by the comment name? Did I understand that correctly? You say you could import all bloc dependencies by importing `shared_dependencies/bloc.dart` but you don't export `bloc.dart` in that example. You only put `bloc.dart` as a comment above the bloc-related exports. That seems odd and I feel like I haven't understood `3.` it correctly – tmaihoff Jan 25 '22 at 13:39
  • @tmaihoff I meant it to be in different files, probably the formatting was confusing. I've updated this point, hopefully, it's more clear now. – Kirill Bubochkin Jan 25 '22 at 16:22
  • ah I see, yeah that makes much more sense. Got it now, thanks! – tmaihoff Jan 25 '22 at 20:54
  • @KirillBubochkin How to get method `4:` running with build_runner? I have a local package for my dev_dependencies and can import it in the `pubspec.yaml` file of the parent like: `dev_dependencies: my_dev_dependency_package: path: '../path/to/package'` But this gives me the `Cannot run executables in transitive dependencies.` Error. I'm not really getting how to apply Method `4.` there. – vander2675 May 18 '22 at 07:56
  • @vander2675 did you add the dependency to the main packages as well? – Kirill Bubochkin May 18 '22 at 08:28
  • @KirillBubochkin no only to the dev_dependencies – vander2675 May 18 '22 at 13:33
  • 1
    @vander2675 yeah, with dev_dependencies it won't work, since they apply only to the package that declared them, they are not added to other packages even as transitive dependencies. – Kirill Bubochkin May 18 '22 at 19:58