Dart correct way to specify generic argument type on callback function - flutter

I'vo got a strange error, for a class similar to this one:
class UpdatableListPage<T> extends ConsumerStatefulWidget {
final StateNotifierProvider<UpdatableNotifier, List<T>> provider;
final Widget Function(T t) callbackWidget;
[...]
#override
_UpdatableListPageState<T> createState() => _UpdatableListPageState<T>();
}
class _UpdatableListPageState<T> extends ConsumerState<UpdatableListPage> {
#override
Widget build(BuildContext context) {
// Here the IDE said modelList is dynamic
var modelList = ref.watch(widget.provider);
[...]
ListView(
key: _refreshKey,
shrinkWrap: true,
scrollDirection: widget.scrollDirection,
children: [
for (final product in modelList as List<T>) widget.callbackWidget.call(product),
],
}
}
And I call the funciton as:
UpdatableListPage<RsMsgMetaData>(
userPostsProvider,
callbackWidget: (t) => PostTeaserCard(t,),
),
Where PostTeaserCard is a statefull Widget that recieve a RsMsgMetaData object as parameter.
The IDE say that everything is Ok but at run time, I got the following error:
type '(RsMsgMetaData) => PostTeaserCard' is not a subtype of type '(dynamic) => Widget'`
Seems like callbackWidget acts as (dynamic) => Widget function, but anyway... Should this function be compatible with the function signature of the anonymous function, right?
I don't know what is going on with this...

You wrote:
class _UpdatableListPageState<T> extends ConsumerState<UpdatableListPage> {
which is equivalent to:
class _UpdatableListPageState<T> extends ConsumerState<UpdatableListPage<dynamic>> {
Consequently, the type of _UpdatableListPageState<T>.widget.callbackWidget is Widget Function(dynamic t). You cannot pass a PostTeaserCard Function(RsMsgMetaData) for a Widget Function(dynamic) because the latter is callable with any argument, but the function you actually passed can be called only with an RsMsgMetaData argument.
To fix this, fix your _UpdatableListPageState class declaration to avoid the implicit use of dynamic and to fully depend on T:
class _UpdatableListPageState<T> extends ConsumerState<UpdatableListPage<T>> {
This probably will fix the type for modelList to List<T> instead of List<dynamic>.
Enabling the strict-raw-types check in your analysis_options.yaml should help catch this kind of error in the future.

Related

Extension of abstract class doesn't work properly in Dart

I have faced with strange behavior of extensions of abstract class in the Dart.
The Code
I have following files:
BaseListItem.dart
abstract class BaseListItem {
ImageProvider get _icon;
IHealthChecker get _healthChecker;
String get _name;
String get _unit;
Widget build(BuildContext context) {
... build widgets tree ...
}
}
Temperature.dart
class Temperature extends BaseListItem {
#override
IHealthChecker get _healthChecker => TemperatureHealthChecker();
#override
ImageProvider<Object> get _icon => const Svg('assets/images/icons/Temperature.svg');
#override
String get _name => "Temperature";
#override
String get _unit => "°C";
}
And all this inheritance stuff I am using in SensorsList.dart file.
class SensorsList extends StatefulWidget {
#override
State<StatefulWidget> createState() => _StateSensorsList();
}
class _StateSensorsList extends State<SensorsList> {
final List<BaseListItem> sensors = [Temperature(), Humidity()];
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: sensors.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return sensors[index].build(context);
});
}
}
The problem
This code built but fail at run time with next exception
Exception has occurred.
NoSuchMethodError (NoSuchMethodError: Class 'Temperature' has no instance getter '_icon'.
Receiver: Instance of 'Temperature'
Tried calling: _icon)
❗️The problem disappears if I put abstract class BaseListItem and his extension Temperature into a single file.❗️
The reason why it happens is because the variables are private. Private variables can only be accessed within the same file. See also In Dart, how to access a parent private var from a subclass in another file?

Flutter: Call a method from a class based on properties

The objective is to use the properties of a parent class to call a method and extend the content of that class.
The following code is designed to call a method based on the property of it's parent class. It returns a type error.
It is called like this:
MyToolbar(data: [
{
'MySecondClass': ['red','green','purple']
}
])
And this is the class:
class MyToolbar extends StatelessWidget {
MyToolbar({required this.data})
final List data;
ToolbarContent(type, data) {
if (type == 'MySecondClass') {
return MySecondClass(toggles: data);
}
#override
Widget build(BuildContext context) {
return Stack(children:[
for (List childData in data)
ToolbarContent('mySecondClass', childData),
])}
Firstly this returns the following type error.
_TypeError (type '_InternalLinkedHashMap<String, List<String>>' is not a subtype of type 'List<dynamic>')
Secondly, the list needs to find the key of the property data in order to set the correct function name for the function 'ToolbarContent'.
There's a few issues here. First, as mentioned by temp_, you need to set the List type for data, in this case would be List<Map<String,List<String>>
Second would be that for (List childData in data) needs to be actually for (Map<String,List<String>> childData in data)
The third is an assumption, but I think that there's a typo in your for loop where mySecondClass should be MySecondClass (or the other way)
The correct code would be as follows:
class MyToolbar extends StatelessWidget {
final List<Map<String, List<String>>> data;
MyToolbar({required this.data});
#override
Widget build(BuildContext context) {
var children = <Widget>[];
data.forEach((childData) {
childData.forEach((key, stringList) {
//I'm assuming Toolbar content takes the key of the map i.e. MySecondClass
//as the first param and the List for the key as the second param
children.add(ToolbarContent(key, stringList));
});
});
return Stack(
children: children,
);
}
}
Note: I'm also assuming that the ToolbarContent is another class, but do let me know if otherwise.
By default Dart sets any List to List<dynamic>. This is what the error is saying. You need to cast your List, try this instead final List<Map<String, List<String>> data;

error: The return type 'CategoryState' isn't a 'Widget', as required by the closure's context

I tried to put the route to Another Class on the Same Page and wrote this code as mentioned in the flutter Doc, but I get this:
error: The return type CategoryState isn't a Widget, as required
by the closure's context.
Page code:
Navigation code:
According to the documentation: the builder function of the MaterialPageRoute class should return a widget. Both StatelessWidget and StatefulWidget extend the Widget class and thus can be used here.
But in your case the CategoryState class does not extend any of them, and it's the reason you are having that error.
You need to use the Category class instead.
It should then look like:
PAGE :
class Category extends StatefulWidget {
#override
_CategoryState createState() => _CategoryState();
}
class _CategoryState extends State<Category> {
#override
Widget build(BuildContext context) {
return Column(
children: [
Container(child: Text("Music")),
Container(child: Text("Projects")),
Container(child: Text("Essay")),
],
);
}
}
NAVIGATION
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Category())
);
Furthermore, I would advise you to add a suffix to this class name (e.g. CategoryScreen) in order to avoid name conflicts in case you have another class (e.g. a model) with the same name.
class CategoryState is not a widget it's just a Dart class you want to navigate to Category()

Dart: Closure types not matching when adding builder

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.

How to avoid redundant type and instance of type in widget constructor?

I am creating a wrapper which takes two arguments: a model and a screen.
This wrapper uses ChangeNotifierProvider<T> internally, T being the model type.
Calling this wrapper widget at the moment looks like this:
NotifierWrapper<Preferences>(Preferences(), PreferencesScreen());
The above works, but it's verbose and redundant and frankly bad API design.
This is the wrapper widget:
class NotifierWrapper<T extends ChangeNotifier> extends StatelessWidget {
final T _model;
final _screen;
NotifierWrapper(this._model, this._screen);
#override
Widget build(context) {
return ChangeNotifierProvider<T>(
builder: (_) => _model,
child: _screen
);
}
}
I want this API to look like:
NotifierWrapper(Preferences(), PreferencesScreen());
But this does not work as ChangeNotifierProvider<T> requires a type. Can I provide a type to ChangeNotifierProvider from an instance of T?
Something like this (and variants) fail:
// ...
Type<ChangeNotifier> T = _model.runtimeType;
return ChangeNotifierProvider<T>(
// ...
or otherwise
NotifierWrapper<Preferences>(PreferencesScreen());
Deriving a constructor from a type?
// ...
return ChangeNotifierProvider<T>(
builder: (_) => T()
// ...
If these are not possible as I fear, how can I provide a sane API design in this case?
Tell me there is a way to avoid supplying type Preferences and a instance of Preferences() at the same time!