Why does Flutter State object require a Widget? - dart

The demo example has the following code, which is apparently typical of Flutter apps:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {...}
}
I'm OK, I guess, with MyHomePage overriding the StatefulWidget createState method. It's a little awkward but what the heck? And even depending on a State subclass. Fine.
But then having the State subclass turn around and depend on MyHomePage?! I'm having trouble wrapping my fairly abundant wits around that one.
So perhaps I'm unclear on what State<MyHomePage> is/does. With, say, Map<String, Object> the meaning is clear: Associate a string with an object. Can someone please elucidate? And if you could include something about the need for a state object to extend a widget, I would enjoy reading that.

This is to make widget properties access far easier.
When you do
new MyStatefulWidget(foo: 42, bar: "string")
Then you most likely want to access foo/bar from your State.
Without such syntax you'd have to type custom State constructor and pass all StatefulWidget subclass properties to State subclass inside createState. Basically you'd have that:
class MyStatefulWidget extends StatefulWidget {
final int foo;
MyStatefulWidget({this.foo});
#override
MyStatefulWidgetState createState() => MyStatefulWidgetState(foo: foo);
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
final int foo;
MyStatefulWidgetState({this.foo});
#override
Widget build(BuildContext context) {
return Container(
);
}
}
Boring. You have to write all fields of StatefulWidget subclass twice.
With the current syntax; you don't have to do this. You can directly access all properties of the currently instantiated widget within State by using widget field.
class MyStatefulWidgetState extends State<MyStatefulWidget> {
#override
Widget build(BuildContext context) {
print(widget.foo);
return Container();
}
}

In Flutter, everything is a widget and there are 2 types of widgets: Stateless and Statefull.
Stateless: A widget that does not require mutable state.
Statefull: A widget that has mutable state.
That is why all Statefull widget is dependent of a State<T>, because it manages changes (state) in the widget.

Related

Generic parameter type in Dart? [duplicate]

This question already has an answer here:
Why am I getting TypeError at runtime with my generic StatefulWidget class?
(1 answer)
Closed 10 months ago.
I'm trying to create a class that use a generic type as a parameter in a callback that returns some subtype of Flutter's Widget. Here's what I started with:
class Subscriber<P extends PublishingController> extends StatefulWidget {
const Subscriber({required this.builder, Key? key}) : super(key: key);
final Widget Function(P) builder;
#override
_SubscriberState<P> createState() => _SubscriberState<P>();
}
class _SubscriberState<P extends PublishingController> extends State<Subscriber> {
final P publisher = GetIt.instance.get<P>();
#override
void initState() {
publisher.subscribe(rebuild);
super.initState();
}
#override
Widget build(BuildContext context) {
return widget.builder(publisher);
}
#override
void dispose() {
publisher.unsubscribe(rebuild);
super.dispose();
}
void rebuild() {
setState(() {});
}
}
... with the Publisher:
mixin Publisher {
List<Function> subscribers = <void Function()>[];
void subscribe(Function f) {
subscribers.add(f);
}
void unsubscribe(Function f) {
subscribers.remove(f);
}
void publish() {
for (var f in subscribers) {
f();
}
}
}
class PublishingController with Publisher {}
... and how I called it:
child: Subscriber<MapController>(
builder: (controller) => Column(...
... with:
class MapController extends PublishingController {...
... but that gives me the error:
======== Exception caught by widgets library =======================================================
The following _TypeError was thrown building Subscriber<MapController>(dirty, state: _SubscriberState<MapController>#d7e05):
type '(MapController) => Column' is not a subtype of type '(PublishingController) => Widget'
I think I'm specifying the parameter type through the generics, and a function can return a subtype of its return type— what am I getting wrong here?
EDIT:
I got it working, but I'm not putting this in as an answer— I don't understand what the problem was, or why this version works; I changed my Subscriber class to:
abstract class Builder<P extends PublishingController> extends StatefulWidget {
const Builder({required this.builder, Key? key}) : super(key: key);
final Widget Function(P) builder;
}
class Subscriber<P extends PublishingController> extends Builder<P> {
const Subscriber({required builder, Key? key}) : super(builder: builder, key: key);
#override
_SubscriberState<P> createState() => _SubscriberState<P>();
}
Can someone explain why this change would make the difference?
Your _SubscriberState<P> class extends State<Subscriber>, which in your case is shorthand for State<Subscriber<PublishingController>>, not for State<Subscriber<P>>.
The static type of the _SubscriberState<P>'s inherited widget member therefore will be Subscriber<PublishingController>, and the static type of widget.builder will be Widget Function(PublishingController). At runtime, the associated Subscriber object has a reference to a Column Function(MapController) object. However, that cannot be treated as a Widget Function(PublishingController) since it does not accept all PublishingController arguments, so you end up with a runtime error.
See: Why am I getting TypeError at runtime with my generic StatefulWidget class?

How to detect hot reload inside the code of a Flutter App?

I have an asset file that need to be processed before it can be used. This asset file will be heavily edited and I would like to not to have to restart the application each time I make an edit.
I'm aware of the existence of the reassemble method on the State class. However, this requires having a dummy widget that overrides this method and putting it inside the app somewhere to get notified about hot reload.
class WdHotReloadNotifier extends StatefulWidget
{
final Function callback;
WdHotReloadNotifier(this.callback);
#override
State<StatefulWidget> createState() => WdHotReloadNotifierState(this.callback);
}
class WdHotReloadNotifierState extends State<WdHotReloadNotifier>
{
Function callback;
WdHotReloadNotifierState(this.callback);
#override
void reassemble()
{
super.reassemble();
callback();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
Then I can use it like this:
WdHotReloadNotifier((){print("HOT REALOADED 1");}),
WdHotReloadNotifier((){print("HOT REALOADED 2");}),
However, adding these to a single page means that it will work as long as the page is in the stack. And adding them to multiple pages means the hooks will execute more than once.
Is there a way in flutter to get notified globally about a hot reload?
Overriding the reassemble method on a State subclass is what you want.
But you can position the widget to a different location to change the behavior.
Consider the following widget which calls a callback on hot-reload and does nothing else:
class ReassembleListener extends StatefulWidget {
const ReassembleListener({Key key, this.onReassemble, this.child})
: super(key: key);
final VoidCallback onReassemble;
final Widget child;
#override
_ReassembleListenerState createState() => _ReassembleListenerState();
}
class _ReassembleListenerState extends State<ReassembleListener> {
#override
void reassemble() {
super.reassemble();
if (widget.onReassemble != null) {
widget.onReassemble();
}
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
You're free to insert that widget wherever you like.
Be it on a single page:
MaterialApp(
home: ReassembleListener(onReassemble: () => print("Foo"), child: Home()),
)
Or globally by wrapping the whole application:
ReassembleListener(
onReassemble: () => print('foo'),
child: MaterialApp(
home: Home(),
),
)

How to access Stateful widget variable inside State class outside the build method?

I am trying to access the 'updateNotesModel' variable declared in the code in the _UpdateNotesState. I found out that you can do it using the 'widget' keyword as shown in the Scaffold below. But the problem here is that I am trying to access the variable outside the build method in order to give the TextEditingController a default value. How can I achieve this?
class UpdateNotes extends StatefulWidget {
final NotesModel updateNotesModel;
UpdateNotes({Key key, this.updateNotesModel}): super(key: key);
#override
_UpdateNotesState createState() => _UpdateNotesState();
}
class _UpdateNotesState extends State<UpdateNotes> {
TextEditingController _titleController =
new TextEditingController(text: widget.updateNotesModel.someValue); //getting an error
}
#override
Widget build(BuildContext context) {
return Scaffold(
var a = widget.updateNotesModel.someValue
.
.
.
)
}
}
You can do it in initState:
class _UpdateNotesState extends State<UpdateNotes> {
TextEditingController _titleController = new TextEditingController();
#override
void initState() {
_titleController.text= widget.updateNotesModel.someValue;
super.initState();
}
}
Now you can use the late keyword. From the docs:
Dart 2.12 added the late modifier, which has two use cases:
Declaring a non-nullable variable that’s initialized after its declaration.
Lazily initializing a variable.
class _UpdateNotesState extends State<UpdateNotes> {
late TextEditingController _titleController = new TextEditingController(text: widget.updateNotesModel.someValue);
#override
Widget build(BuildContext context) {
.
.
.
}
}
that unfortunately cannot be done as, widget keyword cannot be accessed while initializing

Flutter: Where to add listeners in StatelessWidget?

If I were using a StatefulWidget, then I would be listening to a Stream for example inside the initState method. Where would I do the equivalent in a StatelessWidget (like to use Bloc with streams for state management)? I could do it in the build method but since these are repetitively I wondered if there is a more efficient way than checking for existent listeners like below. I know that this is a redundant and useless example but it's just to show the problem.
import "package:rxdart/rxdart.dart";
import 'package:flutter/material.dart';
final counter = BehaviorSubject<int>();
final notifier = ValueNotifier<int>(0);
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
if (!counter.hasListener)
counter.listen((value) => notifier.value += value);
return MaterialApp(
home: Scaffold(
body: Center(
child:FlatButton(
onPressed: () => counter.add(1),
child: ValueListenableBuilder(
valueListenable: notifier,
builder: (context, value, child) => Text(
value.toString()
),
),
)
),
)
);
}
}
There is no clean way to have a StatelessWidget listen to a Listenable/Stream.
You will always need a StatefulWidget.
On the other hand, you can use composition to write that StatefulWidget just once, and be done with it.
Common examples for that pattern are widgets such as ValueListenableBuilder, StreamBuilder, or AnimatedBuilder. But it is possible to do the same thing, for listening too.
You'd use it this way:
class Foo extends StatelessWidget {
Foo({Key key, this.counter}): super(key: key);
final ValueListenable<int> counter;
#override
Widget build(BuildContext context) {
return ValueListenableListener(
valueListenable: counter,
onChange: (value) {
// TODO: do something
},
child: Something(),
);
}
}
Where ValueListenableListener is implemented this way:
class ValueListenableListener<T> extends StatefulWidget {
const ValueListenableListener(
{Key key, this.valueListenable, this.onChange, this.child})
: super(key: key);
final ValueListenable<T> valueListenable;
final ValueChanged<T> onChange;
final Widget child;
#override
_ValueListenableListenerState createState() =>
_ValueListenableListenerState();
}
class _ValueListenableListenerState extends State<ValueListenableListener> {
#override
void initState() {
super.initState();
widget.valueListenable?.addListener(_listener);
_listener();
}
#override
void didUpdateWidget(ValueListenableListener oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.valueListenable != widget.valueListenable) {
oldWidget.valueListenable?.removeListener(_listener);
widget.valueListenable?.addListener(_listener);
_listener();
}
}
#override
void dispose() {
widget.valueListenable?.removeListener(_listener);
super.dispose();
}
void _listener() {
widget.onChange?.call(widget.valueListenable.value);
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
You shouldn't. Not handling variables that might have their values modified is the very purpose of a Stateless widget:
A stateless widget never changes.
UPDATE:
I think this is a problem of understanding Flutter's state management concepts. This new recommended way by the Flutter team should clear some confusions.
You could do something like this:
class ExampleWidget extends StatelessWidget {
bool _initialized = false;
#override
Widget build(BuildContext context) {
if (!_initialized) {
_initialized = true;
// Add listeners here only once
}
return Container();
}
}
But you shouldn't! In fact, your IDE will give you a warning, because this is not the way to go with Stateless widget as it is marked as #immutable. If you need to use lifecycle methods (like initState()) you should make it a Stateful widget. There's no big deal.
This is achievable with flutter_bloc package. The code to be run in initstate can be added inside BlocListener on whatever state you want.
BlocProvider(
create: (BuildContext context) =>
CategoryBlock()..add(LoadCategories()),
child: BlocListener<CategoryBlock, CategoryStates>(
listener: (context, state) {
//Example to add a listener for listview
if (state is LoadCategoriesSuccess) {
itemPositionsListener.itemPositions.addListener(() {
print(itemPositionsListener.itemPositions.value);
});
}
}
You could have your streams being instantiated in a StatefulWidget and then passed down to your StatelessWidgets as an option, so the parent widget would only have a role of controlling the lifecycle of the stream while the child would be using the stream to update the view.
Regarding the earlier answer:
There's no problem in using StreamBuilders inside your StatelessWidgets since the StreamBuilder itself is a a Widget that extends from StatefulWidget and will take care of it's own state and dispose correctly on its own.

What does ClassName method() => ClassName() mean in Dart?

In a Flutter projet, I saw this snippet: _State createState() => _State();
What does ClassName method() => ClassName() mean in Dart and where is the method createState() defined what does all of this mean for Flutter?
Here is the full code:
class Nearby extends StatefulWidget {
#override
_State createState() => _State();
}
class _State extends State<Nearby> {
GoogleMapController mapController;
LatLng _center;
#override
void initState() {
super.initState();
_getCurrentLocation();
}
#override
Widget build(BuildContext context) {
return Container()
}}
From the Dart language tour:
The => expr syntax is a shorthand for { return expr; }. The => notation is sometimes referred to as arrow syntax.
Therefore:
_State createState() => _State();
means that createState() is a function that invokes a _State constructor (with no arguments) and returns the newly constructed _State object.
As for what it means for Flutter, see the StatefulWidget.createState documentation:
Creates the mutable state for this widget at a given location in the tree.
Subclasses should override this method to return a newly created instance of their associated State subclass
(You also might want to look at the StatefulWidget documentation.)

Resources