Dart: Closure types not matching when adding builder - flutter

I'm working on my first Flutter project having spent many years on Objective-C and Swift so I'm very much learning the quirks of Dart vs the languages I'm familiar with. Currently I'm trying to follow the patterns I'm seeing in Dart and Flutter to add a builder to a class like this:
// Type def of builder closure.
typedef RecordWidgetBuilder<T> = Widget Function(#required T);
// Widget to display a list of records
class GroupedByDateWidget<T> extends StatefulWidget {
final List<T> items;
final RecordWidgetBuilder<T> widgetBuilder;
GroupedByDateWidget({#required this.items, #required this.widgetBuilder});
#override
_GroupedByDateWidgetState createState() => _GroupedByDateWidgetState(items: items, widgetBuilder: widgetBuilder);
}
class _GroupedByDateWidgetState<Item> extends State<GroupedByDateWidget> {
final List<Item> items;
final RecordWidgetBuilder<Item> widgetBuilder;
_GroupedByDateWidgetState({#required this.items, #required this.widgetBuilder});
// ... Rest of class
}
I then have this widget class that I want the closure to use:
// Widget that displays a record.
class SummarisedWorkRecordWidget extends StatelessWidget {
final WorkRecord record;
SummarisedWorkRecordWidget(this.record);
// ... rest of class
Then I'm trying to use these classes like this:
class _RecordViewState extends State<RecordView> {
#override
Widget build(BuildContext context) {
return GroupedByDateWidget<WorkRecord>(
items: [WorkRecord(), WorkRecord()],
widgetBuilder: (record) => SummarisedWorkRecordWidget(record),
);
}
}
It compiles, but when I run this I get this error:
The following _TypeError was thrown building RecordView(state: _RecordViewState#ade05):
type '(WorkRecord) => SummarisedWorkRecordWidget' is not a subtype of type '(dynamic) => Widget'
The relevant error-causing widget was:
RecordView file:///Users/derekclarkson/Work/projects/FWO/record_my_hours/lib/HomePage.dart:37:13
When the exception was thrown, this was the stack:
#0 GroupedByDateWidget.createState (package:record_my_hours/widgets/GroupedByDateWidget.dart:17:24)
#1 new StatefulElement (package:flutter/src/widgets/framework.dart:4764:24)
...
At this point I'm assuming that closures cannot match on inherited types, but that seem to clash with what I'm seeing in the APIs. Can anyone shed some light on why this isn't working an how to address it?

Solved it. The problem turned out to be that when I created the _GroupedByDateWidget State instance I didn't also specify the generic qualification to it that I'd added. So it instantiated a state with an dynamic generic and failed.
So:
#override
_GroupedByDateWidgetState createState() => _GroupedByDateWidgetState(items: items, widgetBuilder: widgetBuilder);
Should have been:
#override
_GroupedByDateWidgetState createState() => _GroupedByDateWidgetState<Item>(items: items, widgetBuilder: widgetBuilder);
Small thing.

Related

Is creating a stateful widget with a generic data type <T> passed to the state in Flutter a good practice?

I have created a widget in Flutter as follows:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class WidgetA<T> extends StatefulWidget {
const WidgetA({
super.key,
required this.errorSelector,
...
});
final Function errorSelector;
...
#override
State<WidgetA> createState() =>
_WidgetAState<T>();
}
class _WidgetAState<T> extends State<WidgetA> {
...
#override
Widget build(BuildContext context) {
...
return Builder(
builder: (ctx) {
final String foo =
ctx.select(widget.errorSelector as String Function(T));
return Text(foo);
}
);
}
}
Is this practice okay?
Are there better ways to accomplish this?
Can this cause any issues?
FYI - T is being used to pass a Class that extends a Bloc.
it's not bad and it's not good unless you have a good reason to do it (for example if you want to customize a data type based on that generic ).
so before doing it, ask yourself why so I need to make it generic, all the good patterns in the code are there to add some value to the code.
as you can see the only place where the generic is important is to set it in the State object, this prevents conflicting your StatefulWidget in your app with others and specify it to one StatefulWidget

Initialize StatefulWidget state null safe from widget constructor in Flutter

I want to have a StatefulWidget where I can pass the initial value for a non-nullable member of the widgets State from the widgets constructor.
My current solution (see below) seems to be not ideal, I see two problems with it:
The initial value has to be saved in the widget itself before passing it to the state.
The member in the sate has to be marked as late since it can only be set after initialization.
Is there a better way to initialize a StatefulWidget's state non-nullable member from a value passed to the widget constructor?
My current implementation:
class MyWidget extends StatefulWidget {
final String text;
const MyWidget({Key? key, required this.text}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late String text;
#override
void initState() {
text = widget.text;
super.initState();
}
#override
Widget build(BuildContext context) {
return Text(text);
}
}
(Not shown here, but later the text member should be changeable, that's why it is in the State)
hey there your code seems good.
but the better way is using bloc to pass and receive data.
any way . its not necessary to pass and fill data in initstate and _MyWidgetState .
you can receive your data directly in build widget As you wrote (widget.text)
here is some good things for avoid nullable
https://codewithandrea.com/videos/dart-null-safety-ultimate-guide-non-nullable-types/
You could use the constructor of State like this: _MyWidgetState(){ text=widget.text; }. The constructor will certainly be executed before initState and build methods.

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

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?
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>).

TypeError when calling a generic callback [duplicate]

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?
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>).

Why build method isn't defined inside StatefulWidget?

I'm currently learning Flutter. I tried to deep dive into Flutter Widget life-cycle, and I wonder why StatefulWidget are written like this :
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
// initState
// setState
// ...
#override
Widget build(BuildContext build) {
...
}
}
but not :
class Example extends StatefulWidget {
// initState
// setState
// ...
#override
Widget build(BuildContext build) {
...
}
}
I think the latter makes the source simple. But I don't know why they're using the former style ?
The reason why StatefulWidget uses a separate State class and not having build method inside its body is because all fields inside a Widget are immutable, and this includes all its sub-classes.
You might have noticed that StatelessWidget has its build and other associated methods defined inside it, but that was possible due to the nature of StatelessWidget which is rendered completely using the provided info, and doesn't expect any future change in its State.
In the case of StatefulWidget, State information occasionally change (or expected to change) during the course of the app, thus this information isn't suitable for storage in a final field (build) to satisfy Widget class conditions (all fields are immutable). That's why State class is introduced. You just have to override the createState function to attach your defined State to your StatefulWidget, and let all that change happens in a separate class.