Home > Mobile >  Generic parameter type in Dart?
Generic parameter type in Dart?

Time:04-16

I'm trying to create a class that use a generic type as a parameter in a callback that returns some subtype of Flutter's Widget. Here's what I started with:

    class Subscriber<P extends PublishingController> extends StatefulWidget {
      const Subscriber({required this.builder, Key? key}) : super(key: key);

      final Widget Function(P) builder;

      @override
      _SubscriberState<P> createState() => _SubscriberState<P>();
    }

    class _SubscriberState<P extends PublishingController> extends State<Subscriber> {
      final P publisher = GetIt.instance.get<P>();

      @override
      void initState() {
        publisher.subscribe(rebuild);
        super.initState();
      }

      @override
      Widget build(BuildContext context) {
        return widget.builder(publisher);
      }

      @override
      void dispose() {
        publisher.unsubscribe(rebuild);
        super.dispose();
      }

      void rebuild() {
        setState(() {});
      }
    }

... with the Publisher:

    mixin Publisher {
      List<Function> subscribers = <void Function()>[];

      void subscribe(Function f) {
          subscribers.add(f);
      }

      void unsubscribe(Function f) {
        subscribers.remove(f);
      }

      void publish() {
        for (var f in subscribers) {
          f();
        }
      }
    }

    class PublishingController with Publisher {}

... and how I called it:

  child: Subscriber<MapController>(
             builder: (controller) => Column(...

... with:

  class MapController extends PublishingController {...

... but that gives me the error:

  ======== Exception caught by widgets library =======================================================
  The following _TypeError was thrown building Subscriber<MapController>(dirty, state: _SubscriberState<MapController>#d7e05):
  type '(MapController) => Column' is not a subtype of type '(PublishingController) => Widget'

I think I'm specifying the parameter type through the generics, and a function can return a subtype of its return type— what am I getting wrong here?

EDIT:

I got it working, but I'm not putting this in as an answer— I don't understand what the problem was, or why this version works; I changed my Subscriber class to:

    abstract class Builder<P extends PublishingController> extends StatefulWidget {
      const Builder({required this.builder, Key? key}) : super(key: key);
      final Widget Function(P) builder;
    }

    class Subscriber<P extends PublishingController> extends Builder<P> {
      const Subscriber({required builder, Key? key}) : super(builder: builder, key: key);

      @override
      _SubscriberState<P> createState() => _SubscriberState<P>();
    }

If someone wants to explain why this change would make the difference, please put it in an answer, and I'll gladly accept it.

CodePudding user response:

Your _SubscriberState<P> class extends State<Subscriber>, which in your case is shorthand for State<Subscriber<PublishingController>>, not for State<Subscriber<P>>.

The static type of the _SubscriberState<P>'s inherited widget member therefore will be Subscriber<PublishingController>, and the static type of widget.builder will be Widget Function(PublishingController). At runtime, the associated Subscriber object has a reference to a Column Function(MapController) object. However, that cannot be treated as a Widget Function(PublishingController) since it does not accept all PublishController arguments, so you end up with a runtime error.

Bottom line

If you have a generic StatefulWidget, you must explicitly and consistently supply the type parameters everywhere that the StatefulWidget refers to its State class or where the State refers to its StatefulWidget class. For the benefit of future readers, common mistakes are:

  • Neglecting type parameters in createState. (This wasn't a problem for this particular question.)
  • Neglecting type parameters when declaring inheritance for the corresponding State class.

So:

class MyStatefulWidget<T> extends StatefulWidget {
  ...
  MyState createState() => MyState(); // WRONG
}

class MyState<T> extends State<MyStatefulWidget> { // WRONG
  ...
}

instead should be:

class MyStatefulWidget<T> extends StatefulWidget {
  ...
  MyState<T> createState() => MyState<T>();
}

class MyState<T> extends State<MyStatefulWidget<T>>
  ...
}

The strict_raw_types analysis option sometimes can help catch such mistakes, although the current implementation seems to check only for implicitly dynamic type parameters and won't catch cases where the type parameter is restricted.

  • Related