Why flutter dynamic switch does not changing? - flutter

I am trying to change switch, which are created dynamically by using future builder. it does not changing. Following is my script
bool disabledLanguage = false;
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (context, index){
var postId = snapshot.data[index].id;
var postTranslate = snapshot.data[index].translate;
disabledLanguage = (postId == getLanguageId) ? true : false;
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: _singleSwitch(postId, postTranslate, disabledLanguage),
);
}
)
and below is function in which I added switch functionality
_singleSwitch(postId, postTranslate, disabledLanguage){
return Row(
children: [
Text(
postTranslate,
style: TextStyle(
color: Color(0xff41453c),
fontSize: 16,
fontWeight: FontWeight.w700
)
),
Switch(
value: disabledLanguage,
onChanged: (value){
setState(() {
disabledLanguage=value;
});
},
activeTrackColor: Colors.lightGreenAccent,
activeColor: Colors.green,
)
],
);
}
When I try to switch any language, it does not changing, i mean if english by default enabled, when I click on it, it should change to disable. It is not happening. Can someone please help me. Thank you

You need to set postId value to getLanguageId instead of setting disabledLanguage.
Copy the below code to DartPad and try. Go through the code once, You'll understand.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_State createState() => _State();
}
class _State extends State<MyApp> {
var getLanguageId = 1;
#override
Widget build(BuildContext context) {
var data = [
{
'id': 1,
'translate': 'Eng',
},
{
'id': 2,
'translate': 'Kan',
}
];
return Scaffold(
appBar: AppBar(
title: Text('Flutter Tutorial - googleflutter.com'),
),
body: getListView(data)
);
}
getListView(data) {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (context, index) {
var postId = data[index]['id'];
var postTranslate = data[index]['translate'];
var isdisabled = (postId == getLanguageId) ? true : false;
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: _singleSwitch(postId, postTranslate, isdisabled),
);
});
}
_singleSwitch(postId, postTranslate, isdisabled) {
print('#Im here for' + postTranslate);
return Row(
children: [
Text(postTranslate,
style: TextStyle(
color: Color(0xff41453c),
fontSize: 16,
fontWeight: FontWeight.w700)),
Switch(
value: isdisabled,
onChanged: (value) {
setState(() {
getLanguageId = postId;
});
},
activeTrackColor: Colors.lightGreenAccent,
activeColor: Colors.green,
)
],
);
}
}

The problem is here:
setState(() {
disabledLanguage=value;
});
disabledLanguage is a function argument. You are updating a function argument and not an instance variable!
Declare an instance variable disabledLanguage,
and remove disabledLanguage from parameter list so that the value is persisted after the function call.
It should look something like this:
class MyWidgetState extends State<MyWidget> {
...
...
bool disabledLanguage = true; //<-- Declared as instance variable
_singleSwitch(postId, postTranslate){ //<-- Removed from parameter list
return Row(
children: [
Text(
postTranslate,
style: TextStyle(
color: Color(0xff41453c),
fontSize: 16,
fontWeight: FontWeight.w700
)
),
Switch(
value: disabledLanguage,
onChanged: (value){
setState(() {
disabledLanguage=value;
});
},
activeTrackColor: Colors.lightGreenAccent,
activeColor: Colors.green,
)
],
);
}
}

Related

BLoC emit not updating the state in UI

I have build the screen with Appbar,Tabs and TabView, the Appear has TextField, every time text changes, the control is going inside the BLoC's emit but never received at UI. Please find the code below, I appreciate your any help.
class Buildings extends StatefulWidget {
const Buildings({super.key});
#override
State<StatefulWidget> createState() => _BuildingState();
}
class _BuildingState extends State<Buildings> {
late TextEditingController _searchController;
late NearbybuildingsBloc nearbybuildingsBloc;
late AllbuildingsBloc allbuildingsBloc;
late String _searchText='';
late BuildContext allBuildingContext;
#override
void initState() {
nearbybuildingsBloc = NearbybuildingsBloc();
allbuildingsBloc = AllbuildingsBloc();
_searchController = TextEditingController();
_searchController.addListener(() {
_searchText = _searchController.text;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<NearbybuildingsBloc>(create: (context) => nearbybuildingsBloc..add(NearbyBuildings())),
BlocProvider<AllbuildingsBloc>(create: (context) => allbuildingsBloc..add(AllBuildings()))
],
child: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
backgroundColor: AppColor.bt_indigo,
title: TextField(
onChanged: (value) {
allbuildingsBloc.add(
SearchbuildingLoadingEvent(query: _searchText),
);
},
style: const TextStyle(color: Colors.white),
controller: _searchController,
decoration: InputDecoration(
suffixIcon: _searchText.isEmpty
? null
:IconButton(
icon: const Icon(Icons.clear),
color: Colors.white,
onPressed: () => _searchController.clear(),
),
isDense: true,
hintText: 'Building name',
hintStyle: const TextStyle(color: AppColor.border_color),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
width: 2,
color: AppColor.text_editor_background),
borderRadius: BorderRadius.all(
Radius.circular(10.0)),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
width: 2,
color: AppColor.text_editor_background),
borderRadius: BorderRadius.all(
Radius.circular(10.0)),
),
fillColor: AppColor.text_editor_background,),
),
actions: [
NamedIcon(
text: '',
iconData: Icons.filter_list_alt,
onTap: () {
/*showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext buildContext) {
return StatefulBuilder(
builder: (buildContext, state) {
return _openFilterCategoryDialog(
filterCategories, context, state);
});
});*/
},
),
NamedIcon(
text: '',
iconData: Icons.help,
notificationCount: 1,
onTap: () {
},
)],
bottom:
_searchText.isEmpty? TabBar(
tabs: [
Tab(text: 'Near by'),
Tab(text: 'All Buildings')
],
):null,
),
body: _searchText.isEmpty?TabBarView(
children: [
// Nearby buildings
BlocBuilder<NearbybuildingsBloc, NearbybuildingsState>(
builder: (context, state) {
if( state is NearbybuildingsLoading){
return const Center(child: CircularProgressIndicator(
color: AppColor.bt_indigo,
));
}
if(state is NearbybuildingsGpsPerm){
return row.getGPS(context);
}
if (state is NearbybuildingsLoaded) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Showing ${state.nearBuildingList.length.toString()} buildings ", style: const TextStyle(
fontSize: 13,
color: AppColor.bt_indigo,
fontFamily: 'BT Regular'),
),
Expanded(
child: ListView.builder(
itemCount: state.nearBuildingList.length,
itemBuilder: (BuildContext context, int position) {
return row.getRow(state.nearBuildingList[position], context);
}),
)
],
);
}
return const Text("Failed to load buildings");
},
),
// All buildings
BlocBuilder<AllbuildingsBloc, AllbuildingsState>(
builder: (context, state) {
if( state is AllbuildingsError){
return const Text("Failed to load buildings");
}
if (state is AllbuildingsLoaded) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Showing ${state.all.length.toString()} buildings ", style: const TextStyle(
fontSize: 13,
color: AppColor.bt_indigo,
fontFamily: 'BT Regular'),
),
Expanded(
child: ListView.builder(
itemCount: state.all.length,
itemBuilder: (BuildContext context, int position) {
return row.getRow(state.all[position], context);
}),
)
],
);
}
return const Text("Failed to load buildings");
},
),
],
):
// All buildings
BlocBuilder<AllbuildingsBloc, AllbuildingsState>(
builder: (context, state) {
if( state is SearchbuildingLoadingState){
return const Center(child: CircularProgressIndicator(
color: AppColor.bt_indigo,
));
}
if (state is SearchBuildingLoadedState) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Showing ${state.searchresults.length.toString()} buildings ", style: const TextStyle(
fontSize: 13,
color: AppColor.bt_indigo,
fontFamily: 'BT Regular'),
),
Expanded(
child: ListView.builder(
itemCount: state.searchresults.length,
itemBuilder: (BuildContext context, int position) {
return row.getRow(state.searchresults[position], context);
}),
)
],
);
}
return const Text("Failed to load buildings");
},
),
// This trailing comma makes auto-formatting nicer for build methods.
),
),
);
}
}
State
class SearchBuildingLoadedState extends AllbuildingsState{
List<Building> searchresults;
SearchBuildingLoadedState({required this.searchresults});
#override
List<Object> get props {
return [searchresults];
}
}
Event
class SearchbuildingLoadingEvent extends AllbuildingsEvent {
final String query;
SearchbuildingLoadingEvent({required this.query});
#override
List<Object> get props {
return [query];
}
}
BLoC
class AllbuildingsBloc extends Bloc<AllbuildingsEvent, AllbuildingsState> {
AllbuildingsBloc() : super(AllbuildingsInitial()) {
on<SearchbuildingLoadingEvent>(_onSearchBuilding);
}
Future<void> _onSearchBuilding(SearchbuildingLoadingEvent event, Emitter<AllbuildingsState> emit) async {
emit(SearchbuildingLoadingState());
if(event.query.isNotEmpty || event.query.isNotEmpty) {
final database = await $FloorAppDataBase
.databaseBuilder('bootcamp-instagram-project.db')
.build();
final buildingDao = database.buildingDao;
var getBuilding = await buildingDao.getSearchBuildings(
event.query);
if (getBuilding != null) {
emit(SearchBuildingLoadedState(searchresults: getBuilding));
} else {
emit(SearchbuildingNoDataState());
}
}else{
emit(SearchbuildingNoDataState());
}
}
}
Please check the tab bar view to have three bloc builders where you provided,
whether you can try with
changing the tab controller
DefaultTabController(
length: 3,
child: Scaffold(
and add 1 more tabbar here
TabBar(
tabs: [
Tab(text: 'Near by'),
Tab(text: 'All Buildings')
],
)

Flutter images loading issue after reload display but not first time

Trying to display cover image based on bookList which is RxList from getX but after hot reload it displays that particular book only. Need to load it instantly as soon as the page load.
Tried setstate too but UI is not updating the first time. Please help. Thank you.
Below is the screenshot images not getting displayed first time but after hot restart.
Here images problem:
HorizontalGrid Widget which based on horizontalGridTitle open the specific book
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:matab/constants/constants.dart';
import 'package:matab/controllers/cart_book_controller.dart';
import 'package:matab/models/book.dart';
import 'package:matab/ui/pages/styles.dart';
import '../../../controllers/book_controller.dart';
import '../../general_widgets/book_magazine_tapbar.dart';
import '../../general_widgets/my_network_image.dart';
import '../item_details/book_details_page.dart';
class HorizontalGrid extends StatefulWidget {
final String horizontalGridTitle;
const HorizontalGrid({Key? key, required this.horizontalGridTitle})
: super(key: key);
#override
State<HorizontalGrid> createState() => _HorizontalGridState();
}
class _HorizontalGridState extends State<HorizontalGrid> {
late RxList<dynamic> bookList;
final BookController bookController = Get.find(tag: 'bookController');
final CartBookController cartBookController =
Get.find(tag: 'cartBookController');
#override
void initState() {
bookList = bookController.getSpecifiedBooks(widget.horizontalGridTitle);
super.initState();
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(20.0, 20, 20, 20),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.horizontalGridTitle.tr,
style: TextStyle(
color: secondaryColor,
fontSize: 20,
fontWeight: FontWeight.bold),
),
GestureDetector(
onTap: () {
Get.to(() => BookMagazineTapbar(
titleText: widget.horizontalGridTitle,
));
},
child: Row(
children: [
Text(
"seeAll".tr,
style: TextStyle(
color: mainColor,
fontSize: 20,
fontWeight: FontWeight.bold),
),
Icon(Icons.arrow_forward, color: mainColor)
],
),
)
],
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 30, 20, 10),
child: Column(
children: [
SizedBox(
width: double.infinity,
height: 140,
child: GridView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
),
itemCount: bookList.length,
itemBuilder: (context, index) {
return SizedBox(
child: GestureDetector(
onTap: () {
bookList[index].coverImage;
Get.to(BookDetailsPage(
type: Constants.bookConst,
index: index,
cartButtonText: 'addToCart'.tr,
));
},
child: Obx(
() => CachedNetworkImage(
key: widget.key,
imageUrl: bookList[index].coverImage,
),
)));
},
),
)
],
)),
],
),
);
}
}
This is called based on
getSpecifiedBooks(String title) {
// debugPrint("requested book is $title");
if (title == Constants.recommendedConst) {
debugPrint("recommended requested");
return recommendedBooksList;
} else if (title == Constants.latestByMatabConst) {
return latestBooksList;
} else if (title == Constants.onSaleConst) {
return onSaleList;
} else if (title == Constants.favoritesConst) {
return favoriteBookList;
} else if (title == Constants.allItemsConst) {
return bookList;
} else if (title == Constants.searchConst) {
return bookList;
} else {
return [];
}
}
Afterwards data coming from firebase perfectly in latestBooks:
RxList<dynamic> latestBooks = await databaseService.getLatestBooks();
Future<RxList> getLatestBooks() async {
RxList books = [].obs;
RxInt itemCount = 1.obs;
Query<Map<String, dynamic>> booksQuery = firestoreInstance
.collection('books')
.orderBy('createdAt', descending: true)
.limit(10);
await booksQuery.get().then(
(value) {
for (var doc in value.docs) {
Book bookItem = Book(
author: doc['author'],
title: doc['title'],
price: doc['price'].toDouble(),
coverImage: doc['coverImage'],
bookID: doc.id,
ratings: doc['ratings'].toDouble(),
description: doc['description'],
discountPercentage: doc['discountPercentage'],
isLiked: false,
itemCount: itemCount,
);
books.add(bookItem);
}
},
);
return books;
}
Finally latestBooks is assigned to latestBooksList:
final latestBooksList = [].obs;
latestBooksList.assignAll(latestBooks);

Flutter/Dart - Dynamically controlling CheckboxListTile inside Listview.Builder

I am trying to create a page that lists a number of questions. For each question I will have different answers. At the minute, whenever I select an answer, the same option on the other questions are selected at the same time. How can I avoid this and make each question its own entity? Here is my code:
class QuestionScreen extends StatefulWidget {
#override
_QuestionScreenState createState() => _QuestionScreenState();
}
class _QuestionScreenState extends State<QuestionScreen> {
List<bool> _isChecked;
final Map<String, Map> questions = {"milk comes from what animal":
{"horse": false, "monkey": false, "frog": false, "cow": true},
"what colour is the sea?":
{"red": false, "green": false, "blue": true, "yellow": false}};
#override
void initState() {
super.initState();
_isChecked = List<bool>.filled(questions.values.length, false);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Text("questions page"),
),
Expanded(
child: new ListView.builder(
itemCount: questions.keys.length,
itemBuilder: (BuildContext ctxt, int questionTitleIndex) {
return Padding(
padding: const EdgeInsets.all(24.0),
child: Container(
height: MediaQuery.of(context).size.height * 0.45,
decoration: BoxDecoration(
color: OurTheme().ourCanvasColor,
borderRadius: BorderRadius.circular(25),
),
child: Column(
children: [
Text(
questions.keys.toList()[questionTitleIndex],
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.w800),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.builder(
shrinkWrap: true,
itemCount: questions.values
.toList()[questionTitleIndex]
.keys
.length,
itemBuilder:
(BuildContext ctxt, int questionAnswersIndex) {
return Container(
decoration: BoxDecoration(border: Border.all()),
child: CheckboxListTile(
title: Text(
"${questionAnswersIndex + 1}. ${questions.values.toList()[questionTitleIndex].keys.toList()[questionAnswersIndex]}",
style: TextStyle(
color: Colors.white, fontSize: 16),
),
value: _isChecked[questionAnswersIndex],
controlAffinity:
ListTileControlAffinity.platform,
onChanged: (bool value) {
setState(
() {
_isChecked[questionAnswersIndex] =
value;
},
);
},
activeColor: OurTheme().ourCanvasColor,
checkColor: Colors.white,
),
);
},
),
)
],
),
),
);
},
),
)
],
),
);
}
}
I see a few problems here.
First since your need to maintain the answer for each question in your _isChecked. It would make more sense to make it a Map<String, String> instead of a List<bool>.
Inside it, the key will be the question title and the value will be the selected option title.
So, inside your initState, you will initiate it liket this.
Map<String, String> _isChecked = {}; // Initializing with empty map
#override
void initState() {
super.initState();
widget.questions.keys.forEach((key) {
// For each question we first set the answer as "". means nothing selected.
_isChecked[key] = "";
// We then loop over the options of that question to see if any option was already true and set it as the initial answer.
for (MapEntry entry in widget.questions[key]!.entries) {
if (entry.value) _isChecked[key] = entry.key;
}
});
}
After this, you just have to change the places in your code where you were using the _isChecked variable.
Here is the link to the full working code. Just replace all your code with the code in the link.
Result.

Handle the list of dynamic checkboxes when the widget is added on button click in flutter

When clicking the add button, the same widget is replicated. The widget contains the list of checkboxes that are multi selectable. I am able to replicate the widget but I got problem to handle the checkboxes according to the index of the widget. In image below the checkbox checked state is replicated along with the new add widget.
I have implemented as follows:
Build the widget according to the addbutton click
ListView.builder(
itemCount: counting,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (_, index) {
return _buildLayout(context, index);
});
//counting is number of **blueplus** icon is clicked
Widget _buildLayout(BuildContext context, int i) {
return Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
addContainer,
style: TextStyle(color: Colors.blueGrey),
),
Container(
width: 64.0,
alignment: Alignment.center,
child: IconButton(
onPressed: () => {i == 0 ? addRow(i) : deleteRow(i)},
icon: Icon(
i == 0
? Icons.add_circle_outline
: Icons.remove_circle_outline,
color: i == 0 ? Theme.of(context).primaryColor : Colors.red,
)),
),
],
),
_buildCheckBoxes()
],
);
}
Widget _buildCheckBoxes() {
return
Container(
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
InkWell(
onTap: () {
showHide();
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
productionmareketway,
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
),
showHidee
? Icon(Icons.keyboard_arrow_up)
: Icon(Icons.keyboard_arrow_down)
])),
SizedBox(
width: 20,
),
showHidee
? ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: widget.responseMarket.length,
itemBuilder: (ctx, i) {
return _buildSingleCheckBox(
context,
widget.responseMarket[i].name,
widget.responseMarket[i].isChecked,
widget.responseMarket[i].id,
widget.responseMarket[i].identifier,
i);
})
: Container()
])
);
}
Widget _buildSingleCheckBox(BuildContext context, String name, bool isChecked,
int i, String identifier, int j) {
return Container(
child: new CheckboxListTile(
title: new Text(name),
value: isChecked,
activeColor: Theme.of(context).primaryColor,
checkColor: Colors.white,
onChanged: (bool value) {
setState(() {
widget.responseMarket[i].isChecked = value;
print(value);
print(i);
widget._onChecked(
value,
widget.responseMarket[i].id,
widget.responseMarket[i].name,
widget.responseMarket[i].identifier,
counting);
});
},
),
);
}
Add and delete widget function
addRow(int i) {
setState(() {
counting = counting + 1;
});
}
deleteRow(int i) {
setState(() {
counting = counting - 1;
});
}
My callback function
onMarketChecked(var value, int i, String name, String identifier, int j) {
setState(() {
if (responseMarket[i].isChecked == true) {
nonMarketRepated.add(name);
} else {
nonMarketRepated.remove(responseMarket[i].name);
}
});
}
This issue is because you control all replica by counting and widget.responseMarket. If you want all replicas work individually, you need to Replica it actually.
I suggest to create a new StatefulWidget to replace _buildSingleCheckBox() & _buildCheckBoxes() function. I also put showHidee inside it.
class CheckBoxesWidget extends StatefulWidget {
final responseMarket;
CheckBoxesWidget({this.responseMarket, Key key}) : super(key: key);
#override
_CheckBoxesWidgetState createState() => _CheckBoxesWidgetState();
}
class _CheckBoxesWidgetState extends State<CheckBoxesWidget> {
bool showHidee;
#override
void initState() {
showHidee = true;
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
InkWell(
onTap: () {
showHide();
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'productionmareketway',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
showHidee
? Icon(Icons.keyboard_arrow_up)
: Icon(Icons.keyboard_arrow_down)
],
),
),
SizedBox(
width: 20,
),
if (showHidee)
Column(
children: widget.responseMarket
.map(
(e) => CheckboxListTile(
title: Text(e.name),
value: e.isChecked,
activeColor: Theme.of(context).primaryColor,
checkColor: Colors.white,
onChanged: (bool value) {
setState(() {
e.isChecked = value;
});
},
),
)
.toList(),
),
],
),
);
}
void showHide() {
setState(() {
showHidee = !showHidee;
});
}
}
Second, beyond control the replica by counting, you should use a List to store all replica of responseMarket in the original class.
List<List<Market>> responseMarkets;
#override
void initState() {
responseMarkets = [widget.responseMarket];
super.initState();
}
...
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: responseMarkets.length,
itemBuilder: (_, index) {
return _buildLayout(context, index);
});
}
...
Widget _buildLayout(BuildContext context, int i) {
...
// replace _buildCheckBoxes() with this line
CheckBoxesWidget(responseMarket: responseMarkets[i],),
...
}
Finally, you have to modify the addRow, deleteRow function. Each time create a new ResponseMarkets Object.
addRow(int i) {
setState(() {
responseMarkets.add(responseMarkets[0]
.map((e) => ResponseMarkets(
id: e.id,
name: e.name,
identifier: e.identifier,
isChecked: e.isChecked,
))
.toList());
});
}
deleteRow(int i) {
setState(() {
responseMarkets.removeAt(i);
});
}

ListView.builder itemCount not updating inside StreamBuilder

I have a flutter app where a list is generated with ListView.Builder, and where the itemCount is the number of documents in a firestore collection.
When I add a document to the collection I can see that the value snapshot.data.documents.length changes by printing it, but the the itemCount does not change, which causes the following error:
Invalid value: Not in range 0..17, inclusive: 18
Here is a GitHub thread I created about the same problem: https://github.com/flutter/flutter/issues/39206
And here is the code for the page in question, and the list i'm getting the error from is the one in the StreamBuilder close to the bottom:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
/*
Visar kontaktinformation
*/
class Contact extends StatefulWidget {
#override
_ContactState createState() => _ContactState();
}
class _ContactState extends State<Contact> {
_hasDesc(desc) {
if (desc == '') {
return false;
} else {
return true;
}
}
String sortby = 'namn';
bool decending = false;
var showInfo;
TextEditingController controller = new TextEditingController();
String filter;
#override
void initState() {
super.initState();
controller.addListener(() {
setState(() {
filter = controller.text.toLowerCase(); //Gör om till gemener för att inte vara skiftlägeskänslig
});
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
Widget _personer(context, DocumentSnapshot document, index) {
//Skapar lista från databasen med kontaktinformation
//Denna lista måste vara i rätt ordning i databasen
final info = List<String>.from(document['info']);
//Om sökrutan är tom visas alla personer, om inte så visas bara de som matchar filtret
if (filter == null ||
filter == '' ||
document['namn'].toLowerCase().contains(filter) ||
document['beskrivning'].toLowerCase().contains(filter)) {
return Column(
children: <Widget>[
ListTile(
onTap: () {
setState(() {
for (int i = 0; i < showInfo.length; i++) {
if (i != index) {
showInfo[i] = false; // för att enbart ett kort ska vara expanderat åt gången
}
}
showInfo[index] = !showInfo[index];
});
},
title: Padding(
padding: const EdgeInsets.fromLTRB(0, 4, 0, 4),
child: Column(
children: <Widget>[
Text(
document['namn'],
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline,
),
Visibility(
visible: _hasDesc(document['beskrivning']),
child: Text(
document['beskrivning'],
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.subtitle.copyWith(fontSize: 20),
),
),
Visibility(
visible: showInfo[index],
child: ListView.builder(
//Bygger lista med kontaktinfo för varje person
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: info.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(top: 5),
child: ButtonTheme(
child: GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: info[index]));
//skapar snackbar
final copiedTextSnackBar = SnackBar(
content: Text('"${info[index].replaceAll('/', '')}" har kopierats'),
action: SnackBarAction(
label: 'Okej',
onPressed: () => Scaffold.of(context).hideCurrentSnackBar(),
),
);
//Stänger eventuell snackbar och viar en ny
Scaffold.of(context).hideCurrentSnackBar();
Scaffold.of(context).showSnackBar(copiedTextSnackBar);
},
child: Text(
info[index].replaceAll('/', '\n'),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.body1.copyWith(
fontSize: 16,
color: Color(0xff555555),
),
),
),
),
);
},
),
),
],
),
),
),
Divider(
color: Colors.black,
),
],
);
} else {
return SizedBox(
height: 0, //Visar ingenting om filtret inte stämmer
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Row(
children: <Widget>[
Flexible(
child: TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(20, 10, 20, 10),
hintText: 'Sök',
border: InputBorder.none,
),
controller: controller,
),
),
Text('Sortera: ', style: TextStyle(fontSize: 16, color: Color(0xff555555)),),
DropdownButton<String>(
value: sortby,
onChanged: (String newValue) {
setState(() {
sortby = newValue;
});
},
items: [
DropdownMenuItem(
value: 'namn',
child: Text('Namn'),
),
DropdownMenuItem(
value: 'beskrivning',
child: Text('Titel'),
)
]
),
Stack(
children: <Widget>[
Visibility(
visible: decending,
child: IconButton(
icon: Icon(Icons.arrow_upward),
onPressed: () => setState(() {
decending = false;
}),
),
),
Visibility(
visible: !decending,
child: IconButton(
icon: Icon(Icons.arrow_downward),
onPressed: () => setState(() {
decending = true;
}),
),
)
],
)
],
),
Expanded(
child: Container(
child: StreamBuilder(
stream: Firestore.instance.collection('kontakt').orderBy(sortby, descending: decending).snapshots(), //Hämtar data från databas
builder: (context, snapshot) {
//För att inte skriva över existerande lista:
if (showInfo == null) {
//Listan genereras här för att slippa kalla på databasen två ggr
showInfo = List.generate(snapshot.data.documents.length, (index) => false);
}
if (!snapshot.hasData) {
return Container();
} else if (snapshot.hasData) {
print(snapshot.data.documents.length);
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_personer(context, snapshot.data.documents[index], index),
);
} else {
return Center(
child: Text("Error"),
);
}
},
),
),
),
],
),
);
}
}
I believe it is because of this line:
if (showInfo == null) {
showInfo = List.generate(snapshot.data.documents.length, (index) => false);
}
the showInfo List only gets updated once due to the condition provided. at first, showInfo is null so it gets updated. On consecutive rebuilds, the List doesn't get updated because it is not equal to null anymore. try removing the if condition and see what happens.