Using widget property VS taking arguments on state class - flutter

Given a stateful widget which takes arguments when it's called, there are two options (that I know of).
I can either use widget.arg to access the data in the state object, or I can create new variables and a new constructor in the state object.
Now I've mostly used the second one and there are some use cases in which the first one causes some problems. However, it looks more concise and readable (I guess).
My question is which one is a better practice?
Example code:
First option:
class Home extends StatefulWidget {
final String email;
const Home({Key key, this.email}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
String example() {
return widget.email;
}
Second option:
class Home extends StatefulWidget {
final String email;
const Home({Key key, this.email}) : super(key: key);
#override
_HomeState createState() => _HomeState(email);
}
class _HomeState extends State<Home> {
final String email;
_HomeState(this.email);
String example() {
return email;
}

I use both approaches, however, i don't use a constructor for the second approach because idk i don't like it. I store a reference in initState. Something like email = widget.email;.
It really depends. It's mostly preference. But i use the widget. approach often, it avoids boilerplate code, and it's a way of identifying which arguments come from the widget vs whcih arguments come from the state.
The flutter team also uses this approach. A LOT. Check the Material AppBar source code. It would be a mess to declare the arguments twice and pass them to _AppBarState. It's cleaner and it works for them. And for me ;)

Don't use the second option, aka having a constructor on State. This is a bad practice.
Use the .widget.property syntax instead.
If you purposefully want to ignore the updates of a property, instead use initState:
class Example {
Example(this.initialText);
final String initialText;
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
String text;
#override
void initState() {
text = widget.initialText;
}
}

Related

Initializing Flutter Stateful Widget: Why is it discouraged to pass initial values to `State` constructor?

Currently, when I would like initial values of a stateful widget to be configurable, I follow a pattern that looks like
class MyWidget extends StatefulWidget {
final String? initialValue;
MyWidget({ this.initialValue });
#override State createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
String statefulValue = "default initial value";
#override
void initState() {
super.initState();
if (widget.initialValue != null) { statefulValue = widget.initialValue; }
}
// ...
}
This works, but seems a bit heavyweight to me to achieve something I have to think is a very common use case. First, it doesn't make sense to me that initialValue should have to be a field at all, since its use is only to initialize the state, and then is no longer needed. Second, I think it would avoid some boiler plate if the state class could have a constructor that the stateful widget could call, so the above could look like:
class MyWidget extends StatefulWidget {
final String? initialValue;
MyWidget({ this.initialValue });
#override State createState() => MyWidgetState(initialValue: initialValue);
}
class MyWidgetState extends State<MyWidget> {
String statefulValue;
MyWidgetState({ String? initialValue }) : statefulValue = initialValue ?? "default initial value";
// ...
}
That doesn't exactly solve the first problem, but I think reads more easily. This however triggers the "Don't put any logic in createState" linter error. So my questions are
a) is there a pattern where the initial value doesn't have to be held on to longer than necessary?
b) why is passing parameters to the State constructor frowned upon?
You can provide default value on constructor
class MyWidget extends StatefulWidget {
final String initialValue;
const MyWidget({this.initialValue = "default initial value"});
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late String statefulValue = widget.initialValue;

Initializing non-mutable fields in Stateful Widgets in Flutter

When creating a stateful widget in flutter, you may want some fields of the widget to not be mutated. In that case, I'm having trouble trying to figure out if it is better to always reference those fields from the state's widget reference, or if it's better to declare those fields in the state still, and get any initial values from the widget. For example:
class MyStatefulWidget extends StatefulWidget {
final bool? mutateMe; // allows the user to provide an initial value of a mutable field
final bool? doNotMutateMe; // allows the user to provide the value of a field that is not intended to be mutated
MyStatefulWidget({ super.key, this.mutateMe, this.doNotMutuateMe });
#override State<MyStatefulWidget> createState() => MyStatefulWidgetState();
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
late bool mutateMe;
late bool doNotMutateMe; // <-- HERE: is it better to include this field here?
#override void initState() {
mutateMe = widget.mutateMe ?? true;
doNotMutateMe = widget.doNotMutateMe ?? false;
}
// ...
}
For a field like doNotMutateMe, that is not intended to be modified, does it make sense to re-create the field in the state object, or not, and always just refer to widget.doNotMutateMe instead?
I've read that the state object outlives the widget, so I'm curious what implications that might have here?
As you've included , I will prefer using widget.variableName on state class
class MyStatefulWidget extends StatefulWidget {
final bool? mutateMe;
final bool? doNotMutateMe;
const MyStatefulWidget({
super.key,
this.mutateMe = true,
this.doNotMutateMe = false,
});
#override
State<MyStatefulWidget> createState() => MyStatefulWidgetState();
}

Initialize StatefulWidget state null safe from widget constructor in Flutter

I want to have a StatefulWidget where I can pass the initial value for a non-nullable member of the widgets State from the widgets constructor.
My current solution (see below) seems to be not ideal, I see two problems with it:
The initial value has to be saved in the widget itself before passing it to the state.
The member in the sate has to be marked as late since it can only be set after initialization.
Is there a better way to initialize a StatefulWidget's state non-nullable member from a value passed to the widget constructor?
My current implementation:
class MyWidget extends StatefulWidget {
final String text;
const MyWidget({Key? key, required this.text}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late String text;
#override
void initState() {
text = widget.text;
super.initState();
}
#override
Widget build(BuildContext context) {
return Text(text);
}
}
(Not shown here, but later the text member should be changeable, that's why it is in the State)
hey there your code seems good.
but the better way is using bloc to pass and receive data.
any way . its not necessary to pass and fill data in initstate and _MyWidgetState .
you can receive your data directly in build widget As you wrote (widget.text)
here is some good things for avoid nullable
https://codewithandrea.com/videos/dart-null-safety-ultimate-guide-non-nullable-types/
You could use the constructor of State like this: _MyWidgetState(){ text=widget.text; }. The constructor will certainly be executed before initState and build methods.

"Avoid using private types in public APIs" Warning in Flutter

I have been working on a flutter project and I have noticed Avoid using private types in public APIs.
Is there a way to fix this warning?
class SubCategoriesPage extends StatefulWidget {
final MainModel mainModel;
// final Ads ad;
const SubCategoriesPage(this.mainModel, {Key? key}) : super(key: key);
#override
_SubCategoriesPage createState() { // Avoid using private types in public APIs.
return _SubCategoriesPage();
}
}
Because createState method return State<Example> so it's preventing returning any private State.
You need to update your code like this.
class SubCategoriesPage extends StatefulWidget {
final MainModel mainModel;
// final Ads ad;
const SubCategoriesPage(this.mainModel, {Key? key}) : super(key: key);
#override
State<SubCategoriesPage> createState() { // Avoid using private types in public APIs.
return _SubCategoriesPage();
}
}
Since this is a StatefulWidget, I'm guessing the _SubCategoriesPage class inherits from State, since it's being returned by createState().
If so, the return type can be changed to State. Since State is public, it can safely be returned from the public createState() method.
Just change _SubCategoriesPage by State
For those using Riverpod, change _SubCategoriesPage by ConsumerState
I had the same issue using this code
class MyPage extends StatefulWidget {
const MyPage({super.key});
#override
_MyPageState createState() => _MyPageState();
}
I tried using this method - State createState() { // Avoid using private types in public APIs.
return _MyPageState();
but than I got this error message - The name MyPageState isn't a type so it can't be used a type argument.

The member 'setState' can only be used within instance members of subclasses of 'package:flutter/src/widgets/framework.dart'

(i18n) I use setState from outside of MyApp class to change language, I got this warning, and don't know how to solve it.
info: The member 'setState' can only be used within instance members of subclasses of 'package:flutter/src/widgets/framework.dart'. (invalid_use_of_protected_member at [flutter_firebase_authen] lib\app.dart:22)
class MyApp extends StatefulWidget {
final FirebaseAnalyticsObserver observer;
const MyApp({
Key key,
#required this.observer,
}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
static void setLocale(BuildContext context, Locale newLocale) {
final _MyAppState state = context.ancestorStateOfType(const TypeMatcher<_MyAppState>());
state.setState(() {
state.locale = newLocale;
});
}
}
The warning message is quite clear: the function setState should only be called from within the class, not from another class.
The workaround is simple, write a helper function inside your State class, that calls setState for you. For example:
refresh() => setState(() {});
Now from outside this class, you can call state.refresh().
(But really, if you are calling setState from another class, maybe you should look into ValueNotifier, or StreamBuilder, etc.)