I am trying to show the price of items in the cart but the total value should be shown in TextField. I am saving data to SQLite and retrieving then show to a widget, but when I try to access total_price to another widget it's not updating, but When I press hot reload again the data shows but not first time when I am opening the page
return FutureBuilder<List<CartModel>>(
future: fetchCartFromDatabase(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data.length > 0) {
cartCount = snapshot.data.length;
for(int i = 0;i<snapshot.data.length;i++){
var price = snapshot.data[i].product_price.split("₹");
total_price+=double.parse(price[1]);
}
} else if (snapshot.hasData && snapshot.data.length == 0) {
return new Text("No Data found");
}
else
{
return new Container(
alignment: AlignmentDirectional.center,
child: new CircularProgressIndicator(),
);
}
);
value initialized
int cartCount = 0;
double total_price=0.0;
The FutureBuilder updates only its children. To update the value of another widget you must use setState.
The best way would be putting FutureBuilder in an upper level or using some sort of state manager, like provider.
To use setState you need to initialize you fetch from an initState of a stetefullWidget (or to call it from a function). This way you will not need a FutureBuilder and must refactor your code:
class YourWidget extends StatefulWidget {
#override
_YourWidgetState createState() => _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
double total_price = 0;
#override
void initState() {
super.initState();
fetchCartFromDatabase().then((value){
setState((){
for(int i = 0;i<value.length;i++){
var price = value[i].product_price.split("₹");
total_price+=double.parse(price[1]);
}
});
});
}
}
The addPostFrameCallback is not a good solution, since it updates the value only in the next frame. When the app grows it leads to lags.
To continue using the FutureBuilder, move your widget tree that needs to be updated to be inside of the FutureBuilder.
Related
I am working with flutter_bloc and trying to understand how it works entirely.
I have Profile Screen. Where in the User should enter his details if previously not existed else should update the details.
Logic: If the user already exists then i fill up the textfields prior to loading , else the textfields are left blank
Problem: I have worked out of achieving the above mentioned goal , but everything seems to work only the first time , Once i save the profile and come back to the profile page it doesn't load the data and textfields are empty even if the user exists.
User_Cubit
class UserCubit extends Cubit<UserState> {
UserCubit() : super(UserInitialState()) {
checkIfUserExists();
}
void checkIfUserExists() {
emit(UserLoadingState());
...
if (userExists) {
emit(UserExists(user));
} else {
emit(UserNotExists());
}
});
}
Profile Screen
class _MyProfileScreenState extends State<MyProfileScreen> {
TextEditingController? fullNameController;
TextEditingController? mailAddressController;
late UserCubit _userCubit;
#override
void initState() {
fullNameController = TextEditingController();
mailAddressController = TextEditingController();
_userCubit = UserCubit(); // initializing the cubit here
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: MultiBlocListener(
listeners: [
BlocListener<UserCubit, UserState>(
listener: (context, state) {
if (state is UserExists) {
appUser = state.user;
mailAddressController!.text = appUser!.email; // Loading the fields
fullNameController!.text = appUser!.fullName; // Loading the fields
}
},
)
],
child: BlocBuilder<UserCubit, UserState>(
builder: (context, state) {
if (state is UserLoadingState) {
return const Center(child: CircularProgressIndicator());
}
return Container(
TextFormField() // For fullName
TextFormField() // For EmailAddress
)
)
);
}
Why does this functionality work only the first time not the consecutive times. Thougth the UserCubit is intialized in initstate ?
Any further suggestions to improve this logic by not initializing the UserCubit on every page render would be also appreciated !!
I'm trying to return a FutureBuilder from a SearchDelegate but result shown are not correct.
query seems to be correct and from network I can see all http calls done correctly, so the problem is related to the list data update itself.
In buildSuggestions (of SearchDelegate) a StatefulWidget called 'ResultList' is returned. This widget has for state:
previousQuery - last search term before rerendering
list - list of data returned from a Future
from - first element to show
pageSize - number of elements returned
I need those variables in order to implement infinite scroll so in ResultList build method first of all I check if widget.query has changed from last rendering
if (previousQuery != widget.query) {
setState(() {
from = 0;
list.clear();
previousQuery = widget.query;
});
}
I'm using a ScrollController, so in initState of ResultList when user reach the bottom of the screen i just update "from":
setState(() {
from += pageSize;
});
In FutureBuilder builder method, if snapshot has new data, I append it to list. I should update list in setState but I can't do this inside a builder.
builder: (context, snapshot) {
List<int> ids = [];
List<int> newids = [];
if (snapshot.hasData) {
ids = list.map((item) => item.id as int).toSet().toList();
newids = (snapshot.data.results as List)
.map((item) => item.id as int)
.toSet()
.toList()
.where((id) => !ids.contains(id))
.toList();
if (newids.length != 0) {
setState(() {//can't do this here
list = [
...list,
...(snapshot.data.results as List)
.where((element) => newids.contains(element.id as int))
];
});
}
}
Any hint? Thanks in advance.
when we Use Future Builder Inside of StateFull Widget we Should know On Every SetState
Future Builder call Future Function. and rebuild it self,
so your problem is going to solve if you Remove Future Builder ,
so please change your code to some thing like below...
#override
Widget build(BuildContext context) {
return newids.Empty && ids.Empty ? CircularProgressIndicator() : MyWidget();
}
and Call Your Future in Init State (and When You Want get new Items(next page))
I'm using Flutter. I need to get just one value from Firestore and update it for all users. So I use a listen to keep the value updated. I receive the value in one variable but I can't use it outside the listen method.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class PaginaGraficos extends StatefulWidget {
#override
_PaginaGraficosState createState() => _PaginaGraficosState();
}
class _PaginaGraficosState extends State<PaginaGraficos> {
#override
Widget build(BuildContext context) {
String _totalGeradoApp = "0";
_getTotalGeradoApp () {
Firestore.instance.collection("dados_app").snapshots().listen(
( snapshot ){
var totalGeradoApp;
for( DocumentSnapshot item in snapshot.documents ){
var dados = item.data;
totalGeradoApp = dados["total_gerado_app"];
print("totalGeradoApp: $totalGeradoApp");
}
_totalGeradoApp = totalGeradoApp;
}
);
}
_getTotalGeradoApp();
print("_totalGeradoApp: $_totalGeradoApp");
return Container(
child: Text("$_totalGeradoApp"),
);
}
}
Some names are in Portuguese because I'm Brasilian but the code is still understandable.
I'm new with Dart so please tell me if I'm doing something stupid.
The function passed to the listen method will execute whenever the value is updated, but the rest of the code runs only once. So, if you want the the Text in the container to be updated whenever the value is updated - use a StreamBuilder.
#override
Widget build(BuildContext context) {
return Container(
child: StreamBuilder(
stream: Firestore.instance.collection("dados_app").snapshots(),
builder: (context, snapshot) {
if(!snapshot.hasData) {
return Text('Loading...');
}
// update _totalGeradoApp
var totalGeradoApp;
var docs = (snapshot.data as QuerySnapshot).documents;
for(var item in docs) {
var dados = item.data;
totalGeradoApp = dados["total_gerado_app"];
}
_totalGeradoApp = totalGeradoApp;
// return Text Widget with updated text
return Text("$_totalGeradoApp");
),
);
}
So, in your code the listener is added to the stream and immediately the next code is executed where a Container is created with _totalGeradoApp, which was "0" initially. Whenever the value was updated _totalGeradoApp is updated but the text in the Container is not. By using a StreamBuilder the Text widget is also updated whenever a new value is available.
I'm programming a flutter application in which a user is presented with a PageView widget that allows him/her to navigate between 3 "pages" by swiping.
I'm following the setup used in https://medium.com/flutter-community/flutter-app-architecture-101-vanilla-scoped-model-bloc-7eff7b2baf7e, where I use a single class to load data into my model, which should reflect the corresponding state change (IsLoadingData/HasData).
I have a main page that holds all ViewPage widgets. The pages are constructed in the MainPageState object like this:
#override
void initState() {
_setBloc = SetBloc(widget._repository);
_notificationBloc = NotificationBloc(widget._repository);
leftWidget = NotificationPage(_notificationBloc);
middleWidget = SetPage(_setBloc);
currentPage = middleWidget;
super.initState();
}
If we go into the NotificationPage, then the first thing it does is attempt to load data:
NotificationPage(this._notificationBloc) {
_notificationBloc.loadNotificationData();
}
which should be reflected in the build function when a user directs the application to it:
//TODO: Consider if state management is correct
#override
Widget build(BuildContext context) {
return StreamBuilder<NotificationState>(
stream: _notificationBloc.notification.asBroadcastStream(),
//initialData might be problematic
initialData: NotificationLoadingState(),
builder: (context, snapshot) {
if (snapshot.data is NotificationLoadingState) {
return _buildLoading();
}
if (snapshot.data is NotificationDataState) {
NotificationDataState state = snapshot.data;
return buildBody(context, state.notification);
} else {
return Container();
}
},
);
}
What happens is that the screen will always hit "NotificationLoadingState" even when data has been loaded, which happens in the repository:
void loadNotificationData() {
_setStreamController.sink.add(NotificationState._notificationLoading());
_repository.getNotificationTime().then((notification) {
_setStreamController.sink
.add(NotificationState._notificationData(notification));
print(notification);
});
}
The notification is printed whilst on another page that is not the notification page.
What am i doing wrong?
//....
class _SomeState extends State<SomeWidget> {
//....
Stream<int> notificationStream;
//....
#override
void initState() {
//....
notificationStream = _notificationBloc.notification.asBroadcastStream()
super.initState();
}
//....
Widget build(BuildContext context) {
return StreamBuilder<NotificationState>(
stream: notificationStream,
//....
Save your Stream somewhere and stop initialising it every time.
I suspect that the build method is called multiple times and therefore you create a new stream (initState is called once).
Please try let me know if this helped.
I am trying to populate my ListView with the result from an API. The API call must take place after the values have been retrieved from Shared Preference. However on execution my function for API call runs an infinite loop and the UI doesn't render. I tracked this behaviour through debug statements.
The circular indicator that should be shown when Future builder is building UI is also not showing.
How can I resolve this?
My code:
class _MyHomePageState extends State<MyHomePage>{
#override MyHomePage get widget => super.widget;
String userID = "";
String authID = "";
//Retrieving values from Shared Preferences
Future<List<String>> loadData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
List<String> l= new List<String>();
if(prefs.getString("ID") == null){
l.add("null");
}
else{
l.add(prefs.getString("ID"));
}
if(prefs.getString("authID") == null){
l.add("null");
}
else{
l.add(prefs.getString("authID"));
}
return l;
}
//Setting values retrieved from Shared Pref
setData() async{
await loadData().then((value) {
setState(() {
userID = value[0];
print('the user ID is' + userID);
authID = value[1];
print('the authID is' + authID);
});
// getAllTasks(userID, authID);
});
print("Set data execution completed ");
}
//FUNCTION to use values from Shared Pref and make API Call
Future<List<Task>> getAllTasks() async{
await setData();
//Waiting for Set Data to complete
print('Ive have retrived the values ' + userID + authID );
List<Task> taskList;
await getTasks(userID, authID, "for_me").then((value){
final json = value;
if(json!="Error"){
Tasks tasks = tasksFromJson(json); //of Class Tasks
taskList = tasks.tasks; //getting the list of tasks from class
}
});
if(taskList != null) return taskList;
else {
print('Tasklist was null ');
throw new Exception('Failed to load data ');
}
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context){
_signedOut(){
widget.onSignedOut();
}
//To CREATE LIST VIEW
Widget createTasksListView(BuildContext context, AsyncSnapshot snapshot) {
var values = snapshot.data;
return ListView.builder(
itemCount: values == null ? 0 : values.length,
itemBuilder: (BuildContext context, int index) {
return values.isNotEmpty ? Ink(....
) : CircularProgressIndicator();
},
);
}
//MY COLUMN VIEW
Column cardsView = Column(
children: <Widget>[
....
Expanded(
child: FutureBuilder(
future: getAllTasks(),
initialData: [],
builder: (context, snapshot) {
return createTasksListView(context, snapshot);
}),
),
],
);
return Scaffold(
body: cardsView,
);
}
}
Instead of being called once... my setData function is being called repeatedly.. How can I resolve this..please help
You're creating Future object on every rebuild of the widget. And since you're calling setState inside your setData method, it triggers a rebuild recursively.
To solve this problem you have to keep a reference to the Future object. And use that reference for the FutureBuilder then it can understand that it is the previously used one.
E.g:
Future<List<Task>> _tasks;
#override
void initState() {
_tasks = getAllTasks();
super.initState();
}
And in your widget tree use it like that:
Expanded(
child: FutureBuilder(
future: _tasks,
initialData: [],
builder: (context, snapshot) {
return createTasksListView(context, snapshot);
}),
),
The FutureBuilder widget that Flutter provides us to create widgets based on the state of some future, keeps re-firing that future every time a rebuild happens!
Every time we call setState, the FutureBuilder goes through its whole life-cycle again!
One option is Memoization:
Memoization is, in simple terms, caching the return value of a function, and reusing it when that function is called again.
Memoization is mostly used in functional languages, where functions are deterministic (they always return the same output for the same inputs), but we can use simple memoization for our problem here, to make sure the FutureBuilder always receives the same future instance.
To do that, we will use Dart’s AsyncMemoizer.
This memoizer does exactly what we want! It takes an asynchronous function, calls it the first time it is called, and caches its result. For all subsequent calls to the function, the memoizer returns the same previously calculated future.
Thus, to solve our problem, we start by creating an instance of AsyncMemoizer in our widget:
final AsyncMemoizer _memoizer = AsyncMemoizer();
Note: you shouldn’t instantiate the memoizer inside a StatelessWidget, because Flutter disposes of StatelessWidgets at every rebuild, which basically beats the purpose. You should instantiate it either in a StatefulWidget, or somewhere where it can persist.
Afterwards, we will modify our _fetchData function to use that memoizer:
_fetchData() {
return this._memoizer.runOnce(() async {
await Future.delayed(Duration(seconds: 2));
return 'REMOTE DATA';
});
}
Note: you must wrap inside runOnce() only the body, not the funciton call
Special thanks to AbdulRahman AlHamali.
You need to save the Future in the State because doing getAllTasks() is triggering the call on every build callback.
In the initState:
this.getAllTasksFuture = getAllTasks();
Then you would use this Future property in the FutureBuilder.