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(),
],
),
),
);
}
}
Related
Actually, I have a parent widget, and It has some of the child widgets in its Column.
like this
Container(
width: double.infinity,
color: Colors.white,
padding: EdgeInsets.fromLTRB(20.0, 50.0, 20.0, 0.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Title(),
SizedBox(height: 80.0,),
confirmLoginType(),
SizedBox(height: 10.0),
LoginButton(),
PolicyTips(
key: IndexGlobalKey.policyTipsKey,
updateState: receiveMessageUpdateState
),
Bottom()
],
),
),
in the PolicyTips, I pass a key to it, and I want to get the key in the LoginButton , but It has always been null when I get currentState.
The code is below:LoginButton
class LoginButton extends StatefulWidget {
LoginButton({Key key}) : super(key: key);
#override
_LoginButtonState createState() => _LoginButtonState();
}
class _LoginButtonState extends State<LoginButton> {
#override
Widget build(BuildContext context) {
policyTipsKey = IndexGlobalKey.policyTipsKey.currentState;
return Container(
child: Text()
)
}
}
what can I do? help me please, thanks.
This is IndexGlobalKey code.
class IndexGlobalKey {
static final GlobalKey<_PolicyTipsState> policyTipsKey = GlobalKey<_PolicyTipsState>();
static GlobalKey<_FormState> phoneLoginKey = GlobalKey<_FormState>();
static GlobalKey<_FormForIdCardLoginState> idCardLoginKey = GlobalKey<_FormForIdCardLoginState>();
}
Build method of _LoginButtonState runs before PolicyTips renders and before IndexGlobalKey.policyTipsKey is actually set. The reason is LoginButton goes before PolicyTips in column. Thats why you get null when you call IndexGlobalKey.policyTipsKey.currentState from build of _LoginButtonState.
To solve this you need to call IndexGlobalKey.policyTipsKey.state right where you use it. For example, when you need to get policy tips state on button tap just use it inside onPressed callback:
class _LoginButtonState extends State<LoginButton> {
#override
Widget build(BuildContext context) {
// An example of your button
return TextButton(
onPressed: () {
final policyTipsState = IndexGlobalKey.policyTipsKey.currentState;
// Here you can use policyTipsState
},
child: Text('button'),
);
}
}
I have a notifier.dart file where I have declared some ChangeNotifiers. One of which is OpacityChangeNotifier.
OpacityChangeNotifier Class:
class OpacityChangeNotifier extends ChangeNotifier {
double _opacity = 1.0;
double get opacity => _opacity;
void changeOpacity(double providedOpacity) {
_opacity = providedOpacity;
notifyListeners();
}
void printOpacity() {
print(_opacity);
}
}
This is for my coloring app where I want the user to start with an opacity of 1.0. Then he/she can change it.
Here's the opacity_picker widget
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) {
final opacityPicker = watch(_opacityProvider);
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
onPressed: () {
opacityPicker.changeOpacity(0.1);
},
icon: Icon(Icons.opacity, size: 20),
),
IconButton(
onPressed: () {
opacityPicker.changeOpacity(0.5);
},
icon: Icon(Icons.opacity, size: 20),
),
IconButton(
onPressed: () {
opacityPicker.changeOpacity(1.0);
},
icon: Icon(Icons.opacity, size: 20),
),
],
),
);
}
}
Now I want to use this opacity_picker inside another widget called menu_items. I've added a simple Icon button to test if the values(0.1,0.5,1.0) for opacity were getting updated or not.
IconButton(
onPressed: () {
opacity.printOpacity();
},
icon: Icon(Icons.dock_rounded),
)
But it seems the value is remaining the same which I provided as default: 1.0. Any solution on how to update the value I provided or any other way how I can change the opacity?
I'm not familiar with how you're instantiating ChangeNotifierProvider just above your OpacityPicker class. So I'm not sure if that's correct or not.
As Provider is built with InheritedWidget, any part of your app you wish to be "reactive" to changes of Provider state, needs to be a child underneath your ChangeNotifierProvider.
One way to ensure this is to wrap MyApp with your ChangeNotifierProvider. Therefore, your entire app is within the Provider's InheritedWidget scope. Remi (author of Provider) shows this in his example code:
void main() {
runApp(
/// Providers are above [MyApp] instead of inside it, so that tests
/// can use [MyApp] while mocking the providers
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
],
child: const MyApp(),
),
);
}
He's using MultiProvider but you can see that MyApp() is the child of Provider.
Are you doing something similar in your app?
I have a function on the Grandparent. I pass it down to the parent, and the parent passes it down to the child. The function is called on the child. The expected behaviour is that the function on the grandparent is executed, but it is not. Nothing happens. For testing purposes I also included a button on the parent, that works correctly as expected.
So, what should happen is: when the listTile on the child is tapped, 'print from parent' should appear on the console.
Grandparent (snippet):
StateLoaded(
vm: vm,
procesStatus: _procesStatus,
onTapChange: () => print('print from parent'),
),
Parent (snippet):
class StateLoaded extends StatelessWidget {
const StateLoaded({
#required this.vm,
#required ProcesStatus procesStatus,
#required this.onTapChange,
}) : _procesStatus = procesStatus;
final AuthViewModel vm;
final ProcesStatus _procesStatus;
final Function() onTapChange;
#override
Widget build(BuildContext context) {
return Visibility(
visible: vm.failureVm.failure == null &&
vm.isAuth &&
_procesStatus == ProcesStatus.loaded,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 8),
Avatar(),
Divider(),
UserName(vm: vm, onTapChange: onTapChange),
Child:
class UserName extends StatelessWidget {
const UserName({
#required this.vm,
#required this.onTapChange,
});
final AuthViewModel vm;
final Function() onTapChange;
#override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(
SimpleLineIcons.pencil,
color: Theme.of(context).primaryColor,
size: 18,
),
title: Text(
vm.userName ?? '',
style: linkTextStyle,
),
onTap: onTapChange(),
);
}
}
The onTap, on ListTile child, is wrong.
There's 2 ways to correct it :
onTap: onTapChange,
or
onTap: () => onTapChange(),
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");
}),
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.