How to use generic with interface in dart, flutter - flutter

I am trying to use generic with interface in dart lang in flutter framework.
Basically, I would like to set the type of the generic to an implementation of ITestClass.
in the typedef definition, I am setting the "T" type to extend ITestClass (because you cannot implement with generic in dart). But when I actually use this typedef in the build method, it throws following error
The argument type 'TestClass' can't be assigned to the parameter type 'T'.
From my knowledge, TestClass is an implementation of ITestClass so type error should not be thrown. How can I overcome this problem?
import 'package:flutter/material.dart';
class ITestClass {}
class TestClass implements ITestClass {}
typedef TestTypedef<T extends ITestClass> = Widget Function(T model);
void main() {
runApp(
MaterialApp(
home: TestWidget(
testTypedef: (model) => Container(),
),
),
);
}
class TestWidget<T extends ITestClass> extends StatelessWidget {
final TestTypedef<T> testTypedef;
TestWidget({
required this.testTypedef,
});
Widget build(BuildContext context) {
return Scaffold(
body: testTypedef(
TestClass(), // error here
),
);
}
}

Related

Dart correct way to specify generic argument type on callback function

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.

Flutter bloc is not rebuilding in 7.2.0 version with Equatable

I created simple app to test bloc 7.2.0 and faced that BlocBuilder doesn't rebuild after first successful rebuild. On every other trigger bloc emits new state, but BlocBuilder ignores it.
Please note, if I remove extends Equatable and its override from both, state and event, then BlocBuilder rebuilds UI every time Button pressed. Flutter version 2.5.1
If Equatable is necessary, why it's not working with it? If Equatable isn't necessary, why it's been used in initial creation via VSCode extension.
My code:
bloc part
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
//bloc
class MainBloc extends Bloc<MainEvent, MainState> {
MainBloc() : super(MainInitial()) {
on<MainButtonPressedEvent>(_onMainButtonPressedEvent);
}
void _onMainButtonPressedEvent(
MainButtonPressedEvent event, Emitter<MainState> emit) {
emit(MainCalculatedState(event.inputText));
}
}
//states
abstract class MainState extends Equatable {
const MainState();
#override
List<Object> get props => [];
}
class MainInitial extends MainState {}
class MainCalculatedState extends MainState {
final String exportText;
const MainCalculatedState(this.exportText);
}
//events
abstract class MainEvent extends Equatable {
const MainEvent();
#override
List<Object> get props => [];
}
class MainButtonPressedEvent extends MainEvent {
final String inputText;
const MainButtonPressedEvent(this.inputText);
}
UI part
import 'package:bloc_test/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: BlocProvider(
create: (context) => MainBloc(),
child: SubWidget(),
),
),
);
}
}
class SubWidget extends StatelessWidget {
TextEditingController inputText = TextEditingController();
String? exportText;
#override
Widget build(BuildContext context) {
MainBloc mainBloc = BlocProvider.of<MainBloc>(context);
return BlocBuilder<MainBloc, MainState>(
builder: (context, state) {
if (state is MainCalculatedState) {
exportText = state.exportText;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${exportText ?? ''} data'),
SizedBox(
width: 200,
child: TextField(
controller: inputText,
),
),
ElevatedButton(
onPressed: () =>
mainBloc.add(MainButtonPressedEvent(inputText.text)),
child: const Text('Button')),
],
),
);
},
);
}
}
Equatable is used to make it easy for you to program, how and when states are the same (no update) and when they are different (update).
Your updates do not work because you are sending the same state repeatedly, but you did not tell the Equatable extension how to find out if they are different. So they are all the same.
So to make sure your program understands that some states of the same kind are indeed different and should cause an update, you need to make sure you mention what makes them different:
class MainCalculatedState extends MainState {
final String exportText;
const MainCalculatedState(this.exportText);
// this tells the Equatable base class to consider your text property
// when trying to figure out if two states are different.
// If the text is the same, the states are the same, so no update
// If the text is different, the states are different, so it will update
#override
List<Object> get props => [this.exportText];
}
If you remove Equatable altogether, two newly instanciated states are never equal, so that would solve your problem as well... except that at some point you will want them to be, and then you need to add it back in.
Your MainCalculatedState needs to override the props getter from Equatable and return the list of all properties which should be used to assess equality. In your case it should return [exportText].
Example:
class MainCalculatedState extends MainState {
final String exportText;
const MainCalculatedState(this.exportText);
#override
List<Object> get props => [exportText];
}

Flutter - auto_route _CustomNavigatorState error

I'm using the auto_route package in order to route my app and until 2 days ago everything worked just fine, until now.
For some reason, I'm getting the following error.
SYSTEM:
flutter: 1.22
dart: 2.10.0
auto_route: ^0.6.7
ERROR:
../../.pub-cache/hosted/pub.dartlang.org/custom_navigator-0.3.0/lib/custom_navigator.dart:60:7: Error: The non-abstract class '_CustomNavigatorState' is missing implementations for these members:
- WidgetsBindingObserver.didPushRouteInformation
Try to either
- provide an implementation,
- inherit an implementation from a superclass or mixin,
- mark the class as abstract, or
- provide a 'noSuchMethod' implementation.
class _CustomNavigatorState extends State<CustomNavigator>
^^^^^^^^^^^^^^^^^^^^^
/opt/flutter/packages/flutter/lib/src/widgets/binding.dart:122:16: Context: 'WidgetsBindingObserver.didPushRouteInformation' is defined here.
Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
main.dart
import 'package:device_simulator/device_simulator.dart';
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'Test.gr.dart' as r;
void main() => runApp(MyApp());
const bool debugEnableDeviceSimulator = true;
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
builder: ExtendedNavigator.builder<r.Router>(
router: r.Router(),
builder: (context, extendedNav) => DeviceSimulator(
enable: debugEnableDeviceSimulator,
child:
Scaffold(body: extendedNav, backgroundColor: Colors.red ),
),
),
);
}
}
Also, I should mention, because of some update of flutter I guess, now I need to use r.Router instead of just Router.
Test.dart
import 'package:auto_route/auto_route_annotations.dart';
#MaterialAutoRouter(
routes: <AutoRoute>[],
)
class $Router {}
And also if has any importance here it's the generated file
Test.gr.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// AutoRouteGenerator
// **************************************************************************
// ignore_for_file: public_member_api_docs
import 'package:auto_route/auto_route.dart';
class Routes {
static const all = <String>{};
}
class Router extends RouterBase {
#override
List<RouteDef> get routes => _routes;
final _routes = <RouteDef>[];
#override
Map<Type, AutoRouteFactory> get pagesMap => _pagesMap;
final _pagesMap = <Type, AutoRouteFactory>{};
}
Do you have any idea what's going one here or how can I fix this? Or if I can't fix this, what can I use instead of the auto_route package which will offer me the same benefice?
i solved this problem with add some function on library CustomNavigator :
Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
return didPushRoute(routeInformation.location);
}
Find the place where the error is generating. Add the following function below there. I did the same and my problem is solved.
Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
return didPushRoute(routeInformation.location);
}

Dynamically create instance from type name

There's the question – could I create class instance if I have string variable which contains its name?
For example, I have
var className = 'DocumentsList';
could I do something like this
var docListWidget = createInstance(className[, params]);
Flutter (and Dart) do not have the facilities to do that. On purpose. They implement tree shaking, a mechanism to remove unused code to make your app smaller and faster. However, to do that, the compiler has to know what code is used and what is not. And it cannot possibly know what code gets used if you can do stuff like you describe.
So no, this is not possible. Not with that degree of freedom. You can have a big switch statement to create your classes based on strings, if you know in advance which string it will be. That is static, the compiler can work with that.
What you want is called "reflection" and you can add some capabilities using packges like reflectable or mirror but they cannot change the compilation process, they too work through the fact that you specify beforehand which classes need reflection. A totally dynamic usage is just not possible (on purpose).
Since you mentioned the routing table: You cannot create a class from a string, you can however create the string form the class:
import 'package:flutter/material.dart';
typedef Builder<T> = T Function(BuildContext context);
String routeName<T extends Widget>() {
return T.toString().toLowerCase();
}
MapEntry<String, Builder<Widget>> createRouteWithName<T extends Widget>(Builder<T> builder) {
return new MapEntry(routeName<T>(), (context) => builder(context));
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: routeName<ScreenPicker>(),
routes: Map.fromEntries([
createRouteWithName((context) => ScreenPicker()),
createRouteWithName((context) => ScreenOne()),
createRouteWithName((context) => ScreenTwo()),
]),
);
}
}
class ScreenPicker extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("Navigate a route")),
body: Column(children: [
RaisedButton(
child: Text('One'),
onPressed: () => Navigator.pushNamed(context, routeName<ScreenOne>())),
RaisedButton(
child: Text('Two'),
onPressed: () => Navigator.pushNamed(context, routeName<ScreenTwo>())),
]));
}
}
class ScreenTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("Second Screen")), body: Center(child: Text("Two")));
}
}
class ScreenOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("First Screen")), body: Center(child: Text("One")));
}
}
This way you have no strings with route names in your project that can be changed or mistyped or forgotten when renaming something.
The best way to do that is using a named constructor like:
class MyClass {
const MyClass.create();
}
So the way you do this is passing the Type calling the method within. Remember you can pass the function without calling it, like:
Get.lazyPut(MyClass.create);
If you really need to use String for identifying the class type I suggest you create some mapping for it.
There's some functions from rootBundle that can be useful in this situation, like rootBundle.loadString() that can be done to load code from files.
Hope this was helpful.

Dart / Flutter - abstract class not working

I'm trying to make a generic dropdown widget with the current code:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
abstract class FormDropdownProtocol {
String get title;
}
class FormDropdown<FormDropdownProtocol> extends StatelessWidget {
const FormDropdown({this.value, this.items, this.onChanged});
final FormDropdownProtocol value;
final List<FormDropdownProtocol> items;
final ValueChanged<FormDropdownProtocol> onChanged;
#override
Widget build(BuildContext context) {
return DropdownButton<FormDropdownProtocol>(
value: value,
items: items.map<DropdownMenuItem<FormDropdownProtocol>>(
(FormDropdownProtocol value) {
return DropdownMenuItem<FormDropdownProtocol>(
value: value,
child: Text(value.title),
);
}).toList(),
onChanged: onChanged,
);
}
}
But I'm getting the following error when trying to use the title:
Error: The getter 'title' isn't defined for the class 'Object'.
- 'Object' is from 'dart:core'.
Try correcting the name to the name of an existing getter, or defining a getter or field named > 'title'.
child: Text(value.title)
What am I doing wrong?
class FormDropdown<FormDropdownProtocol> extends StatelessWidget {
This defines a generic class (FormDropdown) with a type parameter named FormDropdownProtocol. That type parameter could be named anything; it might be clearer to understand what's going on if it were:
class FormDropdown<T> extends StatelessWidget {
Dart generics aren't like C++ templates where the class is instantiated with the formal type information before everything is resolved. With Dart, everything is resolved before the generic is instantiated with a type.
In the case of class FormDropdown<T>, nothing is known about T. No constraints are given, so it can only be deduced to be an Object. And indeed, Object has no title member.
If you instead do class FormDropdown<T extends FormDropdownProtocol>, then T is now constrained to be FormDropdownProtocol, which does have a title member.