Home > Back-end >  Dart fails to infer type of sub class
Dart fails to infer type of sub class

Time:03-14

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);
});


ServiceBdepends 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);
});
  • Related