vessel 3.1.0 copy "vessel: ^3.1.0" to clipboard
vessel: ^3.1.0 copied to clipboard

IoC-Container written in pure Dart with Flutter in mind. Provides compile-time checks, typed factories, scoping and much more.

IoC-Container for Dart and Flutter

How is it different from Riverpod? #

  • It's not a state management solution
  • Only 2 provider types
  • No need to specify dependencies to achieve correct scoping

How is it different from GetIt? #

  • Type-safe factories (no more param1 and param2)
  • Providers are registered at compile-time
  • Ability to register the same type twice or more in type-safe manner.

Getting started #

Define provider as global variable

class Counter {
    final int count = 0;
}

final counterProvider = Provider((read) => Counter());

Create container - it holds all providables

final container = ProviderContainer();

Read provider

void main() {
    /// Counter is created lazily and cached inside container
    final counter = container.read(counterProvider);
    final sameCounter = container.read(counterProvider);

    print(counter == sameCounter); // true
    print(counter.count); // 0
}

Features #

Providers #

There are 2 types of Providers in vessel:

class Counter {}
class UserViewModel {
    final int userId;
    UserViewModel(this.userId);
}

// Provider
final counterProvider = Provider((_) => Counter());

// Factory provider
final userVmProvider = Provider.factory(
    (_, int userId) => UserViewModel(userId),
);

The difference between these two is that Provider.factory creates providers, while usual providers are self-contained.

Consider Provider usage:

container.read(counterProvider); // Counter instance

And Provider.factory:

final user100Provider = userVmProvider(100);
container.read(user100Provider);

or just:

container.read(userVmProvider(100)) // UserVM.userId === 100

Injecting providers

final cartRepositoryProvider = Provider(
    (_) => CartRepository(),
);

final cartViewModelProvider = Provider.factory((read, int cartId) {
    final repository = read(cartRepository);
    return CartViewModel(
        repository: repository,
        cartId: cartId,
    );
});

Disposing providers

final cartViewModelProvider = Provider(
    (read) =>  CartViewModel(...),
    dispose: (CartViewModel vm) => vm.dispose(),
);

Container has dispose method, which disposes all providers within it.

container.dispose();

Overrides #

You can override any provider with any other provider of compatible type.

    final container = ProviderContainer(
        overrides: [
            userRepositoryProvider.overrideWith(mockUserRepositoryProvider)
        ]
    );

    container.read(userRepositoryProvider); // MockUserRepository

Example

Consider this:

class UserRepository {
    User getById(int id) {
        return User(id: id, isAdmin: false);
    }
}

class UserProfileViewModel {
    final UserRepository repository;
    final int userId;
    
    UserProfileViewModel({
        required this.repository, 
        required this.userId,
    });

    String get isAdmin => repository.getById(userId).isAdmin;
}

final userRepositoryProvider = Provider(
    (_) => UserRepository(),
);

final userProfileVmProvider = Provider.factory(
    (read, int userId) => UserProfileViewModel(
        userId: userId,
        repository: read(userRepositoryProvider),
    )
);

Here is the task: mock UserRepository, so getById always returns admin user. Easy:

class MockUserRepository implements UserRepository {
    User getById(int id) {
        return User(id: id, isAdmin: true);
    }
}


final mockRepositoryProvider = Provider<UserRepository>(() => MockUserRepository());

final containerWithOverride = ProviderContainer(
    overrides: [
        userRepositoryProvider.overrideWith(mockRepositoryProvider),
    ],
);


void main() {
    final profileVm = containerWithOverride.read(userProfileVmProvider(1));
    print(profileVm.isAdmin); // true
}

Scopes #

Providers can be scoped:

final userProvider = Provider((_) => User(...));
final containerRoot = ProviderContainer();
final containerChild = ProviderContainer(
    overrides: [userProvider.scope()],
    parent: containerRoot,
);

void main() {
    final rootUser = containerRoot.read(userProvider);
    final childUser = containerChild.read(userProvider);

    identical(rootUser, childUser); // false
}

provider.scope() is essentially the same as provider.overrideWith(provider), so scoping and overriding are basically the same thing.

Provider becomes scoped, if any of its dependencies* gets scoped. Consider this example:

class Counter {
    final int count;
    Counter(this.count);
}

final provider1 = Provider((_) => Counter(1));
final provider2 = Provider((read) => Counter(read(provider1).count + 1));
final provider3 = Provider((read) => Counter(read(provider2).count + 3));


final container = ProviderContainer();
final containerChild = ProviderContainer.scoped(
    [provider2],
    parent: container, 
);

void main() {
    // now provider3 also scoped inside containerChild
    final instance3 = containerChild.read(provider3); 
    final rootInstance3 = container.read(provider3);

    identical(instance3, rootInstance3); // false


    final instance1 = containerChild.read(provider1);
    final rootInstance1 = container.read(provider1);

    // provider1 doesn't have scoped dependencies, so it doesn't become scoped.
    identical(instance1, rootInstance1); // true
}

Dependencies*

final provider1 = Provider((_) => Counter(1));
final provider2 = Provider((read) => Counter(read(provider1).count + 1));
final provider3 = Provider((read) => Counter(read(provider2).count + 3));
  • provider1 has no dependencies
  • provider2 has single dependency - on provider1.
  • provider3 has 2 dependencies:
  • direct dependency on provider2
  • transitive dependency on provider1 through provider2

Is it production ready? #

Not enough testing have been done to consider this production ready. But I'm going to use it on production project.

Credits #

The whole project inspired by riverpod, created by Remi Rousselet and community.

0
likes
130
pub points
0%
popularity

Publisher

unverified uploader

IoC-Container written in pure Dart with Flutter in mind. Provides compile-time checks, typed factories, scoping and much more.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

meta

More

Packages that depend on vessel