I have this little class:
class WidgetToImage extends StatefulWidget {
final Function(GlobalKey key) builder;
const WidgetToImage({Key? key, #required this.builder}) : super(key: key);
#override
_WidgetToImageState createState() => _WidgetToImageState();
}
This chunk of code won't compile because anybody can pass a null value for the builder parameter when constructing the WidgetToImage widget. I know I could make the builder nullable but this is not what I want because later I must be checking if it is null, etc. and semantically it doesnt make any sense. A valid builder must be always passed.
Is there any way to annotate this in dart to avoid converting builder property to a nullable type?
If you use Dart version 2.12, you get null safety as a language feature.
It seems you are already using that since your code contains Key?, which is the null-safety way of writing "nullable".
On the other hand, your this.builder parameter should have been marked as required (a keyword in null safe code) instead of the older #required annotation, so it doesn't look like valid null safe code.
The code should read:
class WidgetToImage extends StatefulWidget {
final Function(GlobalKey key) builder;
const WidgetToImage({Key? key, required this.builder}) : super(key: key);
#override
_WidgetToImageState createState() => _WidgetToImageState();
}
and then it's a compile-time error for null-safe code to pass null as an argument to builder.
(Older non-null-safe code can still get away with passing null, but they're asking for trouble then.)
You can add an assertion:
const WidgetToImage({Key? key, required this.builder})
: assert(builder as dynamic != null),
super(key: key);
that will tell people developing against your library to not pass in null from non-null-sound code, but only while developing with assertions enabled.
Related
I have this method
void doSomething<T>() { ... }
and a variable of type T
T someVar = ...;
how can I use doSomething method to perform an action on someVar, something like this?
doSomething<someVar.Type>();
in fact, I want to access Type T form variable.
here is the example
class BlocManagerProvider extends StatefulWidget {
const BlocManagerProvider({
#required this.child,
#required this.blocs,
Key key,
}) : super(key: key);
final Widget child;
final List<Cubit<Object>> blocs;
#override
_BlocManagerProviderState createState() => _BlocManagerProviderState();
}
class _BlocManagerProviderState extends State<BlocManagerProvider> {
#override
Widget build(BuildContext context) => widget.child;
#override
void dispose() {
for (final Cubit<Object> bloc in widget.blocs) {
BlocManager.instance.dispose<type>();
}
super.dispose();
}
}
Future<void> dispose<T extends Cubit<Object>>() async {
final String objectKey = _getKey<T>(key);
if (_repository.containsKey(objectKey)) {
await _repository[objectKey].close();
_repository.remove(objectKey);
await removeListener<T>(key);
}
}
Dart does not provide a way to go from an object of type X to a type variable bound to X. There are good technical reasons for not allowing that (it allows the web compilers to know at compile-time which types can ever be bound to a type variable, which allows it to reduce the compiled code).
The dispose method is treating the type argument as its only argument and acting on the value of that type argument.
It makes me think you're trying to do something that the language is not designed for.
You're passing in a type argument, and then the code inspects that type of argument and behaves differently depending on the value. That's not what's usually meant by being "generic" - to act the in the same (generic) way independently of the types, so the only real effect of passing a type is to make the return type match the argument type.
(That's why Java can erase type arguments at run-time).
So, if you need to know a type for some object, either that object must provide it for you, or you have to store it from earlier (perhaps when the object was created).
So, if you really need to access the type argument that the cubit is implementing Cubit<X> of, the Cubit class needs to make it available to you. That will usually be with a method with a callback (like a visitor), something like:
abstract class Cubit<T> ... {
...
R visit<R>(R Function<C extends Cubit<T>, T>(C value) action);
}
class SomeCubit<T> extends Cubit<T> {
...
R visit<R>(R Function<C extends Cubit<T>, T>(C value) action) =>
action<SomeCubit<T>, T>(this);
}
If something like that's available, then you can do what you want as:
bloc.visit(<C extends Cubit<T>, T>(_) => BlocManager.instance.dispose<C>());
If something like that is not available, then you are in trouble.
You can detect a number of known types, with a bunch of if statements, but that's unlikely to be sufficient.
That means you need to remember the type from earlier, but since it looks like you just get a List<Cubit<Object>> that has already been created, that doesn't seem practical either.
If the BlocManager is your own class, consider changing it to use Type objects instead of type arguments (which is contrary to everything I usually say you should do), because then you can call ..dispose(bloc.runtimeType). I'd prefer to avoid that, but if other constraints make what you do impossible, then it might be the lesser evil.
This is a follow-up question to
How does the const constructor actually work?,
So far from what I've read about const constructors in Dart, it ensures that only one object of the class in question is allocated. In theory, this can save allocation space and execution time. It's even recommended to apply it wherever possible when following Effective Dart design.
Now, say we have a stateless widget Foo:
import 'package:flutter/material.dart';
class Foo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text("Hello world");
}
}
Without defining an explicit const constructor, this widget cannot be used in a const context. In other words, the following snippet
import 'package:flutter/material.dart';
class Bar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const Foo();
}
}
is illegal. But is there even a benefit to adding a const Foo(); to the Foo class if it doesn't (and won't have) any fields in the foreseeable future?
Yes, there is a benefit. You should declare as const if possible.
According to the Flutter doc:
Use const widgets where possible, and provide a const constructor for the widget so that users of the widget can also do so.
The benefits are the same as having a default vs const constructor:
Performance (see)
Your code is consistent and sound
Are used as annotations
Can use in compile switch
You can enable const by default using Lint
The code is part of this class
class Category extends StatelessWidget {
final String name;
final ColorSwatch color;
final IconData iconLocation;
And the use of required is this:
const Category({
Key key,
#required this.name,
#required this.color,
#required this.iconLocation,
}) : assert(name != null),
assert(color != null),
assert(iconLocation != null),
super(key: key);
The use of Key key also confuses me.
The #required annotation indicates that the parameter is a required parameter (i.e an argument needs to be passed to the parameter).
You can instead create the function parameters without using the optional parameter syntax which implicitly makes it a required.
ie This
Category(
this.name,
this.color,
this.iconLocation,
)
Instead of
Category({
Key key,
#required this.name,
#required this.color,
#required this.iconLocation,
})
Why use the optional parameter syntax together with the #required annotation?
The major benefit of doing it this way is readability! It helps when passing values to your widget fields since you don't have to guess the position of the parameters.
according to Dart's Language tour
Flutter instance creation expressions can get complex, so widget constructors use named parameters exclusively. This makes instance creation expressions easier to read.
When you are creating object this keyword #required makes it required(needed).
Dart Language Tour
A function can have two types of parameters: required and optional. The required parameters are listed first, followed by any optional parameters. Named optional parameters can also be marked as #required. See the next section for details.
read about required
I've created a small app to add items in list, however when i delete something from list, it gets deleted successfully but ListView.builder doesn't show correct values. i know its something related to keys given to class but i'm pretty new in flutter so don't know how to do that.
Gist: https://gist.github.com/OculusMode/213052325ec725aad3ab92c73599b187
Thanks in advance.!
Add this to constructor of your Widget:
:super(key:new ObjectKey(_data))
Example:
class TodoTile extends StatefulWidget {
String _data;
int _index;
ValueChanged<int> onDelete;
TodoTile(this._data,this._index,{ #required this.onDelete , Key key}):super(key:new ObjectKey(_data));
TodoTileState createState() {return new TodoTileState(_data, _index,this.onDelete);}
}
Not sure if this would cause problems too but I've also changed widget.onDelete to onDelete (passing the function pointer to the state too)
Source:
https://flutter.io/widgets-intro/#keys
In the flutter docs there's sample code for a stateless widget subclass as shown:
class GreenFrog extends StatelessWidget {
const GreenFrog({ Key key }) : super(key: key);
#override
Widget build(BuildContext context) {
return new Container(color: const Color(0xFF2DBD3A));
}
}
and this
class Frog extends StatelessWidget {
const Frog({
Key key,
this.color: const Color(0xFF2DBD3A),
this.child,
}) : super(key: key);
final Color color;
final Widget child;
#override
Widget build(BuildContext context) {
return new Container(color: color, child: child);
}
}
What is a key and when should this super constructor be used? It seems like if you have your own constructor you must have {Key key} why? I've seen other examples where the super keyword is not used so this is where my confusion is.
TLDR: All widgets should have a Key key as optional parameter or their constructor.
Key is something used by flutter engine at the step of recognizing which widget in a list as changed.
It is useful when you have a list (Column, Row, whatever) of widgets of the same type that can potentially get removed/inserted.
Let's say you have this (code not working, but you get the idea) :
AnimatedList(
children: [
Card(child: Text("foo")),
Card(child: Text("bar")),
Card(child: Text("42")),
]
)
Potentially, you can remove any of these widgets individually with a swipe.
The thing is, our list has an animation when a child is removed. So let's remove "bar".
AnimatedList(
children: [
Card(child: Text("foo")),
Card(child: Text("42")),
]
)
The problem: Without Key, flutter won't be able to know if the second element of your Row disappeared. Or if it's the last one that disappeared and the second has its child change.
So without Key, you could potentially have a bug where your leave animation will be played on the last element instead!
This is where Key takes place.
If we start our example again, using key we'd have this :
AnimatedList(
children: [
Card(key: ObjectKey("foo"), child: Text("foo")),
Card(key: ObjectKey("bar"), child: Text("bar")),
Card(key: ObjectKey("42"), child: Text("42")),
]
)
notice how the key is not the child index but something unique to the element.
From this point, if we remove "bar" again, we'll have
AnimatedList(
children: [
Card(key: ObjectKey("foo"), child: Text("foo")),
Card(key: ObjectKey("42"), child: Text("42")),
]
)
Thanks to key being present, flutter engine now knows for sure which widget got removed. And now our leave animation will correctly play on "bar" instead of "42".
What are Keys?
Keys are IDs for widgets. All widgets have them, not just StatelessWidgets. They are used by the Element tree to determine if a widget can be reused or if it needs to be rebuilt. When no key is specified (the usual case), then the widget type is used to determine this.
Why use Keys?
Keys are useful for maintaining state when the number or position of widgets changes. If there is no key then the Flutter framework can get confused about which widget changed.
When to use Keys?
Only use them when the framework needs your help to know which widget to update.
Most of the time you don't need to use keys. Since keys are mostly only useful for maintaining state, if you have a stateless widget whose children are all stateless, then there is no need to use a key on it. It won't hurt to use a key in this case, but it also won't help.
There are some micro-optimizations you can make using keys. See this article.
Where to use Keys?
Put the key at the part of the widget tree where the reordering or addition/deletion is taking place. For example, if you are reordering the items of a ListView whose children are ListTile widgets, then add the keys to the ListTile widgets.
What kind of Keys to use?
A key is just an id, but the kind of ID you use can vary.
ValueKey
A ValueKey is a local key that takes a simple value like a string or integer.
ObjectKey
If you widget is displaying more complex data than a single value, then you can use an ObjectKey for that widget.
UniqueKey
This type of key is guaranteed to give you a unique ID every time. If you use it, though, do NOT put it in the build method. Otherwise your widget will never have the same ID and so the Element tree will never find a match to reuse.
GlobalKey
GlobalKeys can be used to maintain state across your app, but use them sparingly because they are similar to global variables. It is often preferable to use a state management solution instead.
Examples of using Keys
AnimatedLists
Changing the position in a column/row
TextFormField
References
Keys! What are they good for?
Using Keys in Flutter
Key is object that is used to identify a widget uniquely.
They are used to access or restore state In a StatefulWidget (Mostly we don't need them at all if our widget tree is all Stateless Widgets).
There are various types of key that I will try to explain on the basis of usage.
Purpose (key types)
1. Mutate the collection i.e. remove / add / reorder item to list in stateful widget like draggable todo list where checked items get removed
➡️ ObjectKey, ValueKey & UniqueKey
2. Move widget from one Parent to another preserving it's state.
➡️ GlobalKey
3. Display same Widget in multiple screens and holding its state.
➡️ GlobalKey
4. Validate Form.
➡️ GlobalKey
5. You want to give a key without using any data.
➡️ UniqueKey
6. If you can use a certain fields of data like UUID of users as unique Key.
➡️ ValueKey
7. If you do not have any unique field to use as key but object itself is unique.
➡️ ObjectKey
8. If you have multiple Forms or Multiple Widgets of the same type that need GlobalKey.
➡️ GlobalObjectKey, LabeledGlobalKey whichever is appropriate, similar logic to ValueKey and ObjectKey
❌ Do not use random string/number as key, it defeats the purpose of keys ❌
The Key is an optional parameter needed to preserve state in your widget tree, you have to use them if you want to move a collection of elements in your tree and preserve the state of them.
The best explanation can be found in this video by Google When to Use Keys - Flutter Widgets 101 Ep. 4
With Dart 2.12 or later, add ? after Key to make it optional if you want.
class Frog extends StatelessWidget {
const Frog({
Key? key,
this.color: const Color(0xFF2DBD3A),
this.child,
}) : super(key: key);
final Color color;
final Widget child;
#override
Widget build(BuildContext context) {
return new Container(color: color, child: child);
}
}