setState not updating part of page when called - flutter

When accessing a flutter page, I'm creating a graph (syncfusion) and 2 rows with values downloaded from some API.
I create an empty page, then, when the data is loaded, I set the variables with the respective data and call setState(() {walletRows=wallet.getWallet();}); to update the rows variable with the data for the rows I mentioned in the beginning.
Problem: the graph is actually generated but the rows are not created (if I change page or hot reload, the rows appears)
In initState i call loadPageData which loads the data in 2 variables.
#override
initState() {
loadPageData();
_pageController = PageController();
super.initState();
}
This is the loadPageData func
Future<void> loadPageData() async {
this._chartData = await getChartData(); // variable to feed sfcartesianchart
const storage = FlutterSecureStorage(); // getting token for api call
String token = await storage.read(key: 'token') as String;
var wallet = WalletRow();
wallet.addText(token);
setState(() {walletRows=wallet.getWallet();}); //setting state with updated walletrows
}
Within a Column i have another Column where the children are taken from walletRows.
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
textAlign: TextAlign.left,
"Rows",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color:
Color.fromARGB(255, 113, 113, 113)),
),
Column(children:walletRows)
]),
As requested, here is the page (just part of it, else it would be too long):
https://pastebin.com/cVC2pdhX
UPDATE
I realized that if I add a Button with setState on the onPressed event, the rows get correctly added; so it seems there is a problem with the first setState although I still can't find a solution to it.

Related

hive is giving me empty list upon listening to changes made to an open box

I have a hive box opened in the main.dart and set it to a global variable so am able to access it from all other classes.
Now in one of my classes (settingsView.dart) which is a StatefulWidget am able to put data in the box in the form Map<String,Map<String,dynamic>>. To be specific the Map<String,dynamic> can be a Map<String,String> or Map<String,List>. e.g.
{"1A":{"num_on_roll": "34", "subjects": ["Mathematics","English","Science",...]}}
Now am also retrieving or reading this data and to display it in the UI the "num_on_roll" value in a Text widget and "subjects" value in a Wrap.
NOW THE PROBLEM.
The first ("num_on_roll") is always updated in the UI successfully but the "subjects" values in the Wrap are never updated unless I do hot restart or quit application and start it afresh, by so doing all data will be displayed successfully.
I have tried using ValueListenableBuilder to listen for changes in the box.
"class_constants" is the specific for the stored data which is Map<String,Map<String,dynamic>>.
ValueListenableBuilder(
valueListenable: Hive.box("mainDB").listenable(keys: ["class_constants"]),
builder: (context,Box box,child) {
var clsConst = box.get("class_constants", defaultValue: {});
return Wrap(
children: List.generate(
isPresent
? clsConst[classes[tab]]["subjects"].length
: selectedSubjects.length,
(index) => Text(
"${isPresent ? clsConst[classes[tab]]["subjects"][index] : selectedSubjects[index]}, ",
style: const TextStyle(
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic),
)),
);
}
),
Why is it that the data is store successfully but not displaying some part?
Please help me out.
I found the proper solution myself.
Hive has problem retrieving growable list immediately it has been put in the box. Even you must await it else it can't store!
So trick is to change growable list to non-growable list.
List list = [];
list.add(1);
list.add(2);
list.add(3);
// non-growable
List tempList = List.generate( list.length, (_)=> list[_], growable: false);
mainDB!.put('key',tempList)
;

How do i modify the data of an existing variable in flutter?

I want to make an editable TextWidget in flutter but I don't really know how to go around it, I did some research, but still can't find a good solution.
Here's my sample code below.
I have a variable called
int qty = 1;
and so I called the variable in TextWidget
Column(
children: [
Text(
"${qty}",
style: TextStyle(),
)
],
),
I want to have these features that make user tab on the value to change it if they want, upon tap, a pop-up dialog will show to give the user the ability to change the existing value to whatever the user wants.
Please if anyone knows how, please help.
You will need a statfull widget to call setState and make the UI update with the new value stored in your qty variable. (I'am assuming that you are not using any state managment).
I wrote a possible solution for what you need.
Let look into some considerations:
Text will show whatever is in the qty as long we call setState after (or do it inside) we change the value of qty.
You need some widget to detect your tap. If you want to the text be 'clicable' then it should be wraped inside that widget.
The onTap/onPress call back of that widget should show a new widget. For this you can use the already made showDialog() and pass it a Dialog Widget. in here you will put your ui for that.
In some point of that UI you need to introduce the new value. So you can use a simple TextField that will save the introduced value, where you can assign it to qty, without forgetting to call setState! Note that it deal with strings, so you neet to do an int.parse() ou double.parse accordingly to you qty var type.
And I think that's it.
The could be other ways of doing it. This is a good and simple approach for your need.
I wrote a piece of code to help or somelse how is trying to do it:
InkWell(
// can be gesture detector, button, etc
onTap: () => showDialog(
context: context,
builder: (context) => Dialog(
child: Container(
color:
Colors.white60, // change it accordingly to you
height: 80, // change it accordingly to you
width: 200, // change it accordingly to you
child: Column(
children: [
const Text('Change your value here'),
TextField(
decoration:
InputDecoration(hintText: qty.toString()),
onChanged: (insertValue) => setState(() {
qty = int.parse(insertValue);
}),
// you can use other callBack function (like onComplete,
// onSaved), wich is more eficient than calling setState eveytime,
// but you have to do the needed adtaptions. Like onSave
// needs a key to call the save function. is easy just google it.
),
],
)),
)),
child: Text(
"${qty}",
),
),
What you are probably looking is a DropdownButton.
You would have something like this:
int qty = 1;
List<int> listOfValues = [1,2,3,4];
and then in your column you would have
DropdownButton<int>(
// This are the list of items that will appear in your dropdown menu.
// items is all the options you want your users to be able to select from,
// and it take a list of `DropdownMenuItem`. So instead of creating a `DropdownMenuItem`
// for each of the items in `listOfValues`, we iterate through it and return
// a `DropdownMenuItem`
items: listOfValues
.map((item) => DropdownMenuItem<int>(
value: item,
child: Text('$item'),
))
.toList(),
value: qty,
onChanged: (value) {
if (value != null) {
setState(() {
qty = value;
});
}
},
),
For more information on DropDownButton, check the following links:
https://api.flutter.dev/flutter/material/DropdownButton-class.html
https://www.youtube.com/watch?v=K8Y7sWZ7Q3s
Note: In a scenario where you want to increase the quantity of an item, like in a shopping cart, maybe having a button increment qty by 1 would be better.

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.

How to 'cancel' the stream data response after request another one

I have a list of Expandable Items, each time I click in one of the items a stream is called returning a list of another items related to that I clicked on. The problem is if I quick expand two items of my initial list, the last remains with the items from the first one.
Example of what is expected:
(EXPANDABLE LIST)
Colombia
(items showed when I click on Colombia)
Bogotá
China
(items showed when I click on China)
Beijing
Example of what happens if I quickly open two items:
(EXPANDABLE LIST)
Colombia (clicked first, and before the load quickly click on China)
China
Bogotá
Is there a way to close or cancel or pause the stream every time I expand one item?
UPDATE
SCREEN
return ExpansionTile(
leading: items[index].image == null || items[index].image.isEmpty ? Image.asset(ASSET_NOIMAGE_URL,
fit: BoxFit.scaleDown
) : Image.network('${BASE_ROUTE_URL}/${ROUTE_SLASH}/${items[index].image}', fit: BoxFit.scaleDown,),
title: Text('${items[index].code} | ${items[index].desc}', style: TextStyle(fontWeight: FontWeight.bold, color: AppColorSecondary),),
children: [
Container(
height: MediaQuery.of(context).size.height * 0.75,
width: MediaQuery.of(context).size.width,
child: CountryDetails(items[index]),
),
],
WIDGET
class _CountryDetailsState extends State<CountryDetails> {
Country country;
#override
void initState() {
country = Provider.of<Country>(context, listen: false);
country.load(produtoGradeFVList: Provider.of<CountryListProvider>(context, listen: false).produtoGradeFVList).then((value) {
}); // here the stream is feed
super.initState();
}
You could set a debounce duration on the stream controller to avoid receiving two requests within a very short interval of time as:
_cityController.stream
.debounceTime(const Duration(milliseconds: 500))
.listen(_handlerFunctionForTapEvent);
Here, _cityController is your StreamController that's listening to the tap events of expandable items from the ui. Try setting a debounce time of 500 milliseconds, hopefully that would fix the issue.
Or you could also send the name of country along with the list of cities in the response from the stream and perform a check in the ui before displaying the city names in the expandable list.

Can mobx observable data persist between multiple screens?

I have a mobx class that stores an observable named value and an action named increment, i also have 2 screens and i want the value of the observable to change on the second screen if i increment it on the first screen but the value always restart if i change a screen.
It used to work for me that way using mobx in react native , even if i change the screen the state updates in all of the screens but in flutter it doesn't work that way.
Mobx class
import 'package:mobx/mobx.dart';
part 'appstores.g.dart';
class AppStore = _AppStore with _$AppStore;
abstract class _AppStore with Store {
#observable
int x = 0;
#action
void increment() {
x++;
}
}
Screen 1
// the x value updates here
Observer(
builder: (_) => Text("${store.x}"),
),
ButtonBar(
children: <Widget>[
RaisedButton(
onPressed: (){store.addToOrders();},
child: Text('Disabled Button',
style: TextStyle(fontSize: 20))),
],
),
Screen 2
// but not here
Observer(
builder: (_) => Text('orders ${store.x}',
style: new TextStyle(
fontSize: 15,
color: Colors.grey,
)),
),
I want the value of the observable x to change on all off the sreens with i increment it in one of the screens. How can i acheive that ?
You are instantiating a different store in each screen.
I think the best way to use the same instance is by injecting as a singleton. I'm using getIt and to inject I'm using registerLazySingleton method
getIt.registerLazySingleton(() => MyStore());
To access that on each screen you can do something like:
final store = getIt<MyStore>();