I have the following situation: there's a button on the screen, which adds data to db. And there's the future builder which has future getDataFromDB. When I add data by button, the future builder doesn't get data from DB. And when I do several swipes on the screen it works correctly. What's the matter? Here's the code:
FutureBuilder(
future: DatabaseManager().findAllCaloriesForSelectedDate(currentDate),
builder: (context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData) {
_caloriesCurrent = snapshot.data;
return AnimatedCircularChart(
size: Size(constraints.maxWidth * 0.8, constraints.maxWidth * 0.8),
initialChartData: <CircularStackEntry>[
CircularStackEntry(
<CircularSegmentEntry>[
CircularSegmentEntry(
currentProgress,
Color(AppColors.brandViolet),
),
CircularSegmentEntry(
100 - currentProgress,
Color(AppColors.layoutBackgroundColor),
),
],
),
],
chartType: CircularChartType.Radial,
edgeStyle: SegmentEdgeStyle.round,
percentageValues: true,
);
} else {
return Container();
}
},
)
I'll appreciate any help. Thanks in advance!
There're two problems with your code:
You're obtaining Future object within build function which isn't right way to do it. Every time your widget rebuilds, you get new Future object (so, maybe, it rebuilds when you touch/scroll). You should store Future object in State of your Widget.
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateConfig, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder.
You're using FutureBuilder, but you expect FutureBuilder to rebuild after you change some data. FutureBuilder doesn't work like that and rebuilds only once - when Future value is resolved (if you store Future object within State. In your case, it always gets new Future on rebuild). You probably want to use StreamBuilder. It will allow you to add new data to the stream of data. StreamBuilder will trigger on stream changes and automatically rebuild.
You should read about BloC Architecture, in case you haven't.
Related
So, I'm facing this problem: the 'snapshot' doesn't get any data from Firestore in StreamBuilder in Flutter.$
Here is the code:
StreamBuilder<Driver>(
initialData: null,
stream: DatabaseServices(uid: driver.uid).driverData,
builder: (streamContext, snapshot) {
print(driver.uid);
if (snapshot.hasData) {
Driver currentDriver = snapshot.data;
print(currentDriver.fullName);
print(currentDriver.email);
} else {
print('no data');
}
}
)
Note: stream: DatabaseServices(uid: driver.uid).driverData
-> driver here works fine on top of the whole code and gets the driver data such as uid.
And this code always returns 'no data'.
The weird thing here is that I'm using the same code (with another kind of user -> Client) in another screen, and it works normally, and it gets the data properly.
And in Firestore, I have 2 collections, Driver and Clients, almost the same attributes.
It even has a SubCollection for both collections and it called 'Notification', and I'm using a StreamBuilder to show the notifications for both Client and Driver and it works normally.
Problem solved, the problem was in some attribute that I called it with the wrong name that used in the other collection (Client), I forgot to change it.
Firebase structure:
Code:
I'm using a StreamBuilder for document uid like this:
#override
Widget build(BuildContext context) {
return StreamBuilder<User>(
stream: _stream(),
builder: (BuildContext _, AsyncSnapshot<User> snapshot) {
// this block may get called several times because of `build` function
if (snapshot.hasData) {
final user = snapshot.data;
return SomeWidget(user: user);
}
return CircularProgressIndicator();
},
);
}
Questions:
Since StreamBuilder's builder may get called several times because of the build() method, will that cost me a read every time builder gets called?
Is there any difference in terms of read-count when reading complete uid vs reading uid/education?
If I update age and name value, will that count as one-write or two-writes in terms of firebase write-count?
Firestore charges on every document read, write and delete therefore:
Since StreamBuilder's builder may get called several times because of the build() method, will that cost me a read every time builder gets called?
Yes, if you are reading(retrieving) one document each time, then you will be charged as one read.
Is there any difference in terms of read-count when reading complete uid vs reading uid/education
No difference. The read is done in the document, when you retrieve one document then you are doing one read.
If I update age and name value, will that count as one-write or two-writes in terms of firebase write-count?
If you update one document once (even if all the fields are updated), it will cost you one write operation.
So in my build function, I load my user data, and once it is loaded I would like to change the theme of the app (which calls setState()). The issue is that I can't call setState during the build process, like the error below states. How would I go about loading a user selected theme on app startup? Also, is it not recommended to be loading data inside the build function? It seems to work well but feels kinda gross. Thanks!
Widget build(BuildContext context) {
//get user object from Firebase
//once user is loaded, take their chosen color theme and call updateTheme()
}
Error:
This ThemeSwitcherWidget widget cannot be marked as needing to build because the framework is
I/flutter (23889): already in the process of building widgets. A widget can be marked as needing to be built during the
I/flutter (23889): build phase only if one of its ancestors is currently building. This exception is allowed because
I/flutter (23889): the framework builds parent widgets before children, which means a dirty descendant will always be
For loading data or doing a blocking call, you should use FutureBuilder or StreamBuilder (I am not much aware of firebase APIs so can't tell which one to use, but both of them are very similar.) It takes a future or stream as an argument and builds based on it. I am assuming you know about future API of dart
Here is some example code that will give you some idea.
StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return new SplashScreen();
} else {
if (snapshot.hasData) {
return new MainScreen(firestore: firestore, uuid: snapshot.data.uid);
}
return new LoginScreen();
}
}
)
To keep my app as performant as possible and code testable and maintainable i read about normalizing the redux state: https://redux.js.org/recipes/structuringreducers/normalizingstateshape
I was wondering if something like this is possible with redux in flutter. I have this state and view model:
class CompetitionState {
final LoadingStatus status;
final Competition competition;
}
class Competition {
final List<Competitor> competitors;
}
class CompetitionPageViewModel {
final LoadingStatus status;
final Competition competition;
}
A single competitor inside the List of competitors can change dynamically based on user input or socket events. Currently a change inside one competitor would lead to a complete rerender of my ListView as i connect the view model like this:
return StoreConnector<AppState, CompetitionPageViewModel>(
distinct: true,
converter: (store) => CompetitionPageViewModel.fromStore(store),
builder: (context, viewModel) => CompetitionPageContent(viewModel)
);
Is there a solution to this problem? My first approach would be to also normalize the state by introducing an EntityState, which holds all the different normalized entites. Similiar to how it is displayed here:
https://medium.com/statuscode/dissecting-twitters-redux-store-d7280b62c6b1#.2e55tu6wb
But with this i am unsure how to structure the state and the reducers to be able to work on single entities without causing a complete list rerender. And also how to connect a Widget to a single entity inside this state. Has anyone any expierence with this and can guide me in a direction?
First of all, normalizing state is simply a paradigm so it is applicable in any form of state management. The first thing you need to normalize your state is some way to uniquely identify the model you want to normalize (most likely an ID), in this case the Competitor. You can follow the EntityState example that you linked to to accomplish this. Basically you would just store a mapping of id to Competitor somewhere in your state. Then, you would change your Competition class to:
class Competition {
final List<int> competitorIds;
}
And then, in your Flutter ListView you can attach each competitor to its own StoreConnector like so:
ListView.builder(
itemCount: competition.competitorIds.length,
itemBuilder: (context, index) {
int competitorId = competition.competitorIds[index];
return StoreConnector<AppState, Competitor>(
distinct: true,
converter: (store) => store.state.entities.allCompetitors[competitorId],
builder: ...
);
}
)
In a screen, a Firebase list has logic applied to
reactively detect blueness of widgets
hide blue widgets
show non-blue widgets
I want to count the blue widgets.
What is the best way to do this?
return new StreamBuilder<int>(
stream: subscribeMyThings(thing.id),
builder: (context, thingsSnapshot) {
return new FirebaseAnimatedList(
query: getThings(thing.id),
itemBuilder: (context, snapshot, animation, index) {
return new ReactiveWidget<int>(
reactiveRef: getThingBlueness(snapshot.key),
widgetBuilder: (checkBlueness) {
// code to check blueness here
// if Thing is blue, place empty container
if (thingIsBlue) {
return new Container();
// *** how do I count these??? ***
}
// or, if Thing is NOT blue
return new Thing(thing.id);
);
},
);
);
}
I think you are not hitting the problem from the proper point of view, plus the title is misleading.
You might not intend to count the widget shown on the screen, but rather count a subset of Firebase database entries that fit some conditions.
This makes a big difference in Flutter, since counting widgets involves the the render tree, the layouts and graphics in general, while it looks you want to address a data problem.
If I am right then you might consider having an index in your db keeping track of the number of blue items and listen to it in another streambuilder.
Long story short, you might want to keep a map< id, blueness >, update each element at build time, and iterate over the map whenever required to count.
// somewhere in your outer scope
Map<int, bool> _bluenessMap = new Map<int, bool>();
return new StreamBuilder<int>(
stream: subscribeMyThings(thing.id),
builder: (context, thingsSnapshot) {
return new FirebaseAnimatedList(
query: getThings(thing.id),
itemBuilder: (context, snapshot, animation, index) {
return new ReactiveWidget<int>(
reactiveRef: getThingBlueness(snapshot.key),
widgetBuilder: (checkBlueness) {
// code to check blueness here
// ----------------------------
// before return let us update the map
_bluenessMap[thing.id] = thingIsBlue
// ----------------------------
// if Thing is blue, place empty container
if (thingIsBlue) {
return new Container();
}
// or, if Thing is NOT blue
return new Thing(thing.id);
);
},
);
);
}
// call me when you want to count
int fancyBlueCounter(){
int result = 0;
for(bool isBlue in _bluenessMap.values){
if(isBlue) result += 1;
}
return result;
}
Edit
Since the author of the question confirmed it is a "data problem", I feel like suggesting another (maybe better) way to address it.
Aggregates or Indexes are (in my opinion) the proper way to solve your problem. Anytime someone touches a Thing depending on its blueness it refreshes a purposefully created aggregate in the DB (actually also a simple data entry count). You can do this either following any function editing Things with a transaction updating the blue count, or with cloud functions (see doc here) listening for changes events (creation, deletion, and edit) and updating the count as well.