dart_container 1.0.11 copy "dart_container: ^1.0.11" to clipboard
dart_container: ^1.0.11 copied to clipboard

Injection container and app server for Dart. Provides functionality similar to Java's Spring dependency injection and appserver.

Description #

This package provides a dependency injection solution for the Dart language, as well as a application server based on [dart_router_extended].

Features #

  • Simple injection: register an object instance. This will basically be treated as a Singleton object. Each injection call will return the same object
  • Lazy injection: register a builder function producing an object. This function will be called when the first injection will be executed, then the same object will be returned on any subsequent injections
  • Factory injection: register a factory function producing objects on each injection call
  • Qualified name injection: the container supports qualified injection so you can provide a name for your dependency
  • Injection profiles: you can register a certain object for a number of profiles, then inject or don't inject the value according to the selected profile. This feature is helpful if you want to run your application with different injection profiles
  • Value injection: inject simple named values into the container
  • Web server configuration
  • Web routes and controllers support
  • Web routes security using route guard
  • CORS configuration support
  • Scheduled tasks support
  • Eventing support

Usage #

Simple injection #

var myObject = MyClass();
var myProperty = "Prop value";
// Register an object with the container
$().generic(object: myObject)
   // then register a value
   .value("myProperty", myProperty)
   // then register a named object. This also works with builders and factories
   .generic(object: myObject, name: "alias");

// Retrieve object
MyClass injectedObject = $().get();
// Retrieved the qualified object
MyClass injectedObjectAlias = $().get(name: "alias");

// Retrieve object if present
MyClass? injectedObjectIfPresent = $().getIfPresent();
// Retrieve the qualified object 
MyClass? injectedAliasObjectIfPresent = $().getIfPresent(name: "alias");

// You can also use shortcut methods
MyClass injectedObject = $get();
MyClass injectedObjectAlias = $get(name: "alias");
MyClass? injectedObjectIfPresent = $$get();

// Retreieve values
String property = $().getValue("myProperty");
String? propertyIfPresent = $().getValueIfPresent("myProperty");

//or use the shortcut methods
String property = $val("myProperty");
String? propertyIfPresent = $$val("myProperty");

Builder and factory injection #

Both the builder and the factories are practically methods that are used to build the container object, with the difference that with a factory, the method is called every time the object is retrieved from the container, while with the builder it is only built once, then the same instance returned, making it basically a lazy buildable singleton.

class SimpleObj {
    final String timestamp;
    SimpleObj(this.timestamp);
}
// Register with the container
$()
    //Inject the builder function that will only be called once to create the container object
    .generic(builder: () => MyClass())
    .generic(factory: () => SimpleObj(DateTime.now().microsecondsSinceEpoch.toString()));

// Retrieve object
MyClass injectedObject = $().get();
// Produce object using the injected factory
SimpleObj injectedObjectIfPresent = $().getIfPresent();

// You can also use shortcut methods
MyClass injectedObject = $get();
SimpleObj injectedObjectIfPresent = $$get();

Conditional callbacks #

class SimpleObj {
    final String timestamp;
    SimpleObj(this.timestamp);
}
// Register with the container
$()
    //Inject the builder function that will only be called once to create the container object
    .generic(builder: () => MyClass())
    .generic(factory: () => SimpleObj(DateTime.now().microsecondsSinceEpoch.toString()));

// Retrieve object
MyClass injectedObject = $().get();
// Produce object using the injected factory
SimpleObj? injectedObjectIfPresent = $().getIfPresent();

// You can also use shortcut methods
MyClass injectedObject = $get();
SimpleObj? injectedObjectIfPresent = $$get();

// Conditional callback, call some code only if an object is present in the container
Container().ifPresentThen<MyClass>((MyClass obj) {
    print(obj);
});
// Or by using the shortcut method
$then<MyClass>((MyClass obj) {
    print(obj);
});

// Conditional callback. Call some code only if a value is present in the container
$().ifValuePresentThen("valueKey", (value) {
    print(value);
});
// Or by using the shortcut method
$valThen("valueKey", (value) {
    print(value);
});

// Conditional callback with multiple dependencies. The container will invoke the callback
// only if all dependencies are found.
$().ifAllPresentThen([
    Lookup.object(MyClass), 
    Lookup.object(SimpleObject), 
    Lookup.value("valueKey")
    ], (list) {
        MyClass? myClass;
        SimpleObject? simpleObject;
        String? value;
        [myClass, simpleObject, value] = list;
});
// Or by calling the shortcut method
$allThen([
    $look(MyClass), 
    $look(SimpleObject), 
    $lookVal("valueKey")
    ], (list) {
        MyClass? myClass;
        SimpleObject? simpleObject;
        String? value;
        [myClass, simpleObject, value] = list;
});

Using profiles #

var myObject = MyClass();
var myProperty = "Prop value";

// Register with the container
$()
    .generic(object: myObject, profiles: ["test", "run"])
    .value("myProperty", myProperty, profiles: ["test", "run"])
    // Setting the active profile
    .profile("run");

// Retrieve object. The injection always uses the active profile when injecting any registered objects or provided values
// If the object is not present in the container for the active profile, this method will throw an exception
MyClass injectedObject = $().get();
// Retrieve object if present. 
// If the object is not present in the container for the active profile, this method will return null
MyClass? injectedObjectIfPresent = $().getIfPresent();

// Retreieve values. If the value does not exist on the active profile, this method will throw an exception
String property = $().getValue("myProperty");
// If the value does not exist on the active profile, this method will return null
String? propertyIfPresent = $().getValueIfPresent("myProperty");

Injecting objects for interfaces #


class MyInterface {
    void doSomething() {}
}

class MyClass implements MyInterface {
    @override
    void doSomething() {
        print("Something");
    }
}

var myObject = MyClass();

// Register with the container for the interface instead of the type
$().typed(MyInterface, object: myObject);

// If the object is not present in the container for the active profile, this method will throw an exception
MyInterface injectedObject = $().get();
// Retrieve object if present. 
// If the object is not present in the container for the active profile, this method will return null
MyInterface? injectedObjectIfPresent = $().getIfPresent();

Autostartable objects #

Sometimes you might need to run some code, or start a webserver (the build in web server is also an AutoStart implementation). This iw why dart_container implements autostartable functionality.

class AutoStartMock implements AutoStart {
  @override
  void init() {
    print("Init called");
  }

  @override
  void run() {
    print("Run called");
  }
}

$().generic(builder: () => AutoStartMock(), autoStart: true)
  // Once autostart is called, the init method is called first for the AutoStart objects,
  // then the run method is called asynchronously, to avoid blocking the container and any other functionality
  .autoStart();

Scheduled jobs #

Configuring the scheduler

// Sets the timer polling interval to the specified duration
// The polling interval is useful if you have scheduled tasks running at long periods
// of time. Longer periods will lessen the CPU load but will also reduce trigger time accuracy
// The default value, if not specified, is 10 seconds
$().schedulerPollingInterval(Duration(seconds: 1));

// Will delay all tasks from starting by the specified duration
$().schedulerInitialDelay(Duration(seconds: 10));

One time scheduled job

class OneTimeScheduledJob implements ScheduledJob {
  bool hasRun = false;
  @override
  Duration? getDuration() => Duration(seconds: 1);

  @override
  ScheduledJobType getType() => ScheduledJobType.oneTime;

  @override
  void run() {
    hasRun = true;
  }

  @override
  DateTime? getStartTime() => null;
}

// Will run scheduled task after 1 second, as provided by the getDuration implementation
$().schedule(oneTime).autoStart();

Periodic scheduled job

class PeriodicScheduledJob implements ScheduledJob {
  int runTimes = 0;
  @override
  Duration? getDuration() => Duration(seconds: 1);

  @override
  ScheduledJobType getType() => ScheduledJobType.periodic;

  @override
  DateTime? getStartTime() => null;

  @override
  void run() {
    runTimes++;
  }
}

// Will run immediately, then at every 1 second as specified by the getDuration implementation
PeriodicScheduledJob periodic = PeriodicScheduledJob();
$().schedule(periodic).autoStart();

At exact time scheduled job

class AtExactTimeScheduledJob implements ScheduledJob {
  bool ran = false;
  @override
  Duration? getDuration() => null;

  @override
  DateTime? getStartTime() => DateTime.now().add(Duration(seconds: 3));

  @override
  ScheduledJobType getType() => ScheduledJobType.atExactTime;

  @override
  void run() {
    ran = true;
  }
}

AtExactTimeScheduledJob atTime = AtExactTimeScheduledJob();

// Will run after 3 seconds
$().schedulerPollingInterval(Duration(seconds: 1))
  .schedule(atTime)
  .autoStart();

At exact time repeating scheduled job

class AtExactTimeRepeatingScheduledJob extends ScheduledJob {
  bool ran = false;
  @override
  Duration? getDuration() => Duration(seconds: 2);

  @override
  DateTime? getStartTime() => DateTime.now().add(Duration(seconds: 3));

  @override
  ScheduledJobType getType() => ScheduledJobType.atExactTime;

  @override
  void run() {
    ran = true;
  }
}

AtExactTimeScheduledJob atTime = AtExactTimeScheduledJob();

// Will run after 3 seconds, then run every other 2 seconds
$().schedulerPollingInterval(Duration(seconds: 1))
  .schedule(atTime)
  .autoStart();

Web server #

dart_container includes a built in webserver that can be easily configured using Controllers and Routes.

class StausController extends Controller {
  StatusController()
      : super(
        // All the routes under a controller are mounted under the controller prefix
          pathPrefix: "/status",
          routes: [
            GetStatusRoute(),
            PostStatusRoute(),
          ],
          // The guard allows secured access to routes. If you don't want anyone accessing a route or
          // controller, you can implement a guard
          guard: StatusGuard(),
        );
}

class StatusGuard extends RouteGuard {
  @override
  bool isSecure(Request request) {
    return true;
  }
}

class GetStatusRoute extends AbstractRoute {
  GetStatusRoute()
      : super(
        // All the route parsing is fully compatible with shelf_router since that is the actual library used
          ["/<key>"],
          Method.get,
        );
  
  @override
  Function buildHandler() {
    return _respond;
  }

  Response _respond(Request req, String key) {
    return JsonResponse.okJson({key: "requested"});
  }
}

class PostStatusRoute extends AbstractRoute {
  PostStatusRoute()
      : super(
          ["/<key>"],
          Method.post,
        );

  @override
  Function buildHandler() {
    return _respond;
  }

  Response _respond(Request req, String key) async {
    return JsonResponse.ok({key: "posted"});
  }

}

$().webServerConfig(
  // First of all, we need a not found handler
    (req) => Response.notFound(
        "Route not found for ${req.method}:${req.requestedUri}"),
    // the host
    'localhost',
    // and the port
    env.httpPort,
    shared: true,
    profiles: ["test", "run"],
  )
  // Provide the list of controllers
  .controllers([
    StatusController();
  ])
  // and/or provide a list of routes
  .routes([
    GetStatusRoute(),
    PostStatusRoute(),
  ])
  // set the active profile
  .profile("run")
  // the call autostart to boot up the web server
  .autoStart();

  // If you do not want to use the autostartables and still want to boot up the web server, you can
  Future.delayed(Duration.zero, () async => $get<WebServer>().run());
  .

Eventing #

With dart-container you get a simple publish/subscribe framework for passing along messages, and building reactive applications. For the time being, the support is limited to exact matching topics.

Simple topics


// First subscribe to the topic
$().subscribe("topic", (topic, event) {
  print("Got message on topic $topic with event value $event");
});

// Then publish to the topic
$().publishEvent(["topic"], "newValue");

// You can also subscribe and publish to multiple topics

// Publishing to multiple subscribers
$().subscribe("topic1", (topic, event) {
  print("Subscriber 1 : Got message on topic $topic with event value $event");
});

$().subscribe("topic2", (topic, event) {
  print("Subscriber 2 : Got message on topic $topic with event value $event");
});

// or use the shortcut
// $sub("topic2", (topic, event) {
//  print("Subscriber 2 : Got message on topic $topic with event value $event");
//});


// Publish a message to multiple topics.
// In this case, both subscribers will get the new message.
$().publishEvent(["topic1", "topic2"], "newValue");
// or use the shortcut
// $pub(["topic1", "topic2"], "newValue");

Wildcards and subtopics topics


$().subscribe("topic/*", (topic, event) {
  // Will receive messages from both subtopics
});

// Publish message to first subtopic
$().publishEvent(["topic/subtopic1"], "newValue1");
// Publish message to second subtopic
$().publishEvent(["topic/subtopic2"], "newValue2");

$().subscribe("topic/*", (topic, event) {
  // Will receive messages from both subtopics
});

// Publish message to first subtopic
$().publishEvent(["topic/subtopic1", "topic/subtopic1"], "newValue1");
// IMPORTANT: When publishing a value to multiple subtopics, the subscriber is only called once
// with the topic parameter having the value of the first matching topic from the publish list
1
likes
160
points
40
downloads

Publisher

unverified uploader

Weekly Downloads

Injection container and app server for Dart. Provides functionality similar to Java's Spring dependency injection and appserver.

Repository (GitHub)

Documentation

API reference

License

LGPL-3.0 (license)

Dependencies

dart_router_extended

More

Packages that depend on dart_container