I'm developing a mobile app in Flutter and have encountered a problem while trying to pass a function as a parameter to a widget.
To be more precise:
class Test extends StatefulWidget {
final Function(bool) onChanged;
const Test({Key key, this.onChanged}) : super(key: key);
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
bool switchValue = false;
#override
Widget build(BuildContext context) {
return Container(
child: Switch(
value: switchValue,
onChanged: (bool value) {
setState(() => switchValue = value);
widget.onChanged(value);
}));
}
}
It throws NoSuchMethodError: "The method 'call' was called on null" when the widget is used without defining the onChanged function.
How to define a default function for the onChanged parameter? The parameter should be optional.
I have tried with:
() {} - A value of type 'Null Function( )' can't be assigned to a variable of type 'dynamic Function(bool)'.
(bool) {} - The default value of an optional parameter must be constant.
Solutions without using default value are:
to check if onChange parameter is not null before calling it, or
to define it every time when the widget is used - onChanged: (bool val) {}
Any suggestions would be appreciated.
You can define a default function like this.
void emptyFunction(bool value) {
if (value) {
// do something
} else {
// do something
}
}
const Test({Key key, this.onChanged = emptyFunction}) : super(key: key);
you can check a sample code showing this in action on dartpad. https://dartpad.dev/30fc0fdc02bec673779eebc733753c05
Related
So, my code:
Type _typeOf<T>() => T;
abstract class BlocBase {
void dispose();
}
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider({
Key? key,
required this.child,
required this.bloc,
}) : super(key: key);
final Widget child;
final T bloc;
#override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context) {
final type = _typeOf<_BlocProviderInherited<T>>();
_BlocProviderInherited<T> provider =
context.getElementForInheritedWidgetOfExactType<type>()?.widget;
return provider?.bloc;
}
}
class _BlocProviderState<T extends BlocBase> extends State<BlocProvider<T>> {
#override
void dispose() {
widget.bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new _BlocProviderInherited<T>(
bloc: widget.bloc,
child: widget.child,
);
}
}
class _BlocProviderInherited<T> extends InheritedWidget {
_BlocProviderInherited({
Key? key,
required Widget child,
required this.bloc,
}) : super(key: key, child: child);
final T bloc;
#override
bool updateShouldNotify(_BlocProviderInherited oldWidget) => false;
}
The offending line is this one:
_BlocProviderInherited<T> provider =
context.getElementForInheritedWidgetOfExactType<type>()?.widget;
And throws the error:
A value of type 'Widget?' can't be assigned to a variable of type '_BlocProviderInherited<T>'.
Edit: Additionally, it is throwing an error on the next line:
return provider?.bloc;
Error:
A value of type 'T?' can't be returned from the method 'of' because it has a return type of 'T'.
This is previously working code from my published app that no longer works after upgrading flutter from a quite old version.
Anyone know what it wants from me?
Edit: Have included full code, as referenced functions were not previously shown.
_BlocProviderInherited<T> provider =
context.getElementForInheritedWidgetOfExactType<type>()?.widget;
This line fails because the variable types don't match. A Widget? is not a _BlocProviderInherited<T>. My guess is that the extends InheritedWidget used to mean that _BlocProviderInherited<T> had a type of Widget, but now (based on the InheritedWidget documentation and the method documentation) that is not the case.
A value of type 'T?' can't be returned from the method 'of' because it has a return type of 'T'.
This error is caused by trying to return a variable that can be null from a method that only returns non-null variables. Flutter has changed a lot between major releases, including adding null safety. To fix it, change that line to return provider!.bloc; or provide a default bloc after: return provider?.bloc ?? <default value here>. Either way the returned value won't be null.
can you comment?
typedef ButtonChangedCallback = void Function<T>(T value);
class MyWidget<T> extends StatefulWidget {
ButtonChangedCallback? onCallback;
const MyWidget(
{required Key key,
this.onCallback})
: super(key: key);
I would like to create such template widget to be used with different enums. So it can signal what value from the enum was selected.
But I am unable to find how to later assign to the "onCallback" method.
MyWidget(
key: const Key("RadioControls"),
onCallback: <MyEnum>(MyEnum value) =>
setState(() {
someSettings.value = value;
}),
)
This does not work with Value of type MyEnum can not be assigned to variable of type MyEnum By some experiments I discovered that inside the lambda does not seem to correspond to MyEnum as defined before.
EDIT: Solution
typedef ButtonChangedCallback<T> = void Function(T value);
class MyWidget<T> extends StatefulWidget {
ButtonChangedCallback<T>? onCallback;
used as
MyWidget<MyEnum>(
key: const Key("RadioControls"),
onCallback: (MyEnum value) =>
setState(() {
someSettings.value = value;
}),
)
When you do:
typedef ButtonChangedCallback = void Function<T>(T value);
class MyWidget<T> extends StatefulWidget {
ButtonChangedCallback? onCallback;
You're declaring that ButtonChangedCallback must be a generic function. Whatever callback is assigned to MyWidget.onCallback must itself be generic. That is, you would only be able to use it as:
MyWidget(onCallback: <T>(T x) { ... });
In the above, T is the name of the type parameter to your generic, anonymous function. It is not a type argument. T could be named anything, and in your attempt, you happened to name it MyEnum, so you ended up in a confusing situation where a generic type parameter had the same name as an actual type.
Additionally, the type parameter for the function would be unrelated to the type parameter for MyWidget.
What you probably want is to for the typedef to be generic and for the Function object to not be:
typedef ButtonChangedCallback<T> = void Function(T value);
class MyWidget<T> extends StatefulWidget {
ButtonChangedCallback<T>? onCallback;
and now you can use it as:
MyWidget<MyEnum>(
onCallback: (value) =>
setState(() {
someSettings.value = value;
}),
This way should work:
class MyWidget<T> extends StatefulWidget {
final Function<T>? callback;
const MyWidget({required this.key, this.callback}) : super(key: key);
}
And you instantiate the widget this way:
MyWidget<MyEnum>(
key: const Key("RadioControls"),
callback: (MyEnum value) {
setState((){
someSettings.value = value;
});
}
)
I have a model which initializes multiple objects. Whenever a property inside one of the objects changes, the ui does not rebuild. I took care of overwriting the hash and == of the objects using Equatable so provider should detect the changes. Here is a sample from the code that shows the important parts:
class Tag extends ChangeNotifier with EquatableMixin {
Tag(this._title, this._value, this._selected);
final String _title;
String _value;
bool _selected;
#override
List<Object> get props => [_title, _value, _selected];
String get title => _title;
String get value => _value;
set value(String newValue) {
_value = newValue;
notifyListeners();
}
bool get selected => _selected;
set selected(bool newValue) {
_selected = newValue;
notifyListeners();
}
}
class TagModel extends ChangeNotifier {
TagModel() {
_tag1.addListener(notifyListeners);
_tag2.addListener(notifyListeners);
_tag3.addListener(notifyListeners);
}
final Tag _tag1 = Tag("", "", false);
final Tag _tag2 = Tag("", "", false);
final Tag _tag3 = Tag("", "", false);
Tag get tag1 => _tag1;
Tag get tag2 => _tag2;
Tag get tag3 => _tag3;
//this function does not trigger a widget rebuild
void loadTags() {
_tag1.title = "tag 1";
_tag2.title = "tag 2";
_tag3.title = "tag 3";
}
}
class TagPane extends StatelessWidget {
const TagPane({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
//this does not change when loadTags function is called
final tag1 = context.select((TagModel tagModel) => tagModel.tag1)
return Container(
.
.
.
ElevatedButton(
onPressed: () => context.read<TagModel>().loadTags(),
)
);
}
}
did you try adding a call to NotifyListeners() at the end of the method?
Try using Statefull Widget instead of Stateless than setState when pressing Elevated button. e.g
class TagPane extends StatefullWidget {
const TagPane({Key? key}) : super(key: key);
#override
_TagPaneState createState() => _TagPaneState();
}
class _TagPaneState extends State<TagPane>{
#override
Widget build(BuildContext context) {
//this does not change when loadTags function is called
final tag1 = context.select((TagModel tagModel) => tagModel.tag1)
return Container(
.
.
.
ElevatedButton(
onPressed: () => setState(() {
context.read<TagModel>().loadTags());
),
)
);
}
}
I implemented a custom DropdownButton widget, but I don't know how to implement it's setState. I would like to pass items and selectedItem to the widget, and let it to handle it's own state. And retrieve selected item when needed by myDropdownButton.selectedItem. How I could implement it?
class MyDropdownButton extends StatefulWidget {
final String selected;
final List<MyDropdownItem> items;
MyDropdownButton({Key key, this.selected, this.items})
: super(key: key);
#override
_MyDropdownButtonState createState() => _MyDropdownButtonState();
}
class _MyDropdownButtonState extends State<MyDropdownButton> {
Widget build(BuildContext context) {
return DropdownButtonFormField(
value: widget.selected,
onChanged: (String value) {
widget.selected = value;
},
But the selected is final and cannot be modified. How to implement it?
Thank you!
There are two questions here:
Updating the widget, using setState.
Passing the value back to the widget that is using the Dropdown with a callback. Medium Article on callbacks
Firstly to have the dropdown update, you need to call a setstate on the value change. But first, you'll need to receive the value passed, usually this is done in initstate.
Second, you need to use a callback function. The class that calls this widget/class can then receive and process that value
class MyDropdownButton extends StatefulWidget {
final String selected;
final List<MyDropdownItem> items;
final Function(String) valueReturned; //callback function
MyDropdownButton({Key key, this.selected, this.items, this.valueReturned})
: super(key: key);
#override
_MyDropdownButtonState createState() => _MyDropdownButtonState();
}
class _MyDropdownButtonState extends State<MyDropdownButton> {
String sel;
#override
void initState() {
super.initState();
sel = widget.selected; //get the value passed
}
Widget build(BuildContext context) {
return DropdownButtonFormField(
value: sel
onChanged: (String value) {
setState() {
sel = value;
widget.valueReturned(value); //this will trigger the callback function
},
}
In the code that calls the widget, you will need to listen to and handle the response.
Container(
child: MyDropdownButton(items: items, selected: selected, valueReturned: _handleValueReturned))
_handleValueReturned(String value) {
thingToUpdate = value;
}
Define a local variable and initialize it in initState():
String _selected;
#override
void initState() {
super.initState();
_selected = widget.selected;
}
use setState to update your local variable as the selection changes:
onChanged: (String value) {
setState(() {_selected = value;})
}
To retrieve the value, define a getter in your class:
String get selectedItem => _selected;
You can then access the selected item using myDropdownButton.selectedItem.
For more detailed explanation on implicit and explicit getters/setters see How do getters and setters change properties in Dart?
import 'package:flutter/material.dart';
class ThemeSwitcher extends InheritedWidget {
final _ThemeSwitcherWidgetState data; // We'll use ThemeSwitcher to get access to the current state of ThemeSwitcherWidget
const ThemeSwitcher({
Key key,
#required this.data,
#required Widget child,
}) : assert(child != null),
super(key: key, child: child);
static _ThemeSwitcherWidgetState of(BuildContext context) { //This method returns the current state of the ThemeSwitcherWidget. This will be used down the tree
return (context.dependOnInheritedWidgetOfExactType(ThemeSwitcher)
as ThemeSwitcher)
.data;
}
#override
bool updateShouldNotify(ThemeSwitcher old) {
return this != old;
}
}
class ThemeSwitcherWidget extends StatefulWidget {
final bool initialDarkModeOn; // this is the initial state of the variable
final Widget child; // child to which this boolean variable should be propagated upon change. This will be our app in this case
ThemeSwitcherWidget({Key key, this.initialDarkModeOn, this.child})
: assert(initialDarkModeOn != null),
assert(child != null),
super(key: key);
#override
_ThemeSwitcherWidgetState createState() => _ThemeSwitcherWidgetState();
}
class _ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
bool isDarkModeOn;
void switchDarkMode() { //method used to toggle dark mode during the runtime of the app
setState(() {
isDarkModeOn = !isDarkModeOn;
});
}
#override
Widget build(BuildContext context) {
isDarkModeOn = isDarkModeOn ?? widget.initialDarkModeOn; // this is the build method which would build the widget tree with the above info
return ThemeSwitcher(
data: this,
child: widget.child,
);
}
}
Too many positional arguments: 0 expected, but 1 found.
Try removing the extra positional arguments, or specifying the name for named arguments.
This is the Error I am continuously facing the issue after trying many methods.
I would like to know how would this problem can be solved as I am not getting any good solution from searches.
Return the following statement in _ThemeSwitcherWidgetState of(BuildContext context) method of your code:
return (context.dependOnInheritedWidgetOfExactType<ThemeSwitcher>()).data;