lite_ref 0.8.1 copy "lite_ref: ^0.8.1" to clipboard
lite_ref: ^0.8.1 copied to clipboard

A lightweight dependency injection package with support for overriding for testing.

Overview #

Lite Ref is a lightweight dependency injection library for Dart and Flutter.

Installation #

dart pub add lite_ref
copied to clipboard

Why Lite Ref? #

  • Fast: Doesn't use hashmaps to store instances so it's faster than all other DI libraries.
  • Safe: Uses top level variables so it's impossible to get a NOT_FOUND error.
  • Lightweight: Has no dependencies.
  • Simple: Easy to learn with a small API surface

Scoped Refs #

A ScopedRef is a reference that needs a build context to access its instance. This is an alternative to Provider for classes that don't rebuild widgets. eg: Controllers, Repositories, Services, etc.

  • Wrap your app or a subtree with a LiteRefScope:

    runApp(
      LiteRefScope(
        child: MyApp(),
      ),
    );
    
    copied to clipboard
  • Create a ScopedRef.

    final settingsServiceRef = Ref.scoped((ctx) => SettingsService());
    
    copied to clipboard
  • Access the instance in the current scope:

    This can be done in a widget by using settingsServiceRef.of(context) or settingsServiceRef(context).

    class SettingsPage extends StatelessWidget {
      const SettingsPage({super.key});
    
      @override
      Widget build(BuildContext context) {
        final settingsService = settingsServiceRef.of(context);
        return Text(settingsService.getThemeMode());
      }
    }
    
    copied to clipboard
  • Override it for a subtree:

    You can override the instance for a subtree by using overrideWith. This is useful for testing. In the example below, all calls to settingsServiceRef.of(context) will return MockSettingsService.

    LiteRefScope(
      overrides: {
        settingsServiceRef.overrideWith((ctx) => MockSettingsService()),
      },
      child: MyApp(),
    ),
    
    copied to clipboard

A ScopedFamilyRef is used when you need to create a unique instance for different keys. This is useful for creating multiple instances of the same class with different configurations.

  • Create a ScopedFamilyRef.

    final postControllerRef = Ref.scopedFamily((ctx, String key) {
      return PostController(key)..fetch();
    });
    
    copied to clipboard
  • Access the instance in the current scope:

    This can be done in a widget by using postController.of(context, key) or postController(context, key).

    class PostsPage extends StatelessWidget {
      const PostsPage({required this.keys, super.key});
    
      final List<String> keys;
    
      @override
      Widget build(BuildContext context) {
        return ListView.builder(
          itemBuilder: (context, index) {
            final post = postControllerRef.of(context, keys[index]);
            return Text(post?.title ?? 'Loading...');
          },
        );
      }
    }
    
    copied to clipboard

Disposal #

When a ScopedRef provides a ChangeNotifier, ValueNotifier or a class that implements Disposable, it will automatically dispose the instance when all the widgets that have access to the instance are unmounted.

In the example below, the CounterController will be disposed when the CounterView is unmounted.

class CounterController extends ChangeNotifier {
  var _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }

  void decrement() {
    _count--;
    notifyListeners();
  }
}

final countControllerRef = Ref.scoped((ctx) => CounterController());

class CounterView extends StatelessWidget {
  const CounterView({super.key});

  @override
  Widget build(BuildContext context) {
    final controller = countControllerRef.of(context);
    return ListenableBuilder(
      listenable: controller,
      builder: (context, snapshot) {
        return Text('${controller.count}');
      },
    );
  }
}
copied to clipboard

Click here for a flutter example with testing. #

Global Singletons and Transients #

  • Create a singleton:

    final dbRef = Ref.singleton(() => Database());
    
    assert(dbRef.instance == dbRef.instance);
    
    copied to clipboard
  • Use it:

    final db = dbRef.instance; // or dbRef()
    
    copied to clipboard
  • Override it (for testing):

    dbRef.overrideWith(() => MockDatabase());
    
    copied to clipboard
  • Freeze it (disable overriding):

    // overrideWith is marked as @visibleForTesting so this isn't really necessary.
    dbRef.freeze();
    
    copied to clipboard
  • Create a transient instance (always return new instance):

    final dbRef = Ref.transient(() => Database());
    
    assert(dbRef.instance != dbRef.instance);
    
    copied to clipboard
  • Create a singleton asynchronously:

    final dbRef = Ref.asyncSingleton(() async => await Database.init());
    
    copied to clipboard
  • Use it:

    final db = await dbRef.instance;
    
    copied to clipboard
  • Use it synchronously:

    // only use this if you know the instance is already created
    final db = dbRef.assertInstance;
    
    copied to clipboard
24
likes
150
points
262
downloads

Publisher

verified publishernosy.dev

Weekly Downloads

2024.09.13 - 2025.03.28

A lightweight dependency injection package with support for overriding for testing.

Repository (GitHub)

Topics

#dependency-injection #provider #di

Documentation

API reference

License

MIT (license)

Dependencies

basic_interfaces, flutter, lite_ref_core

More

Packages that depend on lite_ref