I have this code that I've reduced to the file below.
import 'package:hooks_riverpod/hooks_riverpod.dart';
// this class disctates what other classes do
abstract class BaseUtils<T> {
T fromMap(Map<String, dynamic> json);
}
// Model to be used
class SampleModel extends BaseUtils {
late int? id;
SampleModel({
this.id,
});
@override
SampleModel fromMap(Map<String, dynamic> json) {
return SampleModel(
id: json['id'],
);
}
}
// First service
class ServiceA<T extends BaseUtils> {}
final serviceAProvider = Provider<ServiceA>((ref) {
return ServiceA();
});
class ServiceB {
final ServiceA<SampleModel> _service;
ServiceB(this._service);
void func() {
print(_service);
}
}
final serviceBProvider = Provider<ServiceB>((ref) {
final _a = ref.read(serviceAProvider);
//// Error here
return ServiceB(_a);
});
ServiceB
depends on ServiceA
to get data in form of SampleModel
.
However I get an IDE error The argument type 'ServiceA<BaseUtils<dynamic>>' can't be assigned to the parameter type 'ServiceA<SampleModel>'
which shouldn't happen
What could I be doing wrong ?
CodePudding user response:
Since ServiceA
is a generic class, when you write Provider<ServiceA>
without specifying a type parameter for ServiceA
, Dart implicitly uses the broadest type possible that satisfies its type constraints. For ServiceA<T>
, since T
must extend BaseUtils
, ServiceA
with no explicit type parameter is equivalent to ServiceA<BaseUtils>
, which in turn is equivalent to ServiceA<BaseUtils<dynamic>>
due to BaseUtils
being a generic class with no constraint on its type parameter.
The error message is correct: you cannot safely assign a ServiceA<BaseUtils<dynamic>>
to a ServiceA<SampleModel>
. You have a supertype (ServiceA<BaseUtils<dynamic>>
) and a subtype (ServiceA<SampleModel>
). Although the subtype is a supertype, not every supertype is a subtype!
Enabling the strict-raw-types
option in your analysis_options.yaml
should help catch similar mistakes in the future.
I'm not experienced with package:provider
, but I expect that the fix would be to provide explicit types for your generic classes. For example:
final serviceAProvider = Provider<ServiceA>((ref) { return ServiceA(); })
maybe should be:
final serviceAProvider = Provider<ServiceA<SampleModel>>((ref) {
return ServiceA<SampleModel>();
})
Since this would require a separate serviceAProvider
variable for each type, it'd probably be cleaner to make a generic function so that callers can specify a type parameter:
final _serviceAMap = <Type, Provider<ServiceA>>{};
Provider<ServiceA<T>> getServiceAProvider<T extends BaseUtils>() {
var provider = _serviceAMap[T];
if (provider != null) {
return provider as Provider<ServiceA<T>>;
}
return _serviceAMap[T] = Provider<ServiceA<T>>((ref) => ServiceA<T>());
}
final serviceBProvider = Provider<ServiceB>((ref) {
final _a = ref.read(getServiceAProvider<SampleModel>());
return ServiceB(_a);
});