I am trying to update the check of the CheckBoxListTile but I am not getting the desired result.
This is my code:
Widget build(BuildContext context) {
return Query(
options: QueryOptions(document: getVehiclesTypeQueryDoc),
builder: (result, {fetchMore, refetch}) {
if (result.isLoading && result.data == null) {
return Center(
child: CircularProgressIndicator(),
);
}
newVehiclesType = [];
final _vehiclesTypeJson = result.data['tipoVehiculos'] as List<dynamic>;
for (final i in _vehiclesTypeJson) {
var productMap = {
'id': i['id'],
'nombre': i['nombre'],
'imagenActivo': i['imagenActivo'],
'isSelected': true
};
newVehiclesType.add(productMap);
}
final List<dynamic> test = newVehiclesType;
print('test: $test');
return ListView.builder(
itemCount: test.length,
shrinkWrap: true,
itemBuilder: (context, index) {
print(index);
print('VALO: ${test[index]['isSelected']}');
return CheckboxListTile(
title: Text(test[index]['nombre'].toString()),
value: test[index]['isSelected'],
onChanged: (bool newValue) {
setState(() {
test[index]['isSelected'] = newValue;
});
},
);
},
);
},
);
}
I created a newVehiclesType variable, the query did not give me a variable to use the check for.
I am new to flutter.
this is the code that i use in my application hopefully hopefully it will help you
value: isCheckedEdit.contains(_ingredientList[index].nameIngredient),
onChanged: (value) {
if (value) {
setState(() {
isCheckedEdit
.add(_ingredientList[index].nameIngredient);
print(isCheckedEdit);
});
} else {
setState(() {
isCheckedEdit.remove(
_ingredientList[index].nameIngredient);
print(isCheckedEdit);
});
}
},
);
Angular has an amazing documentation that teaches every concept in detail (60%). I highly recommend you to visit the the documentation site. There are examples and explanations for each topic.
https://angular.io/guide/reactive-forms
Thank you so much for bringing such a beautiful question up. Angular is love.
class VehiclesList extends StatefulWidget {
#override
_VehiclesListState createState() => _VehiclesListState();
}
class _VehiclesListState extends State<VehiclesList> {
List<dynamic> test;
#override
Widget build(BuildContext context) {
// your code
// when result has data, instead of
// final List<dynamic> test = newVehiclesType;
// do this
test = newVehiclesType;
}
}
What this basically does is, it uses test to hold the widget state. In your code, test is local to your build function and would be reinitialized as many times build function runs.
This way it would not reinitialize on widget rebuilds when setState() is called.
Related
I have an expansion panel in _buildCategoryListings() that does not expand when the header or the dropdown button is clicked. isExpanded is set to the boolean categoryView.isExpanded. Through printing via the console I can see that the setState is actually updating the bool value but it looks like the actual widget isn't being redrawn perhaps? If I manually set isExpanded to true I see the results I want from the GUI. I also had set isExtended to theExpanded (which is in MovieListingView) which raises the issue of a mutable variable being in a class that extends StatefulWidget, this did give me the desired results though.
The question: How do I get the expansion panel to update the categoryView.isExpanded (via theListings[panelIndex].isExpanded) bool and show it via the GUI?
Thank you in advance.
Side note I thought about using a provider to keep track of this bool but that seems like overkill.
class MovieListingView extends StatefulWidget {
#override
_MovieListingView createState() => _MovieListingView();
MovieListingView(this.movieList);
final MovieCatalog movieList;
//bool theExpanded = false;
List<MovieCategoryView> generateCategoryList() {
List<MovieCategoryView> tempList = [];
List<String> movieCategories = movieList.Categories;
movieCategories.forEach((category) {
MovieCategoryView categoryView = new MovieCategoryView(
movieCategoryName: category.toString(),
movieList: movieList.getMovieCardListByCategory(category));
tempList.add(categoryView);
});
return tempList;
}
}
class _MovieListingView extends State<MovieListingView> {
Widget build(BuildContext context) {
// TODO: implement build
return SingleChildScrollView(
physics: ScrollPhysics(),
padding: EdgeInsets.all(5.0),
child: _buildCategoryListings(),
);
}
List<MovieCategoryView> generateCategoryList() {
List<MovieCategoryView> tempList = [];
List<String> movieCategories = widget.movieList.Categories;
int counter = 0;
movieCategories.forEach((category) {
MovieCategoryView categoryView = new MovieCategoryView(
movieCategoryName: category.toString(),
movieList:
widget.movieList.getMenuItemCardListByCategory(category),
isExpanded: false);
tempList.add(categoryView);
});
return tempList;
}
Widget _buildCategoryListings() {
final List<MovieCategoryView> theListings = generateCategoryList();
return ExpansionPanelList(
expansionCallback: (panelIndex, isExpanded) {
setState(() {
theListings[panelIndex].isExpanded = !isExpanded;
//widget.theExpanded = !isExpanded;
});
},
children: theListings.map((MovieCategoryView movieCategoryView) {
return ExpansionPanel(
canTapOnHeader: true,
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(movieCategoryView.movieCategoryName),
);
},
body: Column(
children: movieCategoryView.movieList,
),
isExpanded: movieCategoryView.isExpanded);
}).toList(),
);
}
}
class MovieCategoryView {
MovieCategoryView(
{#required this.movieCategoryName,
#required this.movieList,
this.isExpanded});
String movieCategoryName;
List<MovieCard> movieList;
bool isExpanded = false;
}
This is happening because whenever the setstate() is called whole widget tree is rebuild and thus when you try changing the isexpandable value ,is gets changed but the
function generateCategoryList(); again gets called and generates the previous list again and again.
Widget _buildCategoryListings() {
final List<MovieCategoryView> theListings = generateCategoryList();
To fix this call the generateCategoryList(); once in initState() and remove the line above line.
I am building a flutter app and I get some data from a future, I also got the same data with a changenotifier. Well the logic is that while some object doesn't have data because its waiting on the future then display a spinning circle. I have already done this in the app and I have a widget called Loading() when the object has not received data. The problem I have run into is that I get the data, but it doesn't display anything.
the data displays correctly until I perform a hot refresh of the app. a capital R instead of a lowercase r. The difference is that it starts the app and deletes all aggregated data.
when this happens it seems that the data fills the object but I hypothesize that it is becoming not null meaning [] which is empty but not null and is displaying the data "too quickly" this in turn displays nothing for this widget until I restart "r" which shows me the above screenshot.
here is the offending code.
import 'package:disc_t/Screens/LoggedIn/Classes/classTile.dart';
import 'package:disc_t/Screens/LoggedIn/Classes/classpage.dart';
import 'package:disc_t/Screens/LoggedIn/Classes/classpageroute.dart';
import 'package:disc_t/Services/database.dart';
import 'package:disc_t/models/user.dart';
import 'package:disc_t/shared/loading.dart';
import 'package:flutter/material.dart';
import 'package:morpheus/page_routes/morpheus_page_route.dart';
import 'package:provider/provider.dart';
class ClassList extends StatefulWidget {
#override
_ClassListState createState() => _ClassListState();
}
class _ClassListState extends State<ClassList> {
#override
void initState() {
ClassDataNotifier classdatanotif =
Provider.of<ClassDataNotifier>(context, listen: false);
// final user = Provider.of<User>(context);
// getTheClasses(classdatanotif);
// List<ClassData> d = classes;
}
#override
Widget build(BuildContext context) {
ClassDataNotifier classdatanotif = Provider.of<ClassDataNotifier>(context);
List<ClassData> cData = Provider.of<List<ClassData>>(context);
bool rebd = false;
Widget checker(bool r) {
if (cData == null) {
return Loading();
} else {
if (rebd == false) {
setState(() {
rebd = true;
});
rebd = true;
return checker(rebd);
// return Text("Still Loading");
} else {
return PageView.builder(
scrollDirection: Axis.horizontal,
itemCount: cData.length,
// controller: PageController(viewportFraction: 0.8),
itemBuilder: (context, index) {
return Hero(
tag: cData[index],
child: GestureDetector(
onTap: () {
// Navigator.of(context).push(ClassPageRoute(cData[index]));
Navigator.push(
context,
MorpheusPageRoute(
builder: (context) =>
ClassPage(data: cData[index]),
transitionToChild: true));
},
child: ClassTile(
classname: cData[index].classname,
description: cData[index].classdescription,
classcode: cData[index].documentID,
),
),
);
});
}
}
}
return checker(rebd);
}
}
here is how the provider is implemented
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
// final DatabaseService ds = DatabaseService();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<User>.value(
value: AuthService().user,
// child: MaterialApp(
// home: Wrapper(),
// ),
),
ChangeNotifierProvider<ClassDataNotifier>(
create: (context) => ClassDataNotifier(),
),
FutureProvider(
create: (context) => DatabaseService().fetchClassdata,
)
],
child: MaterialApp(home: Wrapper()),
);
}
}
and here is the function that is ran to get the data
Future<List<ClassData>> get fetchClassdata async {
QuerySnapshot snapshot = await classesCollection.getDocuments();
List<ClassData> _classList = List<ClassData>();
snapshot.documents.forEach((element) async {
QuerySnapshot pre = await Firestore.instance
.collection("Classes")
.document(element.documentID)
.collection("Pre")
.getDocuments();
List<Preq> _preList = List<Preq>();
pre.documents.forEach((preClass) {
Preq preqData = Preq.fromMap(preClass.data);
if (preClass.data != null) {
_preList.add(preqData);
}
});
ClassData data =
ClassData.fromMap(element.data, element.documentID, _preList);
if (data != null) {
_classList.add(data);
}
});
return _classList;
}
I think the logic of your provider is fine, the problem lies in the line
snapshot.documents.forEach((element) async {
...
}
The forEach is not a Future (what is inside it's a future because the async, but the method itself not) so the code runs the first time, it reaches the forEach which does its own future on each value and propagate to the next line of code, the return, but the list is empty because the forEach isn't done yet.
There is a special Future.forEach for this case so you can wait for the value method before running the next line
Future<List<ClassData>> get fetchClassdata async {
QuerySnapshot snapshot = await classesCollection.getDocuments();
List<ClassData> _classList = List<ClassData>();
await Future.forEach(snapshot.documents, (element) async {
QuerySnapshot pre = await Firestore.instance
.collection("Classes")
.document(element.documentID)
.collection("Pre")
.getDocuments();
List<Preq> _preList = List<Preq>();
pre.documents.forEach((preClass) {
Preq preqData = Preq.fromMap(preClass.data);
if (preClass.data != null) {
_preList.add(preqData);
}
});
ClassData data =
ClassData.fromMap(element.data, element.documentID, _preList);
if (data != null) {
_classList.add(data);
}
});
return _classList;
}
Here is a similar problem with provider with a forEach. Maybe it can help you understand a bit better
I am trying to implement a listView single selection in my app such that once an item in the list is tapped such that pressed item color state is different from the others. I have done all I know but it does not work well. The problem is that even though my implementation updates each item state when pressed, it doesn't reset the others to their initial state.
class BoxSelection{
bool isSelected;
String title;
String options;
BoxSelection({this.title, this.isSelected, this.options});
}
class _AddProjectState extends State<AddProject> {
List<BoxSelection> projectType = new List();
#override
void didChangeDependencies() {
super.didChangeDependencies();
projectType
.add(BoxSelection(title: "Building", isSelected: false, options: "A"));
projectType
.add(BoxSelection(title: "Gym House", isSelected: false, options: "B"));
projectType
.add(BoxSelection(title: "School", isSelected: false, options: "C"));
}
child: ListView.builder(
itemCount: projectType.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
setState(() {
//here am trying to implement single selection for the options in the list but it don't work well
for(int i = 0; i < projectType.length; i++) {
if (i == index) {
setState(() {
projectType[index].isSelected = true;
});
} else {
setState(() {
projectType[index].isSelected = false;
});
}
}
});
},
child: BoxSelectionButton(
isSelected: projectType[index].isSelected,
option: projectType[index].options,
title: projectType[index].title,
),
);
},
),
Your problem is that you're using index to access projectType elements but you should be using i
if (i == index) {
setState(() {
projectType[i].isSelected = true;
});
} else {
setState(() {
projectType[i].isSelected = false;
});
}
In any case I think your code can be improved since it's not as efficient as it could be. You're iterating over the entire list and calling setState twice in every iteration, recreating the widget tree a lot of times unnecessarily when it can be done in one shoot.
Save your current selection in a class level variable
BoxSelection _selectedBox
Simplify your code to act directly over the current selection insted of iterating over the entire list
onTap: () =>
setState(() {
if (_selectedBox != null) {
_selectedBox.isSelected = false;
}
projectType[index].isSelected = !projectType[index].isSelected;
_selectedBox = projectType[index];
});
I have an API that returns content and I put this content in a GridView.builder to allow pagination.
I have architected the page in such a way that I have a FutureBuilder on a stateless widget and when the snapshot is done I then pass the snapshot data to a stateful widget to build the grid.
It is all working fine, however I want now to implement a functionality that allows me to reload the widget by placing a reload icon when snapshot has error and on click reloading widget. How can I accomplish this?
The following is my FutureBuilder on my Stateless widget:
return new FutureBuilder<List<Things>>(
future: apiCall(),
builder: (context, snapshot) {
if (snapshots.hasError)
return //Reload Icon
switch (snapshots.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.done:
return StatefulWidhet(things: snapshot.data);
default:
}
});
}
You'll need to lift the state up. The whole loading concept is abstracted by the FutureBuilder, but because you don't want to do one-time-loading, that's not the right abstraction layer for you. That means, you'll need to implement the "waiting for the future to complete and then build stuff" yourself in order to be able to trigger the loading repeatedly.
For example, you could put everything in a StatefulWidget and have isLoading, data and error properties and set these correctly.
Because this is probably a recurring task, you could even create a widget to handle that for you:
import 'package:flutter/material.dart';
class Reloader<T> extends StatefulWidget {
final Future<T> Function() loader;
final Widget Function(BuildContext context, T data) dataBuilder;
final Widget Function(BuildContext context, dynamic error) errorBuilder;
const Reloader({
Key key,
this.loader,
this.dataBuilder,
this.errorBuilder,
}) : super(key: key);
#override
State<StatefulWidget> createState() => ReloaderState<T>();
static of(BuildContext context) =>
context.ancestorStateOfType(TypeMatcher<ReloaderState>());
}
class ReloaderState<T> extends State<Reloader<T>> {
bool isLoading = false;
T data;
dynamic error;
#override
void initState() {
super.initState();
reload();
}
Future<void> reload() async {
setState(() {
isLoading = true;
data = null;
error = null;
});
try {
data = await widget.loader();
} catch (error) {
this.error = error;
} finally {
setState(() => isLoading = false);
}
}
#override
Widget build(BuildContext context) {
if (isLoading) {
return Center(child: CircularProgressIndicator());
}
return (data != null)
? widget.dataBuilder(context, data)
: widget.errorBuilder(context, error);
}
}
Then, you can just do
Reloader(
loader: apiCall,
dataBuilder: (context, data) {
return DataWidget(things: data);
},
errorBuilder: (context, error) {
return ...
RaisedButton(
onPressed: () => Reloader.of(context).reload(),
child: Text(reload),
),
...;
},
)
Also, I wrote a package for that case which has some more features built-in and uses a controller-based architecture instead of searching the state through Reload.of(context): flutter_cached
With it, you could just do the following:
In a state, create a CacheController (although you don't need to cache things):
var controller = CacheController(
fetcher: apiCall,
saveToCache: () {},
loadFromCache: () {
throw 'There is no cache!';
},
),
Then, you could use that controller to build a CachedBuilder in the build method:
CachedBuilder(
controller: controller,
errorScreenBuilder: (context, error) => ...,
builder: (context, items) => ...,
...
),
When the reload button is pressed, you can simply call controller.fetch(). And you'll also get some cool things like pull-to-refresh on top.
As you can see in first part I'm checking that a certain value contains in a document from Firestore and returns a boolean value. Now I'm calling that function in a build and based on that return value I'm changing a chip color (second part).
Now the problem is maybe because I'm calling it in a build function so its being called continuously and on that build and it costing me a ton of reads in Firestore or maybe the function is inefficient. How can I write this more efficiently?
checkAtt(String name, id , date) async{
var ref = _db.collection('subjects').document(id).collection('Att').document(date);
var docref = await ref.get();
return docref.data.containsKey(name)
?true
:false;
}
class PresentChip extends StatefulWidget {
final candidate;
PresentChip(
this.candidate, {
Key key,
}) : super(key: key);
#override
_PresentChipState createState() => _PresentChipState();
}
class _PresentChipState extends State<PresentChip> {
var isSelected = false;
var c = false;
#override
Widget build(BuildContext context) {
final SelectSub selectSub = Provider.of<SelectSub>(context);
final Date date = Provider.of<Date>(context);
db.checkAtt(widget.candidate, selectSub.selectsub, date.datenew).then((result){
print(result);
setState(() {
c = result;
});
});
return Container(
child: ChoiceChip(
label: Text('Present'),
selected: isSelected,
onSelected: (selected) {
db.gibAtt(
widget.candidate, selectSub.selectsub, date.datenew.toString());
setState(() {
isSelected = selected;
});
},
backgroundColor: !c ?Colors.red :Colors.green ,
selectedColor: !c ?Colors.red :Colors.green ,
));
}
}
Assuming you only want to read once from firestore, you need a FutureBuilder.
return Container(
child: FutureBuilder(
future: db.checkAtt(widget.candidate, selectSub.selectsub, date.datenew),
builder: (context, snapshot) {
if(snapshot.hasData)
return ChoiceChip(
...
backgroundColor: !snapshot.data ?Colors.red :Colors.green,
selectedColor: !snapshot.data ?Colors.red :Colors.green,
);
//Return another widget if the future has no data
return Text('Future has no data');
}
)
);
If you need your UI to react to changes from firestore, use a StreamBuilder.
You can remove the following bloc from your build method:
db.checkAtt(widget.candidate, selectSub.selectsub, date.datenew).then((result){
print(result);
setState(() {
c = result;
});
});