Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/f-person/toor

Compile-time safe & easy dependency management in Dart
https://github.com/f-person/toor

dart dart-package dependency-injection flutter service-locator

Last synced: 24 days ago
JSON representation

Compile-time safe & easy dependency management in Dart

Awesome Lists containing this project

README

        

[![codecov](https://codecov.io/gh/f-person/toor/branch/master/graph/badge.svg)](https://codecov.io/gh/f-person/toor)
[![Pub version](https://img.shields.io/pub/v/toor.svg)](https://pub.dev/packages/toor)
[![GitHub stars](https://img.shields.io/github/stars/f-person/toor?logo=github)](https://github.com/f-person/toor)

# ๐ŸŒฑ What is Toor
Toor makes service locators compile-time safe and easy to manage.

## Table of Contents

- [๐Ÿš€ Getting Started](#-getting-started)
- [โœจ Toor in detail](#-toor-in-detail)
- [Types of locators](#types-of-locators)
- [Factory](#factory)
- [Lazy Singleton](#lazy-singleton)
- [Mutable Lazy Singleton](#mutable-lazy-singleton)
- [Async Factory](#async-factory)
- [Factories with parameters](#factories-with-parameters)
- [Advanced usage](#advanced-usage)
- [Resetting lazy singletons](#resetting-lazy-singletons)
- [Resetting all lazy singletons](#resetting-all-lazy-singletons)
- [Creating new instances of Toor](#creating-new-instances-of-toor)
- [Registering locators with top-level functions](#registering-locators-with-top-level-functions)
- [๐Ÿงช Testing with Toor](#-testing-with-toor)

## ๐Ÿš€ Getting Started

Define your dependencies somewhere in the project.
All global and static variables are lazy by default
so you shouldn't worry about consuming memory that's not used
by registering stuff.

```dart
final toor = Toor.instance;

final httpClientSingleton = toor.registerLazySingleton(
DioHttpClientImpl.new,
);

final authRepositoryFactory = toor.registerFactory(
() => AuthRepositoryImpl(httpClient: httpClientSingleton()),
);
```

After that, you can safely access your registered factories or lazy singletons:
```dart
void authenticate(String email, String password) {
authRepositoryFactory().authenticate(email, password);
}
```

## โœจ Toor in detail
### Types of locators
Toor currently supports two types of objects: factories and lazy singletons.

#### Factory
Factories are locators that are created on each time you get them.

Use `Toor.registerFactory` to create factory locators:

```dart
final toor = Toor.instance;

final authRepositoryFactory = toor.registerFactory(
AuthRepositoryImpl.new,
);
```

#### Lazy Singleton
Lazy singletons are locators that are created only on the first call.
The object, created at the first call, will be returned every time you get
it afterwards.

Use `Toor.registerLazySingleton` to create lazy singleton locators:

```dart
final toor = Toor.instance;

final credentialManager = toor.registerLazySingleton(
CredentialManagerImpl.new,
);
```

#### Mutable Lazy Singleton
In some cases you may need your singletons to be mutable (e. g. flavors). Toor
has mutable singletons for that. Use `Toor.registerMutableLazySingleton` in
order to create a lazy singleton with a mutable value. And, in order to change
the value of the singleton, use `put`:

```dart
final toor = Toor.instance;

final appFlavor = toor.registerMutableLazySingleton(
create: () => Flavor.dev,
);
print(appFlavor()); // Flavor.dev

appFlavor.put(Flavor.prod);

print(appFlavor()); // Flavor.prod
```

When calling `reset` on mutable singletons, the original value (from `create`)
is assigned to them.

##### Additional cleanup code with `onDispose`

Toor allows you to pass an optional `onDispose` function
when registering lazy singletons in order to do some
clean-up (e.g. close a socket connection, unsubscribe
from a stream etc).
`onDispose` also gives you access to the current value
of the singleton.
In order to use it, just pass the `onDispose` argument
when calling `registerLazySingleton`:

```dart
final toor = Toor.instance;

final permissionManager = toor.registerLazySingleton(
PermissionManagerImpl.new,
onDispose: (manager) {
manager.dispose();
},
);
```

#### Async Factory
Async factories are locators that are asynchronously created each time you get them.

Use `Toor.registerFactoryAsync` to create async factory locators:

```dart
final toor = Toor.instance;

final dataPersisterFactory = toor.registerFactoryAsync(
() async => SharedPreferencesDataPersister(
sharedPreferences: await SharedPreferences.getInstance(),
),
);
```

Await the creation of your factory to obtain and use it:

```dart
final dataPersister = await dataPersisterFactory();
dataPersister.saveData('big');
```

#### Factories with parameters

You can also create factories that accept 1 or 2 parameters:

```dart
final toor = Toor.instance;

final personFactory = toor.registerFactoryWithParam(
(name) => Person(name),
);

final vehicleFactory = toor.registerFactoryWithTwoParams(
(name, productionYear) => Vehicle(name, productionYear);
);

class Person {
const Person(this.name);

final String name;
}

class Vehicle {
const Vehicle(this.name, this.productionYear);

final String name;
final int productionYear;
}

void main() {
// Both are compile-time safe and know about the types.
final driver = personFactory('Doc');
final vehicle = vehicleFactory('DeLorean', 1981);
}
```

Async factories are also supported via `registerFactoryAsyncWithParam`
and `registerFactoryAsyncWithTwoParams`.

### Advanced usage

#### Resetting lazy singletons
You can reset lazy singletons via the `reset` method. This will delete the
current object and create a new one on the next call.

```dart
final toor = Toor.instance;

String value = 'Initial';

final lazySingleton = toor.registerLazySingleton(
() => value,
);

// Even though we change the `value` variable here,
// `lazySingleton`s value will remain 'Initial'.
value = 'Changed';

print(lazySingleton()); // 'Initial'

// Once we reset `lazySingleton`, it's value will be 'Changed' on the next call.
lazySingleton.reset();

print(lazySingleton()); // 'Changed'
```

#### Resetting all lazy singletons
Toor lets you reset all lazy singletons at once via the `reset` method on its
instance. This will call `reset` on every lazy singleton, registered with it.

```dart
final toor = Toor.instance;

String value = 'Initial';

final lazySingleton = toor.registerLazySingleton(
() => value,
);

value = 'Changed';

print(lazySingleton()); // 'Initial'

// Once we reset `toor`, all lazy singletons, registered via
// `toor.registerLazySingleton` will be reset.
toor.reset();

print(lazySingleton()); // 'Changed'
```

#### Creating new instances of Toor
You may want to create several instances of Toor, independent of each other.
The `Toor.instance` getter will return the default instance but you don't
have to use it. You can create new instances of Toor via `Toor.newInstance()`.
You may want to do this in order to reset lazy singletons, related to a
single domain (e.g. reset all singletons that hold user data on logout).

```dart
final authToor = Toor.newInstance();
final analyticsToor = Toor.newInstance();

final credentialManager = authToor.registerLazySingleton(
CredentialManagerImpl.new,
);

final sessionRecorder = authToor.registerLazySingleton(
SessionRecorderImpl(upload: false),
);

void logout() {
// `credentialManager` will be reset, however `sessionRecorder` won't, since
// it's registered in `analyticsToor`, not `authToor`.
authToor.reset();
}
```

#### Registering locators with top-level functions
Toor provides several ways to register a locator in the
global toor instance:

```dart
// You can use `Toor.instance` getter and then call
// `registerLazySingleton` or any other method on it.
final toor = Toor.instance;

final httpClientSingleton = toor.registerLazySingleton(
DioHttpClientImpl.new,
);

// Or you can use a top-level function with the same name
// which basically will call the method on `Toor.instance`.
final httpClientSingleton = registerLazySingleton(
DioHttpClientImpl.new,
);
```

## ๐Ÿงช Testing with Toor
Sometimes, you need different (e.g. mock) objects to be created in tests.
There are two ways to achieve that with Toor:
1. Deciding what to register based on some variables / other toor singletons
(e. g. flavor):
```dart
final toor = Toor.instance;

final authManager = toor.registerLazySingleton(
flavor.isTesting ? MockAuthManager() : AuthManagerImpl(),
);
```
2. Overriding registered objects via `override` which is
available in `toor_test`. The `override` method is annotated with
`visibleForTesting` since it's intended to be used only in tests:
```dart
// dependencies.dart
import 'package:toor/toor.dart';

final toor = Toor.instance;

final authManager = toor.registerLazySingleton(
AuthManagerImpl.new,
);
```

```dart
// app_test.dart
import 'package:toor/toor_test.dart';

void main() {
setUpAll(() {
authManager.override(() => MockAuthManager());
});
}
```