Flutter State using ChangeNotifier doesn't maintain State - flutter

TLDR, I'm building a Stock Screener and when I access a table from one widget it renders proper state, but when called from within the class the state defaults to initial values (I think).
Overall design concept is user configures their stock screener and hits "Screen" button at bottom of page.
Here's a View Example. This is the Market Cap Selector Widget that uses Consumer to retrieve state from the class ScreenerController extends ChangeNotifier
class CapSelectorWidget extends StatelessWidget {
Widget capButton(capSelection) {
return Consumer<ScreenerController>(builder: (context, capData, child) {
return GestureDetector(
child: Container(
padding: EdgeInsets.all(12.0),
margin: EdgeInsets.all(6.0),
decoration: BoxDecoration(
color: capData.getColor(capSelection),
borderRadius: BorderRadius.circular(8.0),
),
child: Text(
capSelection,
textAlign: TextAlign.center,
),
),
onTap: () {
capData.toggleCapSelection(capSelection);
},
);
});
}
Both methods getColor and toggleCapSelection ultimately reference and/or update the below _capColorList. Here is the code for the _capColorList and toggleCapSelection
class ScreenerController extends ChangeNotifier {
//////////////////////
// Market Cap Widget
//////////////////////
Map _capColorList = {
'Micro Cap': {
'isSelected': true,
'selectedColor': Colors.teal[300],
'unSelectedColor': Colors.white38,
},
'Small Cap': {
'isSelected': true,
'selectedColor': Colors.teal[400],
'unSelectedColor': Colors.white38,
},
'Mid Cap': {
'isSelected': true,
'selectedColor': Colors.teal[500],
'unSelectedColor': Colors.white38,
},
'Large Cap': {
'isSelected': true,
'selectedColor': Colors.teal[600],
'unSelectedColor': Colors.white38,
},
};
void toggleCapSelection(String capType) {
// Flip bool selected value
_capColorList[capType]['isSelected'] =
!_capColorList[capType]['isSelected'];
notifyListeners();
print(capType + ' ' + _capColorList[capType]['isSelected'].toString());
};
etc...
So far so good. When user hit's a Cap Bucket, it'll toggle state and the color will update.
Now when user hits "Screen" (ie Submit) at bottom of page, this code is implemented.
child: FlatButton(
child: Text('Screen', textAlign: TextAlign.center),
onPressed: () {
ScreenerController().screenStocks();
//Todo: Figure out why Provider.of doesn't work on Screen Button
//Provider.of<ScreenerController>(context, listen: false)
// .screenStocks();
},
),
I implement the following code to create a list of the Market Cap Buckets into a List.
My hypothesis is that because Provider.of isn't working for the FlatButton (it's in the same ChangeNotifierProvider tree), that I'm inadvertently creating a new instance.
Map screenerQueryBuilder() {
//If Cap Button is selected, add it to selectedCaps
List<String> selectedCaps = [];
for (String cap in _capColorList.keys) {
print(cap + _capColorList[cap]['isSelected'].toString());
if (_capColorList[cap]['isSelected']) {
selectedCaps.add(cap);
}
}
print(selectedCaps);
The problem is, even if let's say the Buttons "Mid Cap" and "Micro Cap" are not selected when the user hits Screen, when I iterate over _capColorList.keys the values are always true, and therefore they are always added to the selectedCaps List.
Might anyone know why this is?
Thank you in advance!

Related

Pass On Get Results from one class to another - Flutter

The data unLockCard is properly created in the main class where the button is placed.
When I moved the button to a dialog in a different class - the unLockCard is lost. I receive the error message
What is the best way to pass on unLockCard[number] = tarots[0]; into a different widget or class.
Homepage
List<bool> flips = [false, false, false, false];
List tarots = [];
List unLockCard = [];
Widget _buildTarotCard(key, number, title) {
return Column(
children: [
FlipCard(
key: key,
flipOnTouch: true,
front: GestureDetector(
onTap: () {
tarots.shuffle();
key.currentState.toggleCard();
setState(() {
flips[number] = true;
});
unLockCard[number] = tarots[0];
tarots.removeAt(0);
},
Dialog
Widget _showDialog(BuildContext context) {
Future.delayed(Duration.zero, () => showAlert(context));
return Container(
color: Color(0xFF2C3D50),
);
}
void showAlert(BuildContext context) {
List unLockCard = [];
Dialogs.materialDialog(
color: colorTitle,
msg: 'Congratulations, you won 500 points',
msgStyle: TextStyle(color: Colors.white),
title: 'Congratulations',
titleStyle: TextStyle(color: Colors.white),
lottieBuilder: Lottie.asset('assets/lottie/spirituality.json',
fit: BoxFit.contain,
),
dialogWidth: kIsWeb ? 0.3 : null,
context: context,
actions: [
NeumorphicButton(
onPressed: () => Get.toNamed(Routes.DETAILS,
arguments: unLockCard.sublist(0, 4)),
margin: EdgeInsets.symmetric(horizontal: 8.0),
The code is a bit cluttered. You are building a new List unLockCard = []; in the showAlert() method even though you have one already in the HomePage. I suggest you create a CardController class that deals with everything card related and use it in all the views you need. One very elegant way I found is by using the GetX library (https://github.com/jonataslaw/getx). You can declutter your code using a GetView and a GetController (https://github.com/jonataslaw/getx#getview). However, if you don't want to add a new library to your project, you can achieve the same results by having a single point that deals with card actions (i.e. holds a single instance of the unlockCard list and updates it accordingly).

Flutter DropDownMenu Button update an existing record

I have been experimenting with my flutter drop down button.
Context of what I am doing.
I have an app that will create a job and give it to an available staff member. I have stored all my staff members in a list for the menu button. I will put the code below to show the creation of the job ticket drop down button. selectedTech is at the top of the program so that's not the issue
String selectedTech = "";
Container(
// margin: EdgeInsets.only(right: 20),
width: MediaQuery.of(context).size.width / 2.5,
child: DropdownButton(
hint: Text(
selectedTech,
style: TextStyle(color: Colors.blue),
),
isExpanded: true,
iconSize: 30.0,
style: TextStyle(color: Colors.blue),
items: listStaffUsers.map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(
() {
selectedTech = val.toString();
},
);
},
),
),
The above code works perfect.
However when I want to update the job ticket to change the available staff member I want to set the initial value of the drop down menu to the staff member assigned to the job, because it isn't always guaranteed that they change the staff member allocated to the job. When I set the selected value to my initial value I am locked with that value and cannot change it.
Here is the code I am using to update the staff member.
String selectedTech = "";
int the build method I add
selectedTech = widget.staff;
Container(
// margin: EdgeInsets.only(right: 20),
width: MediaQuery.of(context).size.width / 2.5,
child: DropdownButton(
hint: Text(
selectedTech,
style: TextStyle(color: Colors.blue),
),
isExpanded: true,
iconSize: 30.0,
style: TextStyle(color: Colors.blue),
items: listStaffUsers.map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(
() {
selectedTech = val.toString();
},
);
},
),
),
Any Guidance or examples will be greatly appreciated.
As I understand under the Widget build method you set
selectedTech = widget.staff and then return the widget like this:
Widget build(BuildContext context) {
selectedTech = widget.staff;
return Container( ...
This will systematically lock your selectedTech to widget.staff whenever the build method is called (when you call setState). I mean whenever you change the value of the dropdown, the value will not be set the actual value on the dropdown menu. Because you call setState, setState builds the widget from scratch and selectedTech = widget.staff is called in these steps.
Instead of in build method you should initialize it first, then continue to build method.
class _StaffHomeState extends State<StaffHome> {
String? selectedTech;
// Write a function to initialize the value of selectedTech
void initializeSelectedTech () {
selectedTech = widget.staff;
}
// Call this function in initState to initialize the value
#override
void initState() {
initializeSelectedTech();
super.initState();
}
// Then Widget build method
Widget build(BuildContext context) {
return Container( .....
By this way, you initialize first the value before build method and whenever state changes, the data will be persisted.
I hope it is helpful.

GestureDetector and exclusive activation while calling onTap in a widget list

I'm trying to create a simple vertical scrolling calendar.
Problem is that I can't manage to find a way to reset back to previous state in case I tap on a new container.
Here's the code:
class CalendarBox extends StatelessWidget {
BoxProprieties boxProprieties = BoxProprieties();
Map item;
CalendarBox({this.item});
bool selected = false;
#override
Widget build(BuildContext context) {
return Consumer<Producer>(
builder: (context, producer, child) => GestureDetector(
onTap: () {
print(item['dateTime']);
selected = producer.selectedState(selected);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 100),
color: selected == true ? Colors.blue : Colors.grey[200],
height: 80,
width: 50,
margin: EdgeInsets.only(top: 5),
child: Column(
children: [
Text(
'${item['dayNum']}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: boxProprieties.dayColor(item['dateTime'])),
),
],
),
),
),
);
}
}
Here's the situation:
One way to achieve it is, create a model for boxes and keep a value current selected block, in your model you will have the index assigned to that block,
int currentSelected =1; //initial value
class Block{
int id;
..
.. // any other stuff
}
now in your code, the check modifies to
block.id == currentSelected ? Colors.blue : Colors.grey[200],
your on tap modifies to
onTap: () {
setState(){
currentSelected = block.id
};
},
If you want to prevent the rebuild of the whole thing every time you can use valueNotifire for current selected block. Hope this gives you an idea.

Saving data between widgets in Flutter

hi i am making an app for restaurant but i need some help i have a listtilel and i would like to save those values in memory or firebase anywhere i would like to get which topics selected how should i do that
basicly i meant i want to get choosen datas from user and i would like to add it orders page if you have any suggestions please let me know thanks
import 'package:flutter/material.dart';
import 'package:resat/BurgerListView/data/toppics_model.dart';
import 'data/toppics.dart';
class decor extends StatefulWidget {
#override
MyAppState createState() {
return new MyAppState();
}
}
class MyAppState extends State<decor> {
List ketchup = [
'No',
'light',
'regular',
'extra',
];
List calori = ['0 Cal', '1 Cal', '2 Cal', '3 Cal'];
var i = 2;
final List<topics> _topics = categories;
#override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
margin: EdgeInsets.all(6), //space between other listtiles
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.all(Radius.circular(8))),
child: ListTile(
title: Row(children: [
Expanded(
child:
Text("Ketchup\n" + calori[i], textAlign: TextAlign.center,),),
Expanded(
child: (InkWell(
child: Text("-", textAlign: TextAlign.center),
onTap: () {
setState(() {
if (i <= 0){
i = i;
}else
i--;
print(i);
});
},
)),
),
Expanded(child: Text(ketchup[i], textAlign: TextAlign.center)),
Expanded(
child: (InkWell(
child: Text("+", textAlign: TextAlign.center),
onTap: () {
setState(() {
if (i > 2) {
i = i;
} else
i++;
print(i);
});
},
)),
),
]),
leading: Icon(Icons.pie_chart))
);
}
}
I can suggest you two ways to save your data across the different users.
Unsurprisingly it's an online database either via firebase or vis your own Web service APIs hosted on your server.
I don't suggest you offline database(SQLite) anyway because it will be limited to a single user.
The data you save and fetch will be limited to a single user.
So, try learning the flutter concepts from,
Firebase database
Web api implementation
Offline datbase

AnimatedSwitcher does not animate

I'm trying to make a news section in my app. In this page that's gonna display the news, i want to be able to click anywhere on the page and get the news that is next in my list. So far no problem with that, but i wanted it to have a nice animation so i tried implementing AnimatedSwitcher, but i can't figure out why there is no animation showing.
I tried changing the hierarchy of my code. Putting the gesture detector inside the animated switcher and the other way around. Letting the main container outside or inside of it too. I tried an animation builder that would scale it just in case it wasnt obvious enough but nothing. Tried changing the duration too but that wasn't it.
class ShowNews extends StatefulWidget {
#override
_ShowNewsState createState() => _ShowNewsState();
}
class _ShowNewsState extends State<ShowNews> {
List<News> _news = [
News(title: 'OYÉ OYÉ', desc: 'bla bla bla bla bla'),
News(title: 'another one', desc: 'plus de bout d\'histoire'),
News(title: 'boum', desc: 'attention à l\'accident'),
News(title: 'Lorem ipsum', desc: 'Lorem ipsum in doloris'),
];
int _currentIndex = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
if (_currentIndex < _news.length - 1) {
_currentIndex++;
} else {
_currentIndex = 0;
}
});
},
child: Container(
height: 160,
padding: EdgeInsets.all(20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0),
),
),
child: AnimatedSwitcher(
duration: Duration(seconds: 5),
child: ColumnArticle(_news, _currentIndex),
),
),
);
}
}
Everything is working fine but the animation.
Edit: I tried adding a key to make it different but still no animation.
class ColumnArticle extends StatelessWidget {
final List<News> _news;
final int _currentIndex;
ColumnArticle(this._news, this._currentIndex);
#override
Widget build(BuildContext context) {
return Column(
key: ValueKey<int>(_currentIndex),
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
_news[_currentIndex].title,
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 20.0,
),
),
SizedBox(
height: 10.0,
),
Text(
_news[_currentIndex].desc,
style: TextStyle(
fontSize: 14.0,
),
),
],
);
}
}
That happens because the AnimatedSwitcher will add an animation anytime it is rebuilt with a different child reference. However, in your widget lifecycle, you are always using a ColumnArticle as a child, thus, not actually swapping any widget type, that's where the ValueKey comes in play.
You can use the index as the reference for the key, but make sure it actually changes, otherwise it won't work and you also need to pass it to your ColumnArticle base widget (super).
So, your ColumnArticle should look like this:
class ColumnArticle extends StatelessWidget {
final List<News> _news;
final int _currentIndex;
ColumnArticle(this._news, this._currentIndex) : super(key: ValueKey<int>(_currentIndex));
...
}
Passing the same type of widget with different attributes will not trigger an animation since they are the same widgets for the framework. It's also mentioned in the description.
If the "new" child is the same widget type and key as the "old" child,
but with different parameters, then AnimatedSwitcher will not do a
transition between them, since as far as the framework is concerned,
they are the same widget and the existing widget can be updated with
the new parameters. To force the transition to occur, set a Key on
each child widget that you wish to be considered unique (typically a
ValueKey on the widget data that distinguishes this child from the
others).
Here is the code from AnimatedSwitcher that checks whether to animate or not:
if (hasNewChild != hasOldChild ||
hasNewChild && !Widget.canUpdate(widget.child, _currentEntry.widgetChild)) {
// Child has changed, fade current entry out and add new entry.
_childNumber += 1;
_addEntryForNewChild(animate: true);
}
This is the static canUpdate method from the framework:
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
To solve this you can set individual keys to your News widgets based on their distinct attributes (eg. text, count, value). ValueKey<T> is just for that.
Column(
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
child: Text(
'$_count',
// This key causes the AnimatedSwitcher to interpret this as a "new"
// child each time the count changes, so that it will begin its animation
// when the count changes.
key: ValueKey<int>(_count),
),
),
RaisedButton(
child: const Text('Increment'),
onPressed: () {
setState(() {
_count += 1;
});
},
),
])