Flutter switch widget not updating - flutter

I have created a stateless widget that has a Flutter switch widget I implement this widget in the parent and pass in the required parameters but it won't change value when I press the switch.
I thought it might have been due to the fact that the child widget wasn't stateful but that made no difference.
Here is a brief example of code from my two widget files
class SettingsButton extends StatelessWidget {
final String text;
final bool initalValue;
final void Function(bool) onOffCallback;
SettingsButton({
this.text,
this.initalValue = false,
this.onOffCallback,
});
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
child: SubHeading(text),
),
Switch(
onChanged: isOnOff ? onOffCallback : null,
activeColor: Theme.of(context).accentColor,
value: initalValue,
)
]);
class _SettingsState extends State<Settings> {
bool test = true;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: ListView(
children: [
Column(
children: [
SettingsButton(
text: "Test",
onOffCallback: (test) => setState(() {
print("Called");
test = !test;
}),
initalValue: test,
),
],
)
],
),
);
}

You're setting the test variable recieved in the callback, rather than that defined in the _SettingsState class. What you should have is this for the callback:
onOffCallback: (newTest) => setState(() {
print("$newTest");
test = newTest;
// or (it shouldn't matter)
test = !test;
print("$test");
}),

Related

Flutter riverpod Change notifier not updating value

I'm using riverpod to manage states of some variables in my app like opacity, stroke width and color for my coloring app.
Here's my opacity class inside notifier.dart:
class OpacityChangeNotifier extends ChangeNotifier {
OpacityChangeNotifier([this.opacity = 1.0]);
double opacity;
void changeOpacity(double providedOpacity) {
opacity = providedOpacity;
notifyListeners();
}
void printOpacity() {
print(opacity);
}
}
This is from my OpacityPicker.dart:
final _opacityProvider = ChangeNotifierProvider<OpacityChangeNotifier>((ref) {
return OpacityChangeNotifier();
});
class OpacityPicker extends ConsumerWidget {
const OpacityPicker({Key key}) : super(key: key);
#override
Widget build(BuildContext context, ScopedReader watch) {
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
onPressed: () {
context.read(_opacityProvider).changeOpacity(0.1);
context.read(_opacityProvider).printOpacity();
},
icon: Icon(Icons.opacity, size: 20),
),
IconButton(
onPressed: () {
context.read(_opacityProvider).changeOpacity(0.5);
context.read(_opacityProvider).printOpacity();
},
icon: Icon(Icons.opacity, size: 30),
),
IconButton(
onPressed: () {
context.read(_opacityProvider).changeOpacity(1.0);
context.read(_opacityProvider).printOpacity();
},
icon: Icon(Icons.opacity, size: 40),
),
],
),
);
}
}
finally this is my menu_items.dart:
final _opacityChangeProvider =
ChangeNotifierProvider<OpacityChangeNotifier>((ref) {
return OpacityChangeNotifier();
});
class UtilityItems extends ConsumerWidget {
const UtilityItems({Key key}) : super(key: key);
#override
Widget build(BuildContext context, ScopedReader watch) {
final opacityNotifier = watch(_opacityChangeProvider);
return SingleChildScrollView(
child: Container(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
onPressed: () {
print(opacityNotifier.opacity);
},
icon: Icon(Icons.dock_rounded),
)
],
),
OpacityPicker(),
],
),
),
);
}
}
Everything is working fine inside OpacityPicker.dart. When I'm pressing the opacity button the selected opacity is getting printed. But when I'm pressing the Icons.dock_rounded in menu_items.dart shouldn't I get the updated value?
It's showing the default value for opacity which is 1.0
I've looked it up and all I got was some issues related to changeNotifier not working back in September 2020.
What am I missing here?
N.B: I've imported all the files correctly. And I want the value of opacity to change to the user selected one on pressed. So I need my menu_item.dart widget to know that.
Building off #puelo comment, you shouldn't be redefining your ChangeNotifierProvider. The way you have it is two isolated providers with two separate ChangeNotifiers that have no knowledge of eachother.
I would recommend making provider a static member of your ChangeNotifier like so:
class OpacityChangeNotifier extends ChangeNotifier {
OpacityChangeNotifier([this.opacity = 1.0]);
static final provider = ChangeNotifierProvider<OpacityChangeNotifier>((ref) {
return OpacityChangeNotifier();
});
double opacity;
void changeOpacity(double providedOpacity) {
opacity = providedOpacity;
notifyListeners();
}
void printOpacity() {
print(opacity);
}
}
To access:
context.read(OpacityChangeNotifier.provider);
This helps to keep your imports clean as well as avoiding redundant naming and reduces the likelihood someone else working on the project would end up creating another provider for that notifier.
Use that provider instead of defining it twice and that should solve your primary issue.
You should also always use context.read inside function handlers like onPressed. In your menu_items.dart you should refactor as follows:
class UtilityItems extends ConsumerWidget {
const UtilityItems({Key key}) : super(key: key);
#override
Widget build(BuildContext context, ScopedReader watch) {
return SingleChildScrollView(
child: Container(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
onPressed: () {
final opacity = context.read(OpacityChangeNotifier.provider).opacity;
print(opacity);
},
icon: Icon(Icons.dock_rounded),
)
],
),
OpacityPicker(),
],
),
),
);
}
}

What is the right way using the build context outside the build function

Lets say I'm enter my named route page and get the arguments in the build function.
Now my widget is state full widget and i want to make api call with the arguments in order to set the state of my widget.
I'm using future Builder to load the api when the page is loading, so i have to create Future and equal him to the api func right?
but i cant do it inside the build it will call it unlimited times, so i send it as props to an other widget but really i should create widget just in order to send my context values?
class GameScreen extends StatefulWidget {
GameScreen({Key key}) : super(key: key);
#override
_GameScreenState createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
Fixture fixture;
Future setFIxture(externalId) async {
final response =
await FixturesService().getLiveFixtureByExternalId(externalId);
setState(() {
fixture = response;
});
}
#override
Widget build(BuildContext context) {
final GameScreenArguments args = ModalRoute.of(context).settings.arguments;
Future initScreen;
initScreen = setFIxture(args.externald);
return RoutePage(
child: Loader(
future: initScreen,
succeed: Container(
height: 223,
width: double.infinity,
child: Column(
children: [
Column(
children: [
Text(""),
Row(
children: [
Text(""),
Text(""),
],
),
Column(
children: [
// TeamImage(),
Column(
children: [
Text(""),
Text(""),
],
),
// TeamImage(),
],
)
],
),
Column(
children: [
Text(""),
],
)
],
),
),
),
);
}
}
my loader widget:
class Loader extends StatefulWidget {
final Future future;
final Widget succeed;
Loader({Key key, this.future, this.succeed}) : super(key: key);
#override
_LoaderState createState() => _LoaderState();
}
class _LoaderState extends State<Loader> {
Future _getTaskAsync;
final spinkit = SpinKitFadingCircle(
color: Colors.black,
size: 40,
);
Future fetchData() async {
try {
await widget.future;
return true;
} catch (e) {
return e;
}
}
#override
void initState() {
_getTaskAsync = fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getTaskAsync,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return widget.succeed;
} else if (snapshot.hasError) {
return Text("error");
} else {
return spinkit;
}
},
);
}
}
Yes you have to create another widget. But maybe use a dependency injection solution to make it simpler to inject objects into the widget tree (Riverpod is good i heard). Store the GameScreen args inside a shared state above in the widget tree.

Flutter, calling a function inside the button

I am new to flutter and I make some practices. I have a StatelessWidget called doChange and makeChange and one StatefulWidget. This class which is statefulwidget I made child of the home page of the app also. But, I think that it is unnecessary to define here. My purpose in this case is that, I want to change the state of the button make open,make closed and at the same time the text open and close will also change. I think that class changeText has not problem but in makeChange class I have some trouble with creating constructor and function to call into onPress. The states do not change. How can i solve this or is that any way to do this without function ?
class changeText extends StatelessWidget{
final doChange;
changeText({#required this.doChange});
#override
Widget build(BuildContext context){
return Container(
//some codes
//some codes
child: doChange ? Text("open") : Text("close"),
);
}
}
class makeChange extends StatelessWidget{
final changeState;
makeChange({#required this.changeState}); // I want to add constructor here lets say onPressButton
whenPressed(){ // I want to create a function with the that constructor that I have add.
}
#override
Widget build(BuildContext context){
return Container(
//some codes
//
child: Column(
children: [
MaterialButton(
//some codes
//
onPressed: () {} // Here I want to call a function when press the button.
child: changeState ? Text("make open") : Text("make close"),
),
],
),
);
}
}
class Mainarea extends StatefulWidget{
#override
_MainareaState createState() => _mainAreaState();
}
class _MainareaState extends State<Mainarea> {
bool isChange= false;
#override
Widget build(BuildContext context){
return Container(
//some codes
//
child: Column(
children: <Widget>[
changeText(doChange: !this.isChange),
makeChange(changeState: !this.isChange),
],
),
);
}
}
I just added a final Function(bool) callback as a parameter, which can be called inside from the stateless widget, and returns to the calling function. From there you can call setState
class changeText extends StatelessWidget {
final bool doChange;
changeText({#required this.doChange});
#override
Widget build(BuildContext context) {
return Container(
//some codes
//some codes
child: doChange ? Text("open") : Text("close"),
);
}
}
class makeChange extends StatelessWidget {
final bool changeState;
final Function(bool) callback;
makeChange(
{#required
this.changeState,
#required
this.callback}); // You can rename this to onPressed or whatever
#override
Widget build(BuildContext context) {
return Container(
//some codes
//
child: Column(
children: [
MaterialButton(
//some codes
//
onPressed: () => callback( changeState),
child: changeState ? Text("make close") : Text("make open"), //I had to swap these around to make the text correct
),
],
),
);
}
}
class Mainarea extends StatefulWidget {
#override
_MainareaState createState() => _MainareaState();
}
class _MainareaState extends State<Mainarea> {
bool isChange = false;
#override
Widget build(BuildContext context) {
return Container(
//some codes
//
child: Column(
children: <Widget>[
changeText(doChange: !this.isChange),
makeChange(
changeState: !this.isChange,
callback: (bool val) {
setState(() => isChange = val); //this is your function that returns and resetst the value in the parent widget
},
),
],
),
);
}
}

dropdown menu with listview builder in flutter

I have a listview.builder in flutter and every item of the list has a dropdown now whenever I select one dropdown value of every dropdown changes. how can I fix this problem in flutter?
Ok, after spending a couple of hours on this and not finding a satisfactory answer (but a lot of hints) I worked it out.
I made a new StatefulWidget class that wraps the DropdownButton. It is instantiated with the List of items for the dropdown.
listview_dropdownbutton.dart
import 'package:flutter/material.dart';
class ListviewDropdownButton extends StatefulWidget {
final List<dynamic> sizes;
const ListviewDropdownButton({
Key? key,
required this.sizes,
}) : super(key: key);
#override
State<ListviewDropdownButton> createState() => _ListviewDropdownButton();
}
class _ListviewDropdownButton extends State<ListviewDropdownButton> {
List<dynamic>? _sizes;
String _currentSize = '';
#override
Widget build(BuildContext context) {
_sizes = _sizes ?? widget.sizes;
_currentSize = _currentSize != '' ? _currentSize : widget.sizes[0];
return DropdownButton<dynamic>(
value: _currentSize,
style: const TextStyle(
color: Colors.green,
),
items: _sizes!.map<DropdownMenuItem<dynamic>>((dynamic size) {
return DropdownMenuItem(
value: size,
child: Text(size),
);
}).toList(),
onChanged: (dynamic size) {
if (_currentSize != size) {
setState(() {
_currentSize = size!;
});
}
},
);
}
}
In the parent widget, just include the class and use it where you'd put the DropdownButton.
Here's a working example.
main.dart
import 'package:flutter/material.dart';
import 'listview_dropdownbutton.dart';
void main() => runApp(const DropdownButtonApp());
class DropdownButtonApp extends StatelessWidget {
const DropdownButtonApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('DropdownButton In ListView')),
body: Center(
child: DropdownButtonExample(),
),
),
);
}
}
class DropdownButtonExample extends StatelessWidget {
DropdownButtonExample({super.key});
final List<String> _items = <String>['Shirt', 'T-Shirt', 'Pants', 'Blouse', 'Coat'];
final List<String> _sizes = <String>['Small', 'Medium', 'Large', 'X-Large'];
String _currentSize = 'Small';
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _items.length,
itemBuilder: (
BuildContext context,
int index,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_items[index]),
Row(
children: [
ListviewDropdownButton(
sizes: _sizes,
),
DropdownButton<String>(
value: _currentSize,
style: const TextStyle(
color: Colors.red,
),
items: _sizes.map<DropdownMenuItem<String>>((String size) {
return DropdownMenuItem(
value: size,
child: Text(size),
);
}).toList(),
onChanged: (String? size) {
if (_currentSize != size) {
// setState(() {
_currentSize = size!;
// });
}
},
),
],
),
const Divider(
thickness: 2,
height: 2,
),
],
);
},
);
}
}
To illustrate it works, I put both the ListviewDropdownButton and a regular DropdownButton in the ListView.
I added String _currentSize = 'Small'; and the onChanged method to show the regular DropdownButton does not work. It never changes from "Small", which was my original problem.

How to make widget out of a GestureDetector with a Container child?

I want to make a reusable button with a container in GestureDetector which will execute some function if I tap it and its color will become dark if I hold it. Any help, hint, tip would be very much appreciated.
I tried writing the GestureDetector in the custom widget file but it gives me errors.
When i try to extract widget on the GestureDetector it gives an Reference to an enclosing class method cannot be extracted error.
(the main page)
import 'package:flutter/material.dart';
import 'ReusableTwoLineList.dart';
import 'Text_Content.dart';
const mainTextColour = Color(0xFF212121);
const secondaryTextColour = Color(0xFF757575);
const inactiveBackgroundCardColor = Color(0xFFFFFFFF);
const activeBackgroundCardColor = Color(0xFFE5E5E5);
enum CardState {
active,
inactive,
}
class SettingsPage extends StatefulWidget {
#override
_SettingsPageState createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
CardState currentCardState = CardState.inactive;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Settings'),
),
body: ListView(
children: <Widget>[
GestureDetector(
onTapDown: (TapDownDetails details) {
setState(() {
currentCardState = CardState.active;
});
},
onTapCancel: () {
setState(() {
currentCardState = CardState.inactive;
});
},
onTap: () {
setState(() {
currentCardState = CardState.inactive;
//some random function
});
},
child: ReusableTwoLineList(
mainTextColor: mainTextColour,
secondaryTextColor: secondaryTextColour,
backgroundCardColor: currentCardState == CardState.active
? activeBackgroundCardColor
: inactiveBackgroundCardColor,
cardChild: TextContent(
mainLabel: 'First Day',
secondaryLabel: 'This is the first day of the week',
),
),
),
ReusableTwoLineList(
mainTextColor: mainTextColour,
secondaryTextColor: secondaryTextColour,
cardChild: TextContent(
mainLabel: '2nd day',
secondaryLabel: 'This is the end day',
),
),
ReusableTwoLineList(
mainTextColor: mainTextColour,
secondaryTextColor: secondaryTextColour,
),
],
),
);
}
}
ReusableTwoLineList.dart (the custom widget i am trying to make)
class ReusableTwoLineList extends StatelessWidget {
ReusableTwoLineList({
#required this.mainTextColor,
#required this.secondaryTextColor,
this.backgroundCardColor,
this.cardChild,
this.onPressed,
});
final Color mainTextColor, secondaryTextColor, backgroundCardColor;
final Widget cardChild;
final Function onPressed;
#override
Widget build(BuildContext context) {
return Container(
color: backgroundCardColor,
padding: EdgeInsets.symmetric(horizontal: 16),
height: 72,
width: double.infinity,
child: cardChild,
);
}
}
This is what i want but in a custom widget so i can use it over and over.
Normal-https://i.imgur.com/lVUkMFK.png
On Pressed-https://i.imgur.com/szuD4ZN.png
You can use extract method instead of extract widget. Flutter will add everything as it is, and instead of a class you will get a reusable function.