Flutter: setState not updating screen in Async function - flutter

From initState() I call setDefaults(), which builds a Person object, having done this execution should return to initState which calls getPrefs(), an async function.
#override
void initState() {
super.initState();
setDefaults();
getPrefs();
}
getPrefs calls SharedPreferences.instance() and retrieves some data from prefs - hooyah!
This is compared with some fields of the Person object, finally setState() is called to update the screen with a Tile colour, and to render a Banner widget if (usersTile == true).
This never happens, setState never updates the screen. I don't know why.
Future<void> getPrefs() async {
prefs = await SharedPreferences.getInstance();
List<String> neighbourProfilesDownloaded = prefs!.getStringList("downloadedPersonProfiles") ?? [];
neighbourProfilesDownloaded.add(widget._person!.firebaseId!);
prefs!.setStringList("downloadedPersonProfiles", neighbourProfilesDownloaded);
usersTile = widget._person!.firebaseId == prefs!.getString("firebaseId") ?? "";
setState(() {
tileColor = usersTile ? Colors.grey.shade900 : Color(0xFF462c22);
usersTile;
});
}
This question has been asked before. I've tried looking at other solutions on StackOverflow, so far as I can see I'm already following their proposed solutions, to await the asynchronous call then setState after it. And none of the proposed answer have been marked correct anyway.
I've tried assigning the value of usersTile inside setState, no difference.
The Tile colour does sometimes work, however not on screen load. If I scroll down the Tile list, swiping the usersTile out of the viewport, then swiping back up to return to it, I find that DOES update the Tile's colour and Banner. Indicating the Tile property assignments are working but setState is not.
This is not satisfactory, it screams buggy to the user.
I know this can work, because I have another class, another ListView screen in my app which seems to reliably update the screen on load. I've studied the control flow on that screen and can't see how it's different. Quite the boggle.
This is the Card Widget, with properties I want to adjust, from build()
return Card(
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
color: tileColor,
key: ValueKey(widget._person!.firebaseId! + "LoaningTileCard"),
child: usersTile ? Banner(
message: "YOURS",
location: BannerLocation.topEnd,
color: Colors.red,
child: CardContentWidget)
: CardContentWidget);

I think CardContentWidget uses Card in build method, Card has default background color as Colors.white by using CardTheme.of(context).
You may have to reset with transparent or user Container instead.

Related

Rating becomes zero when i scroll down in flutter

I am using flutter rating bar package to give feedback to particular section then rating becomes back to 0. ho w can i persist the given rating constant .
Here is the screenshot of the app ...
RatingBar(
initialRating: 0,
direction: Axis.horizontal,
allowHalfRating: false,
itemCount: 5,
ratingWidget: RatingWidget(
full: const Icon(Icons.star,
color: Colors.orange),
half: const Icon(
Icons.star_half,
color: Colors.orange,
),
empty: const Icon(
Icons.star_outline,
color: Colors.orange,
)),
onRatingUpdate: (value) {}),
i think its a flutter behavior. in case we have much children on listview, then when we scrolldown, the widget that out of screen will marked as dirty widget.
then when we back scroll again , flutter will rebuild the widget.
Flutter already provide the solution here called
Destruction mitigation
you have to store the rating value to the object state. so when the widget get re-build , the value will automatically set from stored value.
other simple solution (not recomended)
you can extend the cache of listview.
ListView(
shrinkwrap: true,
cacheExtent : 99999999
children: [
RatingWidget(),
],
here the explanation to the chaceExtent
or another question in stackoverflow
What exactly does cacheExtent property in ListView.Builder do?
In onRatingUpdate save its value
onRatingUpdate :(val) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt("teacherConcept", val);
}
Then in initstate get the value like
SharedPreferences prefs = SharedPreferences.getInstance();
int teacherConcept = prefs.get("teacherConcept", true);
Assign this teacher concept as the initial value of the ratingbar
That is because you have set initialRating to 0. So when you scroll down and come back up the whole widget rebuilds meaning for performance purposes it does not keep it in memory and the ratings get reset. So, what you can do is set the ratings set by the user into a variable through the onRatingUpdate and pass that variable into intitialRating
Map<String, int> ratings={'subjectClear':0, 'subjectVarieties':0}; // add other 8 as well
...
...
initialRating: ratings['subjectClear'],
onRatingUpdate: (value){
rating['subjectClear'] = value;
},
...
Still this data will be erased after the user restarts the app. So what you can do is store the data in a database and when the user returns substitute the values to the necessary variables.

flutter swiper package avoid to swipe to next position, it stay on first

I have a working code before null safety flutter upgrade. But after the migration, the same code doesn't work.
I had a simple horizontal swipe card, but now something force the swipe to stay on the first position or rebuild. When I remove didChangeDependencies (function I use to load when data change) the swipe is OK. I think when data is load by didChangeDependenciesit refresh new Swiper.children( and force to return always to first index position.
But I can't do without didChangeDependencies, how can I do ?
Here is the package https://pub.dev/packages/flutter_swiper_null_safety/example
Here is my code:
#override
void didChangeDependencies() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
}
new Swiper.children(
viewportFraction: 0.8,
scale: 0.6,
autoplay: false,
loop: false,
control: new SwiperControl(
size: 25.0,
color: Color(0xffff9a7b),
disableColor: Colors.transparent ,
padding: const EdgeInsets.all (5.0),
),
children: <Widget>[
Card1()
Card2()
Card3()
]
Usually you would need to have a controller or index saved in the state that could hold the state of the downstream widget so on rebuilds the state stayed the same.
After looking at this package it doesn't appear you can pass in an Index or Controller to the widget so it will be rebuilt any time something above it on the stack is rebuilt.
Is it possible to reorganize your page so that the swiper is not under it in the stack?

Flutter identify Widget built by loop or listview.builder

I can't get around this issue, how do I select Widget built by for loop or a ListView.builder?
In code I will post, I have buttons built by for loop,
I want to change button's background color on click.
I'm using Provider in my project so this function is implemented with Provider.
In my Provider class I made a boolean and a function to switch that boolean, and then I assigned it to buttons, and set it's color to value of that boolean with ternary operator.
While this somewhat works, the problem is... All of the buttons built by the for loop are affected,
meaning. I press one button, all of them change color.
How do I fix this?
My widget which includes mentioned buttons:
ListView(
children: [
for(int i = 0; i < recipeIngredients.length; i++)
IngredientsTab(
ingredientText: recipeIngredients[i],
textColor: categoryAndSeeAllColor,
width: width,
**buttonColor: provider.isButtonPressed ? accentColor : Colors.grey[100],**
buttonIcon: Icons.add,
buttonText: 'Add to cart',
buttonTextColor: accentColor,
**buttonAction: provider.changeBackground,**
),
],
),
In my Provider class:
bool isButtonPressed = false;
void changeBackground() {
isButtonPressed = isButtonPressed ? false : true;
notifyListeners();
}
}
While I understand this happens for obvious reason, I'm assigning this isButtonPressed to all buttons so when its true they all change... So that makes sense, I just don't know how to solve it, I'm also having similar issue in another project where items are built with ListView.builder...
So if someone would help me with this would mean a lot to me :)
Usually you have in the Provider a List of Ingredients.
Each Ingredient have an ID and a bool selected variable.
The buttonAction calls the method with the ID:
void changeBackground(int ingredientID) {
ListOfIngredients.singleWhere((elem)=>elem.ID==ingredientID)).isButtonPressed = !isButtonPressed;
notifyListeners();
}
Then you build the ListView using the List of Ingredients and for each Ingredient you check the isButtonPressed value.

Trying to display a Flutter snackbar throws an exception error

Please before mark it as duplicate take a look, I have been trying this thread but i can't make it work.
I am trying to make my learning app to display a snackbar, and yes i ran the google cookbook sample and it works, but i am trying to challenge my self doing something else, basically what it is, is just a scaffold with two buttons that increase and decrease, when the index is zero i want to display a snackbar that index is zero, can't decrease more than that. So i got this button and onPressed method calls _onTapPrevious method which passed the context parameter, but i am not sure this is the correct way to pass the context.
new RaisedButton(
padding: const EdgeInsets.all(8.0),
textColor: Colors.white,
color: Colors.redAccent,
onPressed: () => _onTapPrevious(context),
child: new Text("Previous"),
),
Here is the _onTapPrevious method that i created a snackbar, and also checks whether the widget index is > than 0 and if so it can decrease the index, and when it reaches 0 then it should display the snackbar.
_onTapPrevious(BuildContext context) {
final snackBar = SnackBar(content: Text("Index 0, can't decrease more."));
setState(() {
if (widget.index > 0) {
widget.index--;
} else {
Scaffold.of(context).showSnackBar(snackBar);
}
});
}
The error that i get:
flutter: Another exception was thrown: Scaffold.of() called with a context that does not contain a Scaffold.
try to add
final _scaffoldKey = GlobalKey<ScaffoldState>();
to your class, then in Scaffold:
key: _scaffoldKey,
and finally:
_scaffoldKey.currentState.showSnackBar(snackBar);
Hope that helps!

Flutter TextEditingController does not scroll above keyboard

In android version, Flutter TextEditingController does not scroll above keyboard like default text fields do when you start typing in field. I tried to look in sample apps provided in flutter example directory, but even there are no example of TextEditController with such behaviour.
Is there any way to implement this.
Thanks in advance.
so simple
if your textfields is between 5-10 fields
SingleChildScrollView(
reverse: true, // add this line in scroll view
child: ...
)
(August 20, 2021 Flutter 2.2.3)
I think my answer might be the cleanest solution for this problem:
#override
Widget build(BuildContext context) {
/// Get the [BuildContext] of the currently-focused
/// input field anywhere in the entire widget tree.
final focusedCtx = FocusManager.instance.primaryFocus!.context;
/// If u call [ensureVisible] while the keyboard is moving up
/// (the keyboard's display animation does not yet finish), this
/// will not work. U have to wait for the keyboard to be fully visible
Future.delayed(const Duration(milliseconds: 400))
.then((_) => Scrollable.ensureVisible(
focusedCtx!,
duration: const Duration(milliseconds: 200),
curve: Curves.easeIn,
));
/// [return] a [Column] of [TextField]s here...
}
Every time the keyboard kicks in or disappears, the Flutter framework will automatically call the build() method for u. U can try to place a breakpoint in the IDE to figure out this behavior yourself.
Future.delayed() will first immediately return a pending Future that will complete successfully after 400 milliseconds. Whenever the Dart runtime see a Future, it will enter a non-blocking I/O time (= inactive time = async time = pending time, meaning that the CPU is idle waiting for something to complete). While Dart runtime is waiting for this Future to complete, it will proceed to the lines of code below to build a column of text fields. And when the Future is complete, it will immediately jump back to the line of code of this Future and execute .then() method.
More about asynchronous programming from this simple visualization about non-blocking I/O and from the Flutter team.
Flutter does not have such thing by default.
Add your TextField in a ListView.
create ScrollController and assign it to the ListView's controller.
When you select the TextField, scroll the ListView using:
controller.jumpTo(value);
or if you wish to to have scrolling animation:
controller.animateTo(offset, duration: null, curve: null);
EDIT: Of course the duration and curve won't be null. I just copied and pasted it here.
Thank you all for the helpful answers #user2785693 pointed in the right direction.
I found complete working solution here:
here
Issue with just using scroll or focusNode.listner is, it was working only if I focus on textbox for the first time, but if I minimize the keyboard and again click on same text box which already had focus, the listner callback was not firing, so the auto scroll code was not running. Solution was to add "WidgetsBindingObserver" to state class and override "didChangeMetrics" function.
Hope this helps others to make Flutter forms more user friendly.
This is an attempt to provide a complete answer which combines information on how to detect the focus from this StackOverflow post with information on how to scroll from Arnold Parge.
I have only been using Flutter for a couple days so this might not be the best example of how to create a page or the input widget.
The link to the gist provided in the other post also looks like a more robust solution but I haven't tried it yet. The code below definitely works in my small test project.
import 'package:flutter/material.dart';
class MyPage extends StatefulWidget {
#override createState() => new MyPageState();
}
class MyPageState extends State<MyPage> {
ScrollController _scroll;
FocusNode _focus = new FocusNode();
#override void initState() {
super.initState();
_scroll = new ScrollController();
_focus.addListener(() {
_scroll.jumpTo(-1.0);
});
}
#override Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Some Page Title'),
),
body: new DropdownButtonHideUnderline(
child: new SafeArea(
top: false,
bottom: false,
child: new ListView(
controller: _scroll,
padding: const EdgeInsets.all(16.0),
children: <Widget>[
// ... several other input widgets which force the TextField lower down the page ...
new TextField(
decoration: const InputDecoration(labelText: 'The label'),
focusNode: _focus,
),
],
),
),
),
);
}
}