Provides base types and utilities for managing the loading and disposal of resources, for example when working with packages that do so via ffi such as flutter_soloud.

Set-up

Just add following dependencies to your pubspec.yaml:

dependencies:
  disposable_resource_management: ^4.0.0

Usage

AsyncDisposableMixin (or DisposableMixin in scenarios where disposal is synchronous) can be used to streamline the implementation of disposable types:

class SomeFFIWrapper with AsyncDisposableMixin {
  final SomeAsyncFFIInteropService _ffi;
  final int _handle;

  SomeFFIWrapper._(int handle, SomeAsyncFFIInteropService ffi)
    : _handle = handle,
      _ffi = ffi;

  Future<void> doSomething() {
    // Since AsyncDisposableMixin implements the isDisposed property for us, we
    // can use it for checks like this to make sure objects are not used after
    // disposal.
    if (isDisposed) {
      throw StateError('Connot use disposed resource.');
    }

    return _ffi.doSomethingWithResource(_handle);
  }

  /// Thanks to [AsyncDisposableMixin] the object will not throw if accidentally
  /// disposed multiple times, as the [onDisposeAsync] logic will only be run
  /// the first time [disposeAsync] is called.
  @protected
  @override
  Future<void> onDisposeAsync() =>
      _ffi.asynchronouslyReleaseUnmanagedResource(_handle);

  static Future<SomeFFIWrapper> create(SomeAsyncFFIInteropService ffi) async {
    final handle = await ffi.asynchronouslyObtainUnmanagedResource();
    return SomeFFIWrapper._(handle, ffi);
  }
}

AsyncResourceManager (or ResourceManager in scenarios where obtaining + releasing the resource is synchronous) can then be used to manage loading and disposal of a resource via tokens, similar to how reference counters work in languages like C++:

class SomeService with AsyncDisposableMixin {
  AsyncResourceToken<SomeFFIWrapper> token;

  SomeService(this.token);

  // ...

  @protected
  @override
  Future<void> onDisposeAsync() => token.disposeAsync();
}

void main() async {
  final ffi = SomeAsyncFFIInteropService();

  final resourceManager = AsyncResourceManager<SomeFFIWrapper>(
    loadResource: () => SomeFFIWrapper.create(ffi),
    releaseResource: (wrapper) => wrapper.disposeAsync(),
  );

  // The resource gets obtained on the first obtainToken() call.
  final service1 = SomeService(await resourceManager.obtainToken());
  final service2 = SomeService(await resourceManager.obtainToken());

  // The resource will not be released yet since service2 still has an
  // un-disposed token.
  await service1.disposeAsync();

  // The resource is now released when service2's token gets disposed.
  await service2.disposeAsync();
  
  // This obtains the resource again
  final token1 = await resourceManager.obtainToken();

  // We can also propagate the token to get another token.
  // This is done synchronously, since propagation isn't allowed for disposed
  // tokens and therefore does not require loading the resource as it is
  // guaranteed to already be loaded.
  final token2 = token1.propagate();

  // The resource will not be released yet because the propagated token2 still
  // is not disposed.
  await token1.disposeAsync();

  // The resource gets released again when all tokens for the resource are
  // disposed.
  await token2.disposeAsync();
}

Libraries

disposable_resource_management
Provides base types and utilities for managing the loading and disposal of resources.