CodeBlanks Logo Icon CodeBlanks

Putting it together

Combine all expert topics into a working mini app.

Putting it together

In the final lesson we bring all parts together: the FFI service, the code generator, the repository and the DI container are gathered in a MathApp shell. The run method starts the application.

MathApp takes a MathRepository and a BindingGenerator. The run method does addition via the repository and produces binding code via the generator.

This structure provides a clean entry point: main only creates MathApp and calls run. All dependencies are injected via the constructor; testing and extending are easy.

The full flow

Full flow: NativeMathService (FFI) → ServiceContainer (DI) → MathRepository (data) → MathApp (shell) → run (startup). Each layer connects to the previous via an interface.

main wires all parts: create service, register in container, resolve repository, create app, call run. This is a concrete application of dependency injection.

This project combines three expert topics: FFI (native integration), codegen (code generation), architecture (layered design). Each was learned in its own lesson; here they meet in a real application.

Practice Add MathApp and run it

The ServiceContainer is already defined below. Now define a class MathApp with final MathRepository repository; final BindingGenerator generator; MathApp(this.repository, this.generator); void run() { print('app: ${repository.sum(7, 8)}'); print(generator.generate('MathApp')); }. In main create final app = MathApp(repo, gen); and call app.run();.

Starter code

main.dart
import 'dart:ffi';

abstract class MathService {
  int add(int a, int b);
}

typedef NativeAdd = Int32 Function(Int32 a, Int32 b);
typedef DartAdd = int Function(int a, int b);

class NativeMathService implements MathService {
  final String bindingName;
  NativeMathService(this.bindingName);
  @override
  int add(int a, int b) => a + b;
  String describe() => 'native: $bindingName';
}

class NativeBinding {
  final String name;
  const NativeBinding(this.name);
}

class BindingGenerator {
  String generate(String className) => '// generated binding for $className';
}

class MathRepository {
  final MathService service;
  MathRepository(this.service);
  int sum(int a, int b) => service.add(a, b);
}

class ServiceContainer {
  final Map<String, dynamic> _services = {};
  void register(String key, dynamic service) => _services[key] = service;
  T resolve<T>(String key) => _services[key] as T;
}

// define a MathApp class (repository and generator fields; run method)

void main() {
  print('service ready');
  final svc = NativeMathService('native_add');
  print(svc.describe());
  print('result: ${svc.add(2, 3)}');
  final gen = BindingGenerator();
  print(gen.generate('NativeMathService'));
  final container = ServiceContainer();
  container.register('math', NativeMathService('native_add'));
  final repo = MathRepository(container.resolve<NativeMathService>('math'));
  print('repo: ${repo.sum(4, 6)}');
  // create a MathApp; start with run
}

Sample solution

main.dart
import 'dart:ffi';

abstract class MathService {
  int add(int a, int b);
}

typedef NativeAdd = Int32 Function(Int32 a, Int32 b);
typedef DartAdd = int Function(int a, int b);

class NativeMathService implements MathService {
  final String bindingName;
  NativeMathService(this.bindingName);
  @override
  int add(int a, int b) => a + b;
  String describe() => 'native: $bindingName';
}

class NativeBinding {
  final String name;
  const NativeBinding(this.name);
}

class BindingGenerator {
  String generate(String className) => '// generated binding for $className';
}

class MathRepository {
  final MathService service;
  MathRepository(this.service);
  int sum(int a, int b) => service.add(a, b);
}

class ServiceContainer {
  final Map<String, dynamic> _services = {};
  void register(String key, dynamic service) => _services[key] = service;
  T resolve<T>(String key) => _services[key] as T;
}

class MathApp {
  final MathRepository repository;
  final BindingGenerator generator;
  MathApp(this.repository, this.generator);
  void run() {
    print('app: ${repository.sum(7, 8)}');
    print(generator.generate('MathApp'));
  }
}

void main() {
  print('service ready');
  final svc = NativeMathService('native_add');
  print(svc.describe());
  print('result: ${svc.add(2, 3)}');
  final gen = BindingGenerator();
  print(gen.generate('NativeMathService'));
  final container = ServiceContainer();
  container.register('math', NativeMathService('native_add'));
  final repo = MathRepository(container.resolve<NativeMathService>('math'));
  print('repo: ${repo.sum(4, 6)}');
  final app = MathApp(repo, gen);
  app.run();
}

Expected output

service ready
native: native_add
result: 5
// generated binding for NativeMathService
repo: 10
app: 15
// generated binding for MathApp

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 The run method

Question
app.____();
Answer
app.run();

Explanation: the run method starts the application.

2 What does MathApp combine?

  • Only comments
  • Only prints
  • Only imports
  • Repository and generator Correct answer

Explanation: MathApp combines repository and generator in a shell.

3 Match the integration parts

Term Meaning
MathApp Application shell
repository Data layer
generator Generation layer
run() Startup

All lessons in Expert