Is it safe to have a Widget class that returns a widget conditionally? - dart

Quite often I only want to draw a Widget based on a condition.
For example, I may be creating a component that displays a FadeIn.image but the image: may not be set in the CMS. In this case I want to ignore drawing the FadeIn.image and just return an empty container.
for context, I had done,
child: (someValue == null) ? new Container() : new LabelComponent(label: myStringLabel)
But this broke hot reload and I needed to replace with,
child: _createLabelComponent(myStringLabel),
Widget _createLabelComponent(String label)
{
if(label == null) {
return new Container();
} else {
return new LabelComponent(label: label)
}
}
Is the below safe to work and will not break hot reload? It seems to work at the moment but before I replace all my conditions with this Widget, I'd like some more feedback.
class ConditionalWidget extends StatefulWidget {
final bool condition;
final Widget conditionalWidget;
ConditionalWidget(this.condition, this.conditionalWidget, {Key key});
#override
State createState() => new ConditionalWidgetState();
}
class ConditionalWidgetState extends State<ConditionalWidget> {
ConditionalWidgetState();
#override
void initState()
{
super.initState();
}
#override
Widget build(BuildContext context)
{
if(widget.condition) {
return widget.conditionalWidget;
} else {
return new Container();
}
}
}

Nothing magic here, child is a property of a Container class:
it is safe to use whatever expression returns a widget for child property, obviously also conditional expressions
condition ? expr1 : expr2.
If hot reload in broken check for other causes.
For example your code that break hot reload:
child: (someValue == null) ? new Container() : new LabelComponent(label: myStringLabel)
uses someValue and myStringLabel, whereas in _createLabelComponent there is only a label variable.

class StatmentExample extends StatelessWidget {
Widget build(BuildContext context) {
return Text((() {
if(true){
return "tis true";}
return "anything but true";
})());
}
}
wrap your statements in a function
(() {
// your code here
}())

Related

How to call setState outside class/stateful widget?

I have the following variables
String _warningMessage;
bool _warningVisibility;
Which I want to update via a Class which implements an interface
class _UserSignupInterface extends _SignupSelectUsernamePageState
implements UserSignupInterface {
#override
void onSuccess() {
_hideWarning();
_navigateToUserPage();
}
#override
void onError(String message) {
_isSignupClickable = true;
if(message != null) {
_displayWarning(message);
}
}
}
with the _displayWarning code (which is inside the _SignupSelectUsernamePageState)
void _displayWarning(String message) {
if (message != null) {
setState(() {
widget._warningMessage = message;
widget._warningVisibility = true;
});
}
}
However, whenever I call the _displayWarning(message) from outside the _SignupSelectUsernamePageState. I get an error saying
Unhandled Exception: setState() called in constructor
Is there a proper way of updating these variable states outside their class? Which in my case, I'm calling the _displayWarning(message) from another class that implements an interface
You have to decide whether this is a value that is changed internally within the widget, or if that's a value that changes externally to it.
If it's internal, the common thing is to place them in the State class with the _ on them, they could start with a value for instance set on initState and every time they change you call setState to indicate that.
However, if they change outside the widget, then you place them on the StatefulWidget class (as you seem to have done), you leave them without the _ as they are actually public and you even make them final and place them in the constructor to allow them to be set.
In this last case, if in the State class you must be aware of a change in the widget, you can implement didUpdateWidget, but that's not mandatory.
Of course you can mix both things, having a _warningMessage in the State, so you can update it with setState, but with an initial value defined in initState that comes from the widget.
Again, if the widget changes externally, you can again update the value of the _warningMessage with the new widgets value.
Something like that: (I didn't test this code)
class YourWidget extends StatefulWidget {
YourWidget({this.warningMessage});
final String warningMessage;
#override
State<YourWidget> createState() => new _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
String _warningMessage;
#override
void initState() {
super.initState();
_warningMessage = widget.warningMessage;
}
#override
didUpdateWidget(ReorderableListSimple oldWidget) {
super.didUpdateWidget(oldWidget);
_warningMessage = widget.warningMessage;
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(_warningMessage),
RaisedButton(
child: Text("Change Message"),
onPressed: () {
setState(() {
_warningMessage = "new message from within the State class";
});
}
)
],
);
}
}
So in this example you can change the warningMessage externally, like in the parent Widget you are able to pass a different message. However, if you need, you can also set it internally using setState, as it's happening in the button's onPressed.
What you might check is wether you actually need that property exposed in the Widget, maybe you don't! Then, the example would look like that:
class YourWidget extends StatefulWidget {
#override
State<YourWidget> createState() => new _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
String _warningMessage;
#override
void initState() {
super.initState();
_warningMessage = "default message, no need for widget";
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(_warningMessage),
RaisedButton(
child: Text("Change Message"),
onPressed: () {
setState(() {
_warningMessage = "new message from within the State class";
});
}
)
],
);
}
}
Just create a static value in the state of your widget class, then when you build the widget, set it's value to the widget. So whenever you want to call it to setState(), just call the static value.

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.

Add Widget based on Condition

I want to add a MaterialButton in a widget's build method only if some condition is true. For instance:
if (..) {
MaterialButton(..)
}
How do I achieve this in Flutter?
It is very simple using conditional operators:
build(context) => condition ? MaterialButton(...) : Container();
In this case, condition is a boolean expression (returning bool, same thing you would put into an if-statement) and the empty Container will render as empty space and will not take up any space.
Yes actually I see at least two ways of doing it.
The first one is :
Container(
child: your_test_here ? MaterialButton() : Container(height:0), //or any other widget but not null
)
The other way of doing it is by creating a function:
Widget your_test_widget(){
if (your_test_here){
return MaterialButton();
}
else{
return Container(height:0); //or any other widget but not null
}
}
Call it in your tree :
Container(
child:your_test_widget(),
)
Hope it helps !!
Another option if your widget appears in a List<Widget>/<Widget>[] (i.e. with Row, Column or ListView), you can do something like:
#override
Widget build(BuildContext context) => Column(children: _buildChildren());
List<Widget> _buildChildren() {
var list = [/* put widgets that always show at top here */];
if(myCondition) list.add(MyConditionalWidget());
return list;
}
It's a nice question!
Assuming you want to add your button in a Container you can do the following:
Container (
child: MyWidget()
)
Widget MyWidget() {
if (...) {
return new MaterialButton(
...
)
}
return Container();
}
Obviously here adding in the Container is an example, you can assign a method that returns a Widget to every child elements!
A more explicative example:
class Example extends StatefulWidget {
// code
#override
EventPageState createState() => ExampleState();
}
class ExampleState extends State<Example> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
Text("Static Widget")
CustomDynamicWidget(),
],
),
)
}
Widget CustomDynamicWidget() {
if (...) {
return new Text("Dynamic Widget IF block")
} else if (...) {
return new Text("Dynamic Widget ELSE IF block")
}
return Container();
}
}

ScopedModelDescendant String returns null. Not retrieving state

I am refactor app to use scoped_model but have issue. State not seem to be pass correct between widget.
I am start with add ScopedModel to top of widget tree on startup.:
#override
Widget build(BuildContext context) {
UserModel().initialiseValues();
return ScopedModel<UserModel>(
model: UserModel(),
child: MaterialApp(
home: new Page(),
),
);
}
This call .initialiseValues(); so the model read from SharedPreferences and store in variables:
class UserModel extends Model {
String _name;
String get name => _name;
void initialiseValues() async {
SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
_name = sharedPrefs.getString('name');
print(‘_name from sharedPrefs is $_name');
print(‘name from sharedPrefs is $name');
notifyListeners();
}
}
Both print statement return correct name.
But when I try call using ScopedModelDescendant in another page name return null:
class PageFive extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<UserModel>(
builder: (context, child, model) =>
Text('${model.name}'),
);
}
}
Text('${model.name}'), return null
Anyone know why name return null?
I have look hard for solve but no idea why.
There seems to be two problems:
1.- You are calling .initialiseValues() in a static way when the method is not static. And then you are creating a new diferent UserModel inside the ScopedModel.
2.- You are calling an async function inside the first build() method but you are not certain when it is going to finish and possibly, the UserModel is not fully initialized when building the tree.
You should maybe have a boolean variable (or check the .name for null) and set it to true when initialized.
In the build method, if that variable is false you could return a CircularProgressIndicator and when true just return your widget.
#override
Widget build(BuildContext context) {
var userModel = UserModel()..initialiseValues();
return ScopedModel<UserModel>(
model: userModel,
child: MaterialApp(
home: new Page(),
),
);
}
class PageFive extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<UserModel>(
builder: (context, child, model) {
if (model.name == null)
return CircularProgressIndicator();
else
return Text('${model.name}');
});
}
}

Am not rebuilding objects on build() call

Despite flutter calling build (and printing the correct information as below), it doesn't seem to build new TaskWidgets (the print in TaskWidgetState's constructor is not called). This is creating some unusual behaviour in my application (for example, the persistence of deleted ListView items).
I have the following code:
class TaskWidget extends StatefulWidget {
TaskWidget({this.task, this.callToSave, this.callToDelete});
final Task task;
final Function callToSave;
final Function callToDelete;
#override
State<StatefulWidget> createState() {
return new TaskWidgetState(task, callToSave, callToDelete);
}
}
class TaskWidgetState extends State<TaskWidget>{
Task task;
Function toCallOnChange;
Function callToDelete;
TaskWidgetState(Task task, Function callToSave, Function callToDelete){
print("I'm a task widget for " + task.serialise().toString());
this.task = task;
toCallOnChange = callToSave;
this.callToDelete = callToDelete;
}
}
and
class ToDoListWidget extends State<ToDoList>{
List<Task> _toDo = new List<Task>();
...
#override
Widget build(BuildContext context) {
print("building");
return new Scaffold(
body: new ListView(
children: <Widget> [
generateCard(),
...
]
),
);
}
Widget generateCard() {
return new Card(
child: new Column (
children: generateWidgets()
),
...
);
}
List<Widget> generateWidgets() {
print("generating Widgets");
List<Task> tasks = getTasks();
List<Widget> widgets = new List<Widget>();
print("I have " + tasks.length.toString() + " widgets to build");
for(Task t in tasks) {
print(t.title);
TaskWidget widget = new TaskWidget(task: t, callToSave: saveList, callToDelete: deleteTask,);
widgets.add(widget);
}
return widgets;
}
}
Prints out:
building
I/flutter (28783): Returning for Daily
I/flutter (28783): // correct, undeleted task
but onscreen state doesn't reflect this
You're not using State and Stateful Widget properly.
How it works in flutter is that the Widget can be created many times, but there will most likely only be one instance of a State to go along with it.
It's a bit of an anti-pattern to have a constructor for a state.
Instead you should be doing something like this:
class TaskWidget extends StatefulWidget {
TaskWidget({this.task, this.callToSave, this.callToDelete});
final Task task;
final Function callToSave;
final Function callToDelete;
#override
State<StatefulWidget> createState() => new TaskWidgetState();
}
class TaskWidgetState extends State<TaskWidget>{
Widget build(Context context) {
// you can just use the widget.task, this is to illustrate.
var task = widget.task;
var callToSave = widget.callToSave;
var callToDelete = widget.calltoDelete;
}
}
This way, when the widget changes, your state will be re-built and will use whatever the updated values are that were passed into the widget.

Resources