Home > Blockchain >  Why am I getting TypeError at runtime with my generic StatefulWidget class?
Why am I getting TypeError at runtime with my generic StatefulWidget class?

Time:04-19

I have a generic StatefulWidget class that has a Function callback. When I try to invoke that callback, I get a runtime TypeError:

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following _TypeError was thrown building MyStatefulWidget<int>(dirty, state:
MyState<int>#cacb3):
type '(int) => Widget' is not a subtype of type '(dynamic) => Widget'

The relevant error-causing widget was:
  MyStatefulWidget<int>
  MyStatefulWidget:file:///path/to/my_flutter_project/lib/main.dart:11:13

When the exception was thrown, this was the stack:
#0      MyState.build (package:my_flutter_project/main.dart:33:19)

Reproducible example:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

Widget f(int n) => Text('$n');

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyStatefulWidget<int>(callback: foo, value: 42),
    );
  }
}

class MyStatefulWidget<T> extends StatefulWidget {
  final Widget Function(T) callback;
  final T value;

  const MyStatefulWidget({
    required this.callback,
    required this.value,
    Key? key,
  }) : super(key: key);

  @override
  MyState<T> createState() => MyState<T>();
}

class MyState<T> extends State<MyStatefulWidget> {
  @override
  Widget build(BuildContext context) {
    return widget.callback(widget.value);
  }
}

I've tried explicitly constructing MyStatefulWidget as MyStatefulWidget<int>, but that doesn't help. Where is the Widget Function(dynamic) type coming from?

CodePudding user response:

MyState createState() => MyState(); omits the type arguments, so it returns a MyState, which is shorthand for MyState<dynamic>.

Additionally, MyState<T> extends State<MyStatefulWidget> is shorthand for ... extends State<MyStatefulWidget<dynamic>>, not for ... extends State<MyStatefulWidget<T>> as intended.

The static type of the MyState<T>'s inherited widget member therefore will be MyStatefulWidget<dynamic>, and the static type of widget.callback will be Widget Function(dynamic). At runtime, the associated MyStatefulWidget object has a reference to f (a Widget Function(int). However, that cannot be treated as Widget Function(dynamic) (as expected by MyState<T>) since f cannot accept all dynamic arguments, so you end up with a TypeError at runtime.

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. Common mistakes are:

  • Neglecting type parameters in createState.
  • 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 doesn't seem to catch cases where the type parameter is restricted (such as if you have MyStatefulWidget<T extends num>).

  • Related