Dependency injection resolution of async factory - dependency-injection

Having some issues with injecting a database connection into a class via the di.dart package. Specifically, resolving an async dependency in the toFactory option.
Both attempts result in the
NoSuchMethodError: Class '_Future' has no instance method 'query'.
error and it's unclear the correct path forward. I would prefer to keep the conn property without being wrapped in a Future. I've attempted doing the unwrapping of the Future in the class constructor but async constructors are not allowed in Dart at this time.
import 'package:postgresql/pool.dart';
import 'package:postgresql/postgresql.dart';
import 'package:di/di.dart';
main() async {
var uri = "postgres://username:password#localhost:5432/database";
var pool = new Pool(uri, minConnections: 2, maxConnections: 5);
await pool.start();
Module module = new Module();
module.bind(TestQuery);
module.bind(TestController);
module.bind(Pool, toValue: pool);
module.bind(Connection, toFactory: (pool) => pool.connect(), inject: [Pool]);
var injector = new ModuleInjector([module]);
var html = await injector.get(TestController).index();
print(html);
}
class BaseQuery {
Connection conn;
BaseQuery(Connection this.conn);
}
class TestQuery extends BaseQuery {
TestQuery(Connection conn) : super(conn); // type '_Future' is not a subtype of type 'Connection' of 'conn' where
run() async {
var results = await conn.query("select 1").toList();
// Do some data manipulation
return results;
}
}
class TestController {
TestQuery testQuery;
TestController(TestQuery this.testQuery);
index() async {
var results = await testQuery.run();
var html = "<pre>" + results.toString() + "</pre>";
return html;
}
}

I can reproduce but when I change the line to
var conn = await injector.get(Connection);
var res = await conn.query("select 1").toList();
it working fine.
I think your code should work as well though.

Related

Error when using argument matcher in mocking methods in dart null safety

I am getting the following error message when using argument matcher, any, when mocking a method in dart tests using mockito in a null safe dart code base.
What steps need to be taken to fix this issue
error:
The argument type 'Null' can't be assigned to the parameter type 'int'.
Test code can be found here:
class MockNumberTriviaRepository extends Mock implements NumberTriviaRespository {}
void main() {
late GetConcreteNumberTrivia usecase;
late MockNumberTriviaRepository mockNumberTriviaRepository;
setUp(() {
mockNumberTriviaRepository = MockNumberTriviaRepository();
usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
});
const tNumber = 1;
const tNumberTrivia = NumberTrivia(number: tNumber, text: "test");
test('should get trivia for the number from repository', () async {
//arrange
when(mockNumberTriviaRepository.getConcreteNumberTrivia(any)).thenAnswer((_) async => const Right(tNumberTrivia));
//act
final result = await usecase.execute(tNumber);
//assert
// UseCase should simply return whatever was returned from the Repository
expect(result, const Right(tNumberTrivia));
// Verify that the method has been called on the Repository
verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
verifyNoMoreInteractions(mockNumberTriviaRepository);
});
}
Implementation code can be found here:
abstract class NumberTriviaRespository {
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number);
Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia();
}
abstract class Failure extends Equatable {
const Failure([List properties = const <dynamic>[]]);
}
class GetConcreteNumberTrivia {
final NumberTriviaRespository respository;
const GetConcreteNumberTrivia(this.respository);
Future<Either<Failure, NumberTrivia>> execute(int number) async {
return await respository.getConcreteNumberTrivia(number);
}
}
class NumberTrivia extends Equatable {
final String text;
final int number;
const NumberTrivia({required this.text, required this.number});
#override
List<Object?> get props => [text, number];
}
Mockito has issues with Dart Null-safety. Please see https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md.
You can override the implementation of your mock class to support a null argument by following the recipes on the link above:
class MockNumberTriviaRepository extends Mock
implements NumberTriviaRespository {
#override
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int? number) =>
super.noSuchMethod(Invocation.method(#getConcreteNumberTrivia, [number]),
returnValue: Future.value(
Right<Failure, NumberTrivia>(NumberTrivia(text: "", number: 1))));
}

flutter dart error when installing google api calendar

I will display a list of events from Google Calendar.
I followed the example already in the following link : How to use Google API in flutter?
and my script is as follows :
import 'package:http/http.dart' as http;
assumed I was logged in.
GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: <String>[
'email',
'https://www.googleapis.com/auth/contacts.readonly',
'https://www.googleapis.com/auth/calendar'
],
);'
class GoogleHttpClient extends http.BaseClient {
Map<String, String> _headers;
GoogleHttpClient(this._headers) : super();
#override
Future<http.StreamedResponse> send(http.BaseRequest request) =>
super.send(request..headers.addAll(_headers)); //have error 'the method 'send' is always abstract in the supertype'
#override
Future<http.Response> head(Object url, {Map<String, String> headers}) =>
super.head(url, headers: headers..addAll(_headers));
}
void getCalendarEvents() async {
final authHeaders = _googleSignIn.currentUser.authHeaders;
final httpClient = new GoogleHttpClient(authHeaders); //have error "The argument type 'Future<Map<String, String>>' can't be assigned to the parameter type 'Map<String, String>'"
var calendar = new Calendar.CalendarApi(new http.Client());
var calEvents = calendar.events.list("primary");
calEvents.then((Calendar.Events events) {
events.items.forEach((Calendar.Event event) {print(event.summary);});
});
}
the above script cannot run because of an error.
the method 'send' is always abstract in the supertype
can someone help me?
If your code is based on How to use Google API in flutter? you'll see that I have a #override Future<StreamedResponse> send(...) in my code.
GoogleHttpClient extends abstract class IOClient that is missing an implementation of send, so the concrete subclass needs to implement it.
That's what the error message is about.
Replace StreamedResponse with IOStreamedResponse
add IOClient library
replace class GoogleHttpClient extends IOClient with class GoogleHttpClient extends http.BaseClient
1 This is error
//have error "The argument type 'Future<Map<String, String>>' can't be assigned to the parameter type 'Map<String, String>'"
fixed: add await ahead
Like below:
final authHeaders = await _googleSignIn.currentUser.authHeaders;
2: Change like below
var calendar = new Calendar.CalendarApi(new http.Client());
to
var calendar = new Calendar.CalendarApi(httpClient);
======> Final:
void getCalendarEvents() async {
final authHeaders = await _googleSignIn.currentUser.authHeaders;
final httpClient = new GoogleHttpClient(authHeaders);
var calendar = new Calendar.CalendarApi(httpClient);
var calEvents = calendar.events.list("primary");
calEvents.then((Calendar.Events events) {
events.items.forEach((Calendar.Event event) {print(event.summary);});
});
}
It worked for me.

How to integrate dependency injection with custom decorators

I'm trying to create a decorator that requires dependency injection.
For example:
#Injectable()
class UserService{
#TimeoutAndCache(1000)
async getUser(id:string):Promise<User>{
// Make a call to db to get all Users
}
}
The #TimeoutAndCache returns a new promise which does the following:
if call takes longer than 1000ms, returns a rejection and when the call completes, it stores to redis (so that it can be fetched next time).
If call takes less than 1000ms, simply returns the result
export const TimeoutAndCache = function timeoutCache(ts: number, namespace) {
return function log(
target: object,
propertyKey: string,
descriptor: TypedPropertyDescriptor<any>,
) {
const originalMethod = descriptor.value; // save a reference to the original method
descriptor.value = function(...args: any[]) {
// pre
let timedOut = false;
// run and store result
const result: Promise<object> = originalMethod.apply(this, args);
const task = new Promise((resolve, reject) => {
const timer = setTimeout(() => {
if (!timedOut) {
timedOut = true;
console.log('timed out before finishing');
reject('timedout');
}
}, ts);
result.then(res => {
if (timedOut) {
// store in cache
console.log('store in cache');
} else {
clearTimeout(timer);
// return the result
resolve(res);
}
});
});
return task;
};
return descriptor;
};
};
I need to inject a RedisService to save the evaluated result.
One way I could inject Redis Service in to the UserService, but seems kind ugly.
You should consider using an Interceptor instead of a custom decorator as they run earlier in the Nest pipeline and support dependency injection by default.
However, because you want to both pass values (for cache timeout) as well as resolve dependencies you'll have to use the mixin pattern.
import {
ExecutionContext,
Injectable,
mixin,
NestInterceptor,
} from '#nestjs/common';
import { Observable } from 'rxjs';
import { TestService } from './test/test.service';
#Injectable()
export abstract class CacheInterceptor implements NestInterceptor {
protected abstract readonly cacheDuration: number;
constructor(private readonly testService: TestService) {}
intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
// Whatever your logic needs to be
return call$;
}
}
export const makeCacheInterceptor = (cacheDuration: number) =>
mixin(
// tslint:disable-next-line:max-classes-per-file
class extends CacheInterceptor {
protected readonly cacheDuration = cacheDuration;
},
);
You would then be able to apply the Interceptor to your handler in a similar fashion:
#Injectable()
class UserService{
#UseInterceptors(makeCacheInterceptor(1000))
async getUser(id:string):Promise<User>{
// Make a call to db to get all Users
}
}

Initialize class with asynchronous variable

How can I initialize a class with asynchronous variables so that they are set before the class is used? I have my class currently just calling an async init function but I would have to call that separately to wait for it to finish:
class Storage {
String imageDirectory;
String jsonDirectory;
SharedPreferences instance;
String uuid;
init() async {
imageDirectory = '${(await getApplicationDocumentsDirectory()).path}/image_cache/';
jsonDirectory = '${(await getApplicationDocumentsDirectory()).path}/json_cache/';
instance = await SharedPreferences.getInstance();
uuid = instance.getString("UUID");
}
}
Is there a better way to do this?
You might hope that you could have async factory constructors, but they aren't allowed.
So one solution is a static getInstance(), for example:
class Storage {
static Future<Storage> getInstance() async {
String docsFolder = (await getApplicationDocumentsDirectory()).path;
return new Storage(
docsFolder + '/image_cache/',
docsFolder + '/json_cache/',
(await SharedPreferences.getInstance()).getString('UUID'));
}
String imageDirectory;
String jsonDirectory;
String uuid;
Storage(this.imageDirectory, this.jsonDirectory, this.uuid);
}
You could pass parameters into getInstance and thus into the constructor, as required. Call the above with:
Storage s = await Storage.getInstance();

Generate one file for a list of parsed files using source_gen in dart

I have a list of models that I need to create a mini reflective system.
I analyzed the Serializable package and understood how to create one generated file per file, however, I couldn't find how can I create one file for a bulk of files.
So, how to dynamically generate one file, using source_gen, for a list of files?
Example:
Files
user.dart
category.dart
Generated:
info.dart (containg information from user.dart and category.dart)
Found out how to do it with the help of people in Gitter.
You must have one file, even if empty, to call the generator. In my example, it is lib/batch.dart.
source_gen: ^0.5.8
Here is the working code:
The tool/build.dart
import 'package:build_runner/build_runner.dart';
import 'package:raoni_global/phase.dart';
main() async {
PhaseGroup pg = new PhaseGroup()
..addPhase(batchModelablePhase(const ['lib/batch.dart']));
await build(pg,
deleteFilesByDefault: true);
}
The phase:
batchModelablePhase([Iterable<String> globs =
const ['bin/**.dart', 'web/**.dart', 'lib/**.dart']]) {
return new Phase()
..addAction(
new GeneratorBuilder(const
[const BatchGenerator()], isStandalone: true
),
new InputSet(new PackageGraph.forThisPackage().root.name, globs));
}
The generator:
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
import 'package:glob/glob.dart';
import 'package:build_runner/build_runner.dart';
class BatchGenerator extends Generator {
final String path;
const BatchGenerator({this.path: 'lib/models/*.dart'});
#override
Future<String> generate(Element element, BuildStep buildStep) async {
// this makes sure we parse one time only
if (element is! LibraryElement)
return null;
String libraryName = 'raoni_global', filePath = 'lib/src/model.dart';
String className = 'Modelable';
// find the files at the path designed
var l = buildStep.findAssets(new Glob(path));
// get the type of annotation that we will use to search classes
var resolver = await buildStep.resolver;
var assetWithAnnotationClass = new AssetId(libraryName, filePath);
var annotationLibrary = resolver.getLibrary(assetWithAnnotationClass);
var exposed = annotationLibrary.getType(className).type;
// the caller library' name
String libName = new PackageGraph.forThisPackage().root.name;
await Future.forEach(l.toList(), (AssetId aid) async {
LibraryElement lib;
try {
lib = resolver.getLibrary(aid);
} catch (e) {}
if (lib != null && Utils.isNotEmpty(lib.name)) {
// all objects within the file
lib.units.forEach((CompilationUnitElement unit) {
// only the types, not methods
unit.types.forEach((ClassElement el) {
// only the ones annotated
if (el.metadata.any((ElementAnnotation ea) =>
ea.computeConstantValue().type == exposed)) {
// use it
}
});
});
}
});
return '''
$libName
''';
}
}
It seems what you want is what this issue is about How to generate one output from many inputs (aggregate builder)?
[Günter]'s answer helped me somewhat.
Buried in that thread is another thread which links to a good example of an aggregating builder:
1https://github.com/matanlurey/build/blob/147083da9b6a6c70c46eb910a3e046239a2a0a6e/docs/writing_an_aggregate_builder.md
The gist is this:
import 'package:build/build.dart';
import 'package:glob/glob.dart';
class AggregatingBuilder implements Builder {
/// Glob of all input files
static final inputFiles = new Glob('lib/**');
#override
Map<String, List<String>> get buildExtensions {
/// '$lib$' is a synthetic input that is used to
/// force the builder to build only once.
return const {'\$lib$': const ['all_files.txt']};
}
#override
Future<void> build(BuildStep buildStep) async {
/// Do some operation on the files
final files = <String>[];
await for (final input in buildStep.findAssets(inputFiles)) {
files.add(input.path);
}
String fileContent = files.join('\n');
/// Write to the file
final outputFile = AssetId(buildStep.inputId.package,'lib/all_files.txt');
return buildStep.writeAsString(outputFile, fileContent);
}
}

Resources