How to create a map of widget references? - flutter

I am trying to create a Map<String, Widget> so that I can use it to dynamically determine which widget to use on the fly based on the key.
Scenario:
class WidgetA extends StatelessWidget {...}
class WidgetB extends StatelessWidget {...}
class WidgetC extends StatelessWidget {...}
class WidgetD extends StatelessWidget {...}
I want to create a map like:
const Map<String, Widget> map_to_widget = {
'widgetA': WidgetA,
'widgetB': WidgetB,
...
}
This will allow me to dynamically create widgets based on a list of widgets I need rendered. All of the above widgets would take in the same parameter so I would intend to use it like (still need to figure out how I would pass parameters):
return Container(
child: map_to_widget('widgetA')
)
When I try to create the map, I keep getting the The element type 'Type' can't be assigned to the map value type 'Widget'.dartmap_value_type_not_assignable error.

So the specific error comes from this:
'widgetA': WidgetA,
Above, WidgetA is not a widget, it is a type, similar to how 1 is an int but int is a type.
To fix this you would want to do something like this:
'widgetA': WidgetA(),
in order to actually create a widget.
The problem comes, as you say, when you want to pass parameters to those widgets. If the parameters are always the same no matter what, then you can pass them directly:
'widgetA': WidgetA(title: 'this is widget A'),
but if the parameters may change as you use the widgets, then you can't use a map of widgets for this, I propose a map of functions instead:
Map<String, Widget Function(String)> widgets {
'widgetA': (title) => WidgetA(title: title),
...
}
This way, when getting the widget:
widgets['widgetA']('this is my title');

Related

Flutter error encounter 'The argument type 'int' can't be assigned to the parameter type 'CardItem'.'

enter image description here
Encounter an error
(The argument type 'int' can't be assigned to the parameter type 'CardItem'.) at line 'itemBuilder: (context, index) => buildCard(item: index,),'
enter image description here
Firstly you extracted Widget to method which named buildCard. This approach is not recommended by Flutter. To learn more why:
https://dartcodemetrics.dev/docs/rules/flutter/avoid-returning-widgets
https://iiro.dev/splitting-widgets-to-methods-performance-antipattern/
The best way which Flutter recommends is extract Widgets to Stateful or Stateless widget classes. In the upper link.
For your code.
Extract your returned Widget to Stateless or Stateful widget
Give CartItem to the constructor of your created CardWidget to use CardWidget inside it data.
Then your item builder must be like this: itemBuilder: (context, index) => MyCustomCardWidget(item: _items[index]),
Your MyCustomCardWidget must be like this:
class MyCustomCardWidget extends StatelessWidget {
final CartItem item;
const MyCustomCardWidget({Key? key, required this.item}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(child: Text(item.name), ....yourwidget code here);
}
}
You need to pass CardItem object instead of index becuase required is CardItem not index
You need to make
List<CardItem> cartItems=
[
CartItem();
CartItem();
];
Then you need pass in your ListView as cartItems[index]
In your case you have to pass your _items List as _items[index] to ListView.builder
You mean that you wanna use cardItems right?
I think you make a typo, because of name of the list cardItems. then you should pass it in the itembuilder :
like this
buildCard(cardItems[index]);

Return same object type user provided in Dart

So i have this Widget I am writing for flutter, it is a Tinder like swiping cards, I want the consumer to be able to provide a List of any type he wants, and i want to use the same type he provides to return in the builder method he should provide:
class Swipeable extends StatelessWidget {
final List<T> data;
final Widget Function(BuildContext, T) builder;
Swipeable({required this.data, required this.builder});
}
Where T is the type of data the user provides, and it is controlled by the user with no limitations by me.
The consumer should be able to consume the widget like this:
Swipeable(
data: <User>[
User(
name: "Zakaria",
profession: "Geek",
images: [
"https://images.unsplash.com/photo-1533488069324-f9265c15d37f?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=594&q=80",
"https://images.unsplash.com/photo-1583864697784-a0efc8379f70?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80",
],
age: 18,
)
],
builder: (context, user) {
return Text(user.name);
}
)
I hope u understood my Question, I'm not that good in explaining stuff when I'm still a newbie.
You can use a generic class for this.
For more information, consult the Generics section in the language tour.
// The class ( ↓ note the type argument here)
class Swipeable<T> extends StatelessWidget {
final List<T> data;
final Widget Function(BuildContext, T) builder;
Swipeable({required this.data, required this.builder});
}
Type inference should work in your example (you shouldn't even need to specify the list type), but if it doesn't, you can specify the type with Swipeable<Type>.
On another note, you may not even need to pass through your list like this. Consider the ListView and GridView builder constructors, which provide an index instead of an object. You may want to do the same for consistency.

Flutter Stateful Widget Constructor That Does Not Include All Members

I am building a Stateful Widget in Flutter, and as such, there is a requirement for all arguments passed in the constructor to be final (since Stateful widgets are marked with the #immutable annotation).
Thing is, I want to have two different constructors for my widget and to exclude some of the members of the Stateful widget, depending on the constructor used. I have to stress, that I do not want these arguments to be optional, but mandatory.
For example,
class MyWidget extends StatefulWidget {
MyWidget.first({this.firstArgument}};
MyWidget.second({this.secondArgument});
final int firstArgument;
final String secondArgument;
#override
MyWidget createState() => MyWidgetState();
}
When I write this, I get a compiler error, telling me that:
All final variables must be initialized, but 'firstArgument' isn't.
The same goes for the second member variable.
How can I overcome this?
I can't move firstArgument and secondArgument to the state of MyWidget, since I want them to be initialized in the constructor(s) and also because they should not be changed.
I can't mark them as not final since then I will get a compiler warning and also break the Stateful widget paradigm.
Is there a different approach I should use?
Thing is, I want to have two different constructors for my widget and to exclude some of the members of the Stateful widget, depending on the constructor used. I have to stress, that I do not want these arguments to be optional, but mandatory.
If you don't want them to be optional, you need to mark them as required:
MyWidget.first({required this.firstArgument}};
MyWidget.second({required this.secondArgument});
(If you don't have null-safety enabled, you will instead need to use the weaker #required annotation from package:meta.)
My understanding is that you want firstArgument and secondArgument to be required for MyWidget.first and MyWidget.second respectively but that they are not intended to be required together (that is, only one should be set).
You could fix this by explicitly initializing both values in the constructors:
MyWidget.first({required this.firstArgument}} : secondArgument = null;
MyWidget.second({required this.secondArgument}): firstArgument = null;
If you have null-safety enabled, you also would need to make your members nullable:
final int? firstArgument;
final String? secondArgument;
Maybe factory constructors would help?
class MyWidget extends StatefulWidget {
MyWidget._({this.firstArgument, this.secondArgument}};
factory MyWidget.first({#required int first})=>MyWidget._(firstArgument: first, );
factory MyWidget.second({#required String second})=>MyWidget._(secondArgument: second, );
final int firstArgument;
final String secondArgument;
#override
MyWidget createState() => MyWidgetState();
}
This way, you'll only be able to build this widget using these constructors (since the class constructor is private) and when you call MyWidget.first the value for secondArgument for the widget will be null, and the same applies when you use MyWidget.second with firstArgument

Flutter, pasing parameters on StatefulWidget

Can someone tell me what is happening in this program?
body: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new StuffInTiles(listOfTiles[index]);
},
itemCount: listOfTiles.length,
),
),
);
}
}
class StuffInTiles extends StatefulWidget{
final MyTile myTile;
const StuffInTiles(this.myTile);
#override
StuffInTilesState createState() => StuffInTilesState();
}
class StuffInTilesState extends State<StuffInTiles> {
#override
Widget build(BuildContext context) {
return Container(child:
//Text(widget.myTile.title),);
_buildTiles(widget.myTile));
}
Widget _buildTiles(MyTile t) {
I want to understand how passing parameters works,why i have
const StuffInTiles(this.myTile);
in this program, what this code is doing?
in my class StuffInTilesState extends State<StuffInTiles> i don't have any constructor, so how this code is working? why my parameters just happen to be there? before i was learning C++, so this is like a magic to me
If you learned C++ you are probably familiar with initializer list, which Dart has as well. In C++ you could do something:
MyClass {
MyClass(MyTitle title) : this.myTitle = title;
final MyTitle myTitle;
}
Which is also valid in Dart. However, Dart allows you to shorthand the call, by automatically assigning the reference to the new class property, without using any intermediate variable.
MyClass(this.myTitle);
Which is basically the same but ensures that any given property won't be null unless you explicitly pass null.
There are other type of constructors available on Dart, such as private constructors, factory constructors and named constructors. You may want to check the official documentation to learn more about it.
By default, when you create classes in Dart, there is just a default constructor implemented.
For example, if you have a class named AwesomeWidget. It will have a default constructor AwesomeWidget() that lets you create an instance of this widget.
So you could use a default constructor in code like so:
//Example 1
return AwesomeWidget();
//Example 2
AwesomeWidget myWidget = AwesomeWidget();
//Example 3
//...
Row(
children: [
Text("Example Code!"),
AwesomeWidget(),
Text("Example Footer Code!"),
],
),
//...
Now if you want to pass some values or some data to your Widget classes then you use the code you have posted above in your question.
The question is: Why would we want to send data to our widgets?
Answer: The biggest use case is when we make our list items as separate widgets. For example, in my food app, I have to show my user's order history in a ListView, so for the UI of each individual list item, I will just make a reusable Widget called OrderHistoryListItem.
In that OrderHistoryListItem, you want to show the date and time of the object. And the order id, and how much the user paid in that order or any other details, so to display this, we send this data to our Reusable Widget List Item, which displays it. Simple as that.
And that is one of the reasons why we pass values to Widgets. As a programmer, you can make use of this handy feature for more complex scenarios, be creative!

Mock a Widget in Flutter tests

I am trying to create tests for my Flutter application. Simple example:
class MyWidget extends StatelessWidget {
#override
build(BuildContext context) {
return MySecondWidget();
}
}
I would like to verify that MyWidget is actually calling MySecondWidget without building MySecondWidget.
void main() {
testWidgets('It should call MySecondWidget', (WidgetTester tester) async {
await tester.pumpWidget(MyWidget());
expect(find.byType(MySecondWidget), findsOneWidget);
}
}
In my case this will not work because MySecondWidget needs some specific and complex setup (like an API key, a value in a Provider...). What I would like is to "mock" MySecondWidget to be an empty Container (for example) so it doesn't raise any error during the test.
How can I do something like that ?
There is nothing done out of the box to mock a widget. I'm going to write some examples/ideas on how to "mock"/replace a widget during a test (for example with a SizedBox.shrink().
But first, let me explain why I think this is not a good idea.
In Flutter you are building a widget tree. A specific widget has a parent and usually has one or several children.
Flutter chose a single pass layout algorithm for performance reasons (see this):
Flutter performs one layout per frame, and the layout algorithm works in a single pass. Constraints are passed down the tree by parent objects calling the layout method on each of their children. The children recursively perform their own layout and then return geometry up the tree by returning from their layout method. Importantly, once a render object has returned from its layout method, that render object will not be visited again until the layout for the next frame. This approach combines what might otherwise be separate measure and layout passes into a single pass and, as a result, each render object is visited at most twice during layout: once on the way down the tree, and once on the way up the tree.
From this, we need to understand that a parent needs its children to build to get their sizes and then render itself properly. If you remove its children, it might behave completely differently.
It is better to mock the services if possible. For example, if your child makes an HTTP request, you can mock the HTTP client:
HttpOverrides.runZoned(() {
// Operations will use MyHttpClient instead of the real HttpClient
// implementation whenever HttpClient is used.
}, createHttpClient: (SecurityContext? c) => MyHttpClient(c));
If the child needs a specific provider you can provide a dummy one:
testWidgets('My test', (tester) async {
tester.pumpWidget(
Provider<MyProvider>(
create: (_) => MyDummyProvider(),
child: MyWidget(),
),
);
});
If you still want to change a widget with another one during your tests, here are some ideas:
1. Use Platform.environment.containsKey('FLUTTER_TEST')
You can either import Platform from dart:io (not supported on web) or universal_io (supported on web).
and your build method could be:
#override
Widget build(BuildContext context) {
final isTest = Platform.environment.containsKey('FLUTTER_TEST');
if (isTest) return const SizedBox.shrink();
return // Your real implementation.
}
2. Use the annotation #visibleForTesting
You can annotate a parameter (ex: mockChild) that is only visible/usable in a test file:
class MyWidget extends StatelessWidget {
const MyWidget({
#visibleForTesting this.mockChild,
});
final Widget? child;
#override
Widget build(BuildContext context) {
return mockChild ?? // Your real widget implementation here.
}
}
And in your test:
tester.pumpWidget(
MyWidget(
mockChild: MyMockChild(),
),
);
You can mock MySecondWidget (eg using Mockito) but you do need to change your real code to create a MockMySecondWidget when in test mode, so it's not pretty. Flutter does not support object instantiation based on a Type (except through dart:mirrors but that is not compatible with Flutter), so you cannot 'inject' the type as a dependency. To determine if you are in test mode use Platform.environment.containsKey('FLUTTER_TEST') - best to determine this once upon startup and set the result as a global final variable, which will make any conditional statements quick.
One way to do it, is to wrap the child widget into a function, and pass the function to parent widget's constructor:
class MyWidget extends StatelessWidget {
final Widget Function() buildMySecondWidgetFn;
const MyWidget({
Key? key,
this.buildMySecondWidgetFn = _buildMySecondWidget
}): super(key: key);
#override
build(BuildContext context) {
return buildMySecondWidgetFn();
}
}
Widget _buildMySecondWidget() => MySecondWidget();
Then you can make up your mock widget, pass it thru buildMySecondWidgetFn in test.