Make a variable in build build context run once - flutter

I have a stateful widget which uses a provider to get questions. The question type looks like this:
{
"question": "What...",
"answer: 1829,
"buffer": [1928, 1874, 1825]
}
I have a shuffle method which shuffles the items passed to it. So in my widget, I have this code:
#override
Widget build(BuildContext context) {
var state = context.watch<Services>();
Tion tion;
List<int> shuffled;
int selectedNumber;
if (state.questions != null) {
tion = state.questions[0];
shuffled = shuffle([tion.answer, ...tion.buffer]); // here's my issue
}
return ...
}
Deeper in the widget tree, I render these numbers:
GridView.count(
crossAxisCount: 2,
children: List.generate(4, (index) =>
Center(
child: GestureDetector(
onTap: () => setState(() {
selectedNumber = shuffled[index]; // setstate
}),
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: selectedNumber == shuffled[index] ? Color(0xff6C63FF) : Colors.grey[200],
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
child: Center(
child: Text(
'${shuffled[index]}',
style: GoogleFonts.lato(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey[800]
)
)
),
),
),
)
),
)
The problem is when I call setState(), the widget rebuilds, and the order of the numbers along with it. Is there any way to prevent this? I tries with initState but it's called outside the scope of context.

If you need a BuildContext for your function you can use didChangeDependencies(): It is called when a dependency of this State object changes and also immediately after initState, it is safe to use BuildContext here. Subclasses rarely override this method because the framework always calls build after a dependency changes. Some subclasses do override this method because they need to do some expensive work (e.g., network fetches) when their dependencies change, and that work would be too expensive to do for every build.
#override
void didChangeDependencies() {
// Your function.
super.didChangeDependencies();
}
Getx package also has variety of ways to insert a Middleware function. You can check them on package page.

Related

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.

Need help chasing down: Exception while building using Provider in Flutter

I'm trying to learn Flutter and become more acquainted with passing data around. So i have this very simple app here that is a sort of complicated version of this: Provider version flutter starter demo
Like I said I'm trying to get acquainted, and I'm a relatively green dev. I'm creating this demo to learn StateManagement as well as Persistence.
My goal with this post, is to get help to fix this issue and also know what I'm doing wrong.
So far I have tried moving a few things around and some typical searches but can't seem to figure out specifically what I'm doing wrong here compared with others who are getting the same error.
The app works fine, exactly as expected, no crash and as far as my green grass eyes can tell my code is structured exactly like the Flutter example (with respect to the Provider StateManagement). However I'm getting this error in the console:
======== Exception caught by foundation library ====================================================
The following assertion was thrown while dispatching notifications for Keeper:
setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<Keeper> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: _InheritedProviderScope<Keeper>
value: Instance of 'Keeper'
listening to value
The widget which was currently being built when the offending call was made was: Consumer<Keeper>
dirty
dependencies: [_InheritedProviderScope<Keeper>]
Page 1
class ScreenOne extends StatelessWidget {
static const String id = 'screen_one';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Page One',
),
),
backgroundColor: Colors.grey.shade200,
body: Container(
child: SafeArea(
child: Padding(
padding: EdgeInsets.all(20.0),
child: Center(
child: Column(
children: [
CustomTextBoxes(title: 'Screen One'),
Consumer<Keeper>(
builder: (_, keeper, child) => Text(
'${keeper.pageOneValue}',
style: TextStyle(color: Colors.grey, fontSize: 20.0),
),
),
CustomTextBoxes(title: 'Screen Two'),
Consumer<Keeper>(
builder: (_, keeper, child) => Text(
'${keeper.pageTwoValue}',
style: TextStyle(color: Colors.grey, fontSize: 20.0),
),
),
CustomTextBoxes(title: 'Total'),
Consumer<Keeper>(
builder: (_, keeper, child) => Text(
'${keeper.addCounters()}',
style: TextStyle(color: Colors.grey, fontSize: 20.0),
),
),
SizedBox(
height: 20.0,
),
CustomButton(
text: 'Screen 2',
function: () {
Navigator.pushNamed(context, ScreenTwo.id);
},
),
],
),
),
),
),
),
floatingActionButton: CustomFloatingButton(
function: () {
var counter = context.read<Keeper>();
counter.incrementCounterOne();
},
),
);
}
}
"Keeper"
class Keeper with ChangeNotifier {
int pageOneValue = 0;
int pageTwoValue = 0;
int totalValue = 0;
void incrementCounterOne() {
pageOneValue += 1;
notifyListeners();
}
void incrementCounterTwo() {
pageTwoValue += 1;
notifyListeners();
}
int addCounters() {
totalValue = pageOneValue + pageTwoValue;
notifyListeners();
return totalValue;
}
}
Main
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Keeper(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue),
initialRoute: ScreenOne.id,
routes: {
ScreenOne.id: (context) => ScreenOne(),
ScreenTwo.id: (context) => ScreenTwo()
},
);
}
}
Your problem is in calling addCounters() inside your build method. addCounters() calls notifyListeners() that triggers a setState().
This cannot be perform within your build function. It can only be performed later at the request of a User action. For example, inside the onPressed of a button as you do for the incrementCounterOne().
Instead of computing and storing the total value of your two counters, you could use a getter:
Keeper:
class Keeper with ChangeNotifier {
int pageOneValue = 0;
int pageTwoValue = 0;
int get totalValue => pageOneValue + pageTwoValue;
void incrementCounterOne() {
pageOneValue += 1;
notifyListeners();
}
void incrementCounterTwo() {
pageTwoValue += 1;
notifyListeners();
}
}
ScreenOne:
Consumer<Keeper>(
builder: (_, keeper, child) => Text(
'${keeper.totalValue}', // addCounters()}',
style: TextStyle(color: Colors.grey, fontSize: 20.0),
),
),

How to update a widget state from another widget in Flutter using global Key?

I have a main widget screen contain two main widgets a Header (marked with red) and a list (marked with purple)
here is my code for that :
class ScreenClient extends StatefulWidget {
_ClientState createState() => _ClientState();
}
class _ClientState extends State<ScreenClient> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
ClientHeader(), // this is my header widget red
Expanded(
child: ClientList(), // this is my list widget purple
),
],
);
}
}
the header widget has three options as you can see Tous Bloqué and ayant Retard , what I'm trying to achieve is pass the value of the clicked option to the list widget marked with purple (because those options are filters and the list elements should be shown based on the chosen option)
I have a hard time understanding state management packages and from what I understand Global Keys can do the trick but How ? .
here is my header widget code :
class ClientHeader extends StatefulWidget {
_HeaderClientState createState() => _HeaderClientState();
}
class _HeaderClientState extends State<ClientHeader> {
String nomSituation;
String option = "Tous";
#override
Widget build(BuildContext context) {
return Container(
child: Row(
children: <Widget>[
GestureDetector(
child: Text(
"Tous",
style: TextStyle(
color: option == "Tous" ? Colors.white : Colors.grey[400],
),
),
onTap: () {
setState(() {
option = "Tous";
});
},
),
GestureDetector(
child: Text(
"Bloqué",
style: TextStyle(
color: option == "Bloqué" ? Colors.white : Colors.grey[400],
),
),
onTap: () {
setState(() {
option = "Bloqué";
//add send value to ClientList widet ?
});
},
),
GestureDetector(
child: Text(
"Ayant Retard",
style: TextStyle(
color:
option == "Ayant Retard" ? Colors.white : Colors.grey[400],
),
),
onTap: () {
setState(() {
option = "Ayant Retard";
});
},
),
],
),
);
}
}
I suggest you can watch 2 examples in this video Pragmatic State Management in Flutter (Google I/O'19)about state mangement. This video helped me a lot when I learn flutter in the begining. They explain how to control the StatefulWidget from the other one:
Make state global, controlled by another widget (from 5m30s)
Use Provider, which is a very popular solution in Flutter, to control share the value between 2 widgets (from 15m05s)
You you have more time, you can study more fancy state management method like Bloc, MobX (List of state management approaches) or even the advance version of Provider named riverpod just pushish few months ago, which try to resolve some cons when using Provider.

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.

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;
});
},
),
])