I am trying to make a product management application. In this application, I divided the products into categories. And in order to reach the products, it is necessary to choose a category from the main page.
I'm saving the user-selected category with GetX and on the Listing Products page, I'm trying to list the products by making a category filter from Firebase Firestore.
But I am having a problem. When the application is opened, when any category is entered, the products of the category entered normally are listed. But when you leave the product listing page and enter a different page, the list of the first entered product is displayed.
My guess is that the source of this problem is the page not refreshing. Can you help me to solve this problem?
Codes:
HomePage.dart(page listing product categories):
return ListTile(
leading: Categories[index].icon,
title: Text(Categories[index].name, style: TextStyle(fontSize: 18, fontFamily: "Montserrat"),),
onTap: () {
if (Categories[index].name == "Meyve & Sebze") {
_temporaryHoldOfInformation.temporaryInformationProducts.value = ["Meyve & Sebze", "FruitAndVegetable"];
// Item 0 of the list is for the AppBar on the product listings page. The 1st item of the list is to be able to filter category from Firebase Firestore.
Get.to(ProductsPage());
}
else if (Categories[index].name == "Fırın") {
_temporaryHoldOfInformation.temporaryInformationProducts.value = ["Fırın", "Bakery"];
Get.to(ProductsPage());
}
else if (Categories[index].name == "Temel Gıda") {
_temporaryHoldOfInformation.temporaryInformationProducts.value = ["Temel Gıda", "Food"];
Get.to(ProductsPage());
}
else if (Categories[index].name == "Süt Ürünleri") {
_temporaryHoldOfInformation.temporaryInformationProducts.value = ["Süt Ürünleri", "DairyProducts"];
Get.to(ProductsPage());
}
else if (Categories[index].name == "Atıştırmalık") {
_temporaryHoldOfInformation.temporaryInformationProducts.value = ["Atıştırmalık", "Snacks"];
Get.to(ProductsPage());
}
else if (Categories[index].name == "Su & içecek") {
_temporaryHoldOfInformation.temporaryInformationProducts.value = ["Su & içecek", "Waters"];
Get.to(ProductsPage());
}
else if (Categories[index].name == "Teknoloji") {
_temporaryHoldOfInformation.temporaryInformationProducts.value = ["Teknoloji", "Tech"];
Get.to(ProductsPage());
}
},
);`
temporary_hold_of_information.dart (To keep user selected category from HomePage.dart - GetX):
import 'package:get/get.dart';
class TemporaryHoldOfInformation extends GetxController {
RxList temporaryInformationProducts = RxList([]);
}
ProducsPage.dart:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:market_cebimde/paginations/temporary_hold_of_information.dart';
class ProductsPage extends StatefulWidget {
#override
State<ProductsPage> createState() => _ProductsPageState();
}
TemporaryHoldOfInformation _temporaryHoldOfInformation = Get.put(TemporaryHoldOfInformation());
String _productName = _temporaryHoldOfInformation.temporaryInformationProducts.value[0];
String _productCategory = _temporaryHoldOfInformation.temporaryInformationProducts.value[1];
FirebaseFirestore _firestore = FirebaseFirestore.instance;
FirebaseAuth _auth = FirebaseAuth.instance;
class _ProductsPageState extends State<ProductsPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blueAccent[700],
title: Text("${_productName.toString()}", style: const TextStyle(color: Colors.white),),
centerTitle: true,
elevation: 0,
),
body: SingleChildScrollView(
child: Column(
children: [
StreamBuilder(
stream: _firestore.collection("products").where("category", isEqualTo: _productCategory).snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1.5,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
return Container(
child: Column(
children: [
Container(
width: double.infinity,
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
image: DecorationImage(
image: NetworkImage(snapshot.data!.docs[index].data()["productImageLink"]),
fit: BoxFit.cover,
),
),
),
Text(snapshot.data!.docs[index].data()["productName"]),
Text(snapshot.data!.docs[index].data()["productCost"]),
Text(snapshot.data!.docs[index].data()["category"]),
Text(snapshot.data!.docs[index].data()["productWeight"]),
],
),
);
},
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
],
),
),
);
}
}
The page that is selected in HomePage.dart, saved with GetX, filtered in Firebase Firestore by category, and the products are listed. The problem is on this page. When you exit-enter the page, the products of the category entered are listed, not the products of the entered category. So the page is not updating.
I have to restart the application for the products according to the selected category to arrive.
I would be glad if you help. Thanks in advance for your help :)
Related
So I'm relatively new to flutter and I've been trying to dynamically add Sections(TextFormFields) that are represented in a form that has Form.Helper as its child and in the process to get the saveAndValidate method to work i had to use a GlobalKey to be able to access the currentState of its so i can validate and save user input and such, but whenever i try add another Section to the screen it display this error massage
════════ Exception caught by widgets library ═══════════════════════════════════
Multiple widgets used the same GlobalKey.
════════════════════════════════════════════════════════════════════════════════
here is the code I wrote and I'd appreciate any help in solving this error please.
#1- the code for the model I used:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AddCourse with ChangeNotifier {
String? sectionName;
List<String>? sections;
List<dynamic>? addVids;
AddCourse({this.sectionName, this.sections, this.addVids});
/*where we save our values later to push them to firbase/database*/
Map<String, dynamic> toJson() {
final Map<String, dynamic> sectionData = <String, dynamic>{};
sectionData['Section #'] =
sections; // where current section number is saved and is stored dynamicly and updates as user adds more or less sections.
sectionData['Section Name'] =
sectionName; // where the input of the textformfield is saved and to be later pushed to the database and also is stored in a list so it can hold multiple section names as such.
return sectionData;
}
/* this is another model data for a functionality thats not implemented yet*/
Map<dynamic, dynamic> toJson2() {
final Map<dynamic, dynamic> vidData = <dynamic, dynamic>{};
vidData['Videos #'] = addVids;
return vidData;
}
}
#2 this the code for the form I created
import 'package:flutter/material.dart';
import 'package:snippet_coder_utils/FormHelper.dart';
import '../provider/course_add_model.dart';
class CourseCardBody extends StatefulWidget {
const CourseCardBody({
Key? key,
}) : super(key: key);
#override
State<CourseCardBody> createState() => _CourseCardBodyState();
}
class _CourseCardBodyState extends State<CourseCardBody> {
/* this is where i set up my global key that has the type of GlobalKey<FormState>*/
/*State associated with a [Form] widget. such as textformfields/forms/textfields..etc// the use of the (FormState) is to be able to Access the Functions "save"/"validate"/"reset" as to use them with forms/textformfields that you want to validate thier input or save it*/
GlobalKey<FormState> globalkey = GlobalKey();
AddCourse coursesModel = AddCourse();
#override
void initState() {
super.initState();
coursesModel.sections = List<String>.empty(growable: true);
coursesModel.sections?.add("");
// adds empty sections to the list of sections when the add button is used
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add Courses'),
centerTitle: true,
),
body: ListView.separated(
shrinkWrap: true,
physics: const ScrollPhysics(),
itemBuilder: ((context, index) => Column(
children: [
_uiWidget(index),
Center(
// the submit button here needs some work to only be show once but for now sorry for this annoying button.
child: FormHelper.submitButton('Save', () {
if (validateAndSave()) {
print(coursesModel.toJson());
}
}),
),
],
)),
separatorBuilder: ((context, index) => const Divider()),
itemCount: coursesModel.sections!.length,
),
);
}
Widget _uiWidget(index) {
/* this form here is the parent of form fields/Formhelper widgets as seen below*/
return Form(
/* -- note here--
if we use a UniqueKey()
instead of our globalkey
here and comment the ValidateAndSave() function here
the form will work in terms of adding and removing sections
but we won't be able to either
save content/input of the user in the fields or
either validate
them so that sucks. */
/*this form is where global key is first used*/
key: globalkey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionsContainer(index),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
flex: 1,
fit: FlexFit.loose,
child: FormHelper.inputFieldWidgetWithLabel(
context,
'Add Section$index',
'',
'Section Title',
(onValidate) {
if (onValidate.isEmpty) {
return 'section ${index + 1} name cant be empty';
}
return null;
},
(onSavedVal) {
coursesModel.sections![index++] = index.toString();
onSavedVal = index;
},
onChange: (onChangedval) {
coursesModel.sectionName = onChangedval;
},
initialValue: coursesModel.sectionName ?? "",
borderColor: Colors.black,
borderFocusColor: Colors.black,
fontSize: 14,
labelFontSize: 14,
validationColor: Colors.redAccent,
),
),
Visibility(
visible: index == coursesModel.sections!.length - 1,
child: IconButton(
onPressed: () {
addEmailControl();
},
icon: const Icon(
Icons.add_circle,
color: Colors.greenAccent,
),
),
),
Visibility(
visible: index > 0,
child: SizedBox(
width: 35,
child: IconButton(
onPressed: () {
removeEmailControl(index);
},
icon: const Icon(
Icons.remove_circle,
color: Colors.redAccent,
),
),
),
),
],
),
],
),
),
);
}
Widget _sectionsContainer(index) {
/* the widget used to create the current section displayed on the top left of each textformfields*/
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Text(
'Section ${index + 1}',
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
],
);
}
void addEmailControl() {
setState(() {
coursesModel.sections!.add('');
});
}
void removeEmailControl(index) {
setState(() {
if (coursesModel.sections!.length > 1) {
coursesModel.sections!.removeAt(index);
}
});
}
bool validateAndSave() {
/* we're especially using the <FormState> that is provided by the Globalkey to be able access the currentState of widget/form that has the global key in order to either validate or save the textformfields input or both in the same time*/
// validate each form
if (globalkey.currentState!.validate()) {
// If all data are correct then save data to out variables
// save each form
globalkey.currentState!.save();
return true;
} else {
return false;
}
}
}
I'm trying my best to figure it out on my own as I want to know how to solve this problem properly and where did I go wrong, and any help is very much appreciated thank you!
I suggest to create List<GlobalKey> variable. When you dynamically add or delete sub forms, you add or remove list items accordingly. It is impossible to use same GlobalKey for multiple widgets. So you need to create separate GlobalKeys for each form.
You may create a file of Global variables that may be shared across multiple files to ensure you are using a single instance.
Example globals.dart file
GlobalKey<SomeState> myGlobalKey = GlobalKey<SomeState>();
Example of implementation inside main.dart (or whatever file)
import './[path-to-globals]/globals.dart' // enter the appropriate path for your project
... // some code
Form(
key: myGlobalKey,
... // code
)
... // maybe more code
Hi in my flutter app have FutureBuilder that return listview, my list listview create some button for update the hive table. when I click the first time on one of buttons everything is run smoothly, but when I click on same button again my hive key turn to null and program show my this error: "type 'Null' is not a subtype of type 'int' "
I write print all over my code but still I do not get it why the key turn null from the second time.
How can I Correct this? please help my.
my Futurebuilder body is:
FutureBuilder<List>(
future: controller.showTaskList(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return SizedBox(
height: Get.height,
child: const Center(
child: CircularProgressIndicator(),
),
);
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List data = snapshot.data ?? [];
return ListView.separated(
scrollDirection: Axis.vertical,
physics:
const BouncingScrollPhysics(),
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (context, index) {
// controller.taskIconCheckList
// .clear();
for (int i = 0;
i < data.length;
i++) {
if (data[i].status == true) {
controller.taskIconCheckList
.add(true.obs);
} else {
controller.taskIconCheckList
.add(false.obs);
}
}
return ListTile(
leading: Obx(
() => PageTransitionSwitcher(
transitionBuilder: (
child,
primaryAnimation,
secondaryAnimation,
) {
return SharedAxisTransition(
animation:
primaryAnimation,
secondaryAnimation:
secondaryAnimation,
transitionType:
SharedAxisTransitionType
.horizontal,
fillColor:
Colors.transparent,
child: child,
);
},
duration: const Duration(
milliseconds: 800),
child: controller
.taskIconCheckList[
index]
.value
? SizedBox(
child: IconButton(
icon: const Icon(
Icons
.check_circle_rounded,
color: Colors
.lightGreenAccent,
),
onPressed: () {
controller
.functionTaskIconCheckList(
index,
);
print('طول دیتا');
print(data.length.toString());
print('مقدار ایندکس');
print(index.toString());
print('مقدار کلید');
print(data[index].key.toString());
print(data[index].taskText.toString());
controller
.updateStatusTask(
index,
data[index]
.key); // here when i first click // return key currectly, but after that show null and updatestatusetask not run and show error.
},
),
)
: IconButton(
onPressed: () {
controller
.functionTaskIconCheckList(
index,
);
print('طول دیتا');
print(data.length.toString());
print('مقدار ایندکس');
print(index.toString());
print('مقدار کلید');
print(data[index].key.toString());
print(data[index].taskText.toString());
controller
.updateStatusTask(
index,
data[index]
.key); // here when i first click // return key currectly, but after that show null and updatestatusetask not run and show error.
},
icon: const Icon(
Icons
.radio_button_unchecked_outlined,
color: Colors.red,
),
),
),
),
title: Text(data[index].taskText,
style: normalTextForCategory),
subtitle: Text(
data[index]
.date
.toString()
.substring(0, 10),
textDirection:
TextDirection.ltr,
textAlign: TextAlign.right,
style: normalTextForSubtitle,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {
myDefaultDialog(
'هشدار',
'آیا از حذف این گزینه اطمینان دارید؟',
'بله',
'خیر',
() {
Get.back();
mySnakeBar(
'',
'گزینه مورد نظر با موفقیت حذف شد.',
Icons
.warning_amber_rounded,
Colors.yellow);
},
);
},
icon: const Icon(
Icons.delete),
color: Colors.redAccent,
),
IconButton(
onPressed: () {
Get.offNamed(
Routs.editTaskScreen,
arguments: 'edit');
},
icon: const Icon(
Icons.edit_calendar,
color:
Colors.yellowAccent,
),
),
],
),
);
},
separatorBuilder:
(BuildContext context,
int index) {
return const Divider(
height: 2,
color: Colors.white70,
);
},
);
}
}
},
),
this is my functionTaskIconCheckList form controller:
functionTaskIconCheckList(int index) {
taskIconCheckList[index].value = !taskIconCheckList[index].value;}
and this the updatestatusetask function
updateStatusTask(int index,int taskKey) async {
print('در تابع آپدیت ایندکس هست: ${index.toString()}');
print('در تابع آپدیت کی هست: ${taskKey.toString()}');
var taskBox = await Hive.openBox('task');
var filterTask = taskBox.values.where((task) => task.key == taskKey).toList();
Task task = Task(
filterTask[0].taskText,
filterTask[0].date,
taskIconCheckList[index].value,
filterTask[0].deleteStatus,
null,
null,
filterTask[0].taskCatId,
filterTask[0].userId);
await taskBox.put(taskKey, task);}
and this is my showtasklist function:
Future<List> showTaskList() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
var taskBox = await Hive.openBox('task');
var filterTask = taskBox.values
.where((task) => task.userId == sharedPreferences.getInt('key'))
.toList();
return filterTask;}
this is my model:
#HiveType(typeId: 2)
class Task extends HiveObject{
#HiveField(0)
String taskText;
#HiveField(1)
DateTime date;
#HiveField(2)
bool status;
#HiveField(3)
bool deleteStatus;
#HiveField(4)
int taskCatId;
#HiveField(5)
int userId;
#HiveField(6)
User? user;
#HiveField(7)
TaskCat? taskCat;
Task(this.taskText, this.date, this.status, this.deleteStatus, this.user,
this.taskCat, this.taskCatId, this.userId);
}
One possible solution would be to wait for the Future function to finish and then load the list. If it tries to load the list early before finishing up the Future function, it might presume the value to be null.
Hope this helps.
Still I do not know what is cause this problem, But I found an alternative temporary solution. I create temporary Int list. then just before the return listTile in the futureBuilder body, I write the Loop and save all of the key in that list. finally instead of pass the "data[index].key." I pass my key from that temporary Int list. so everything work fine now
this is my part of code change from before, but still I want know main solution.
return ListView.separated(
scrollDirection: Axis.vertical,
physics:
const BouncingScrollPhysics(),
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (context, index) {
// controller.taskIconCheckList
// .clear();
for (int i = 0;
i < data.length;
i++) {
Get.find<
HomeScreenController>()
.taskKey.add(data[i].key);
if (data[i].status == true) {
Get.find<
HomeScreenController>()
.taskIconCheckList
.add(true.obs);
} else {
Get.find<
HomeScreenController>()
.taskIconCheckList
.add(false.obs);
}
}
return ListTile(
I have a form that has three types of widgets such that DropDown, TextField, and TypeAhead. I have imported a package named flutter_typeahead that is basically a dynamic DropDown mixed with TextField.(More info about flutter_typeahead: flutter_typeahead, but there is no need to know flutter_typeahead widget to be able to understand and solve the problem.)
However, when I try to validate, if the TypeAhead widget is empty or null, then I throw an error to the user. Everything is ok up to now. But, when I fill the other instances of the form(DropDown and TextFields) and let the TypeAhead empty then the validation is not working. What I mean, If I fill the DropDowns and the TextFields and let the TypeAhead empty, then try to submit the form actually submits it. However, if I let the TextField empty or the DropDown as default the validation works fine.
To sum up, when all form widgets are empty, TypeAhead validation works, when only TypeAhead is empty it doesn't work.
Form widget
Form formWidget(Map<String, dynamic> map) {
return Form(
key: _formKey,
child: ListView(
shrinkWrap: true,
children: [
customTypeAhead(map['stajTuru'], _stajTuruController,
_selectedStajTuru, 'Staj Türü'), //Custom typeahead widget
customTypeAhead(
map['doktor'], _doktorController, _selectedDoktor, 'Doktor'), //Custom typeahead widget
customDropDown(
_valueOrtam, map['ortam'], hintTextOrtam, onChangedOrtam),
customDropDown(
_valueKapsam, map['kapsam'], hintTextKapsam, onChangedKapsam),
customDropDown(_valueEtkilesim, map['etkilesim'], hintTextEtkilesim,
onChangedEtkilesim),
customDropDown(_valueCinsiyet, map['cinsiyet'], hintTextCinsiyet,
onChangedCinsiyet),
const SizedBox(
height: 20,
),
customTextField(
1, "Kayıt No ", 10, _formData.setKayitNo, isEmpty, _kayit, 80),
customTextField(
1, "Hastanın Yaşı", 3, _formData.setYas, isNumeric, _yas, 80),
customTextField(
1, "Şikayet", 10, _formData.setSikayet, isEmpty, _sikayet, 80),
customTextField(1, "Ayırıcı Tanı", 10, _formData.setAyiriciTani,
isEmpty, _ayirici, 80),
customTextField(5, "Kesin Tanı", 50, _formData.setKesinTani, isEmpty,
_kesin, 130),
customTextField(5, "Tedavi Yöntemi", 200, _formData.setTedaviYontemi,
isEmpty, _tedavi, 130),
],
),
);
}
customTypeAhead
Widget customTypeAhead(List<String> listItems, TextEditingController controller,
String? stajTuru,String labelText) {
//check if the typed item is in the list
List<String> getSuggestions(String query) {
return List.of(listItems).where((item) {
final queryLower = query.toLowerCase();
final itemLower = item.toLowerCase();
return itemLower.contains(queryLower);
}).toList();
}
return Padding(
padding: const EdgeInsets.all(PADDING_VALUE),
child: Column(
children: [
Text(
labelText,
style: TEXT_STYLE,
),
TypeAheadFormField<String?>(
onSuggestionSelected: (String? val) =>controller.text = val!,
itemBuilder: (context, String? suggestion) {
return ListTile(
title: Text(suggestion!),
);
},
suggestionsCallback: getSuggestions,
validator: (value) { // This is my validation method
bool isInTheList=false;
for(var item in listItems){
if(item==value) {
isInTheList=true;
}
}
if (value == null || value.isEmpty || isInTheList==false) {
return 'Lütfen ${labelText.toLowerCase()} seçiniz';
} else {
print("null returned");
return null;
}
},
textFieldConfiguration: TextFieldConfiguration(
controller: controller,
decoration: const InputDecoration(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: TEXT_COLOR,
),
),
border: OutlineInputBorder(
borderSide: BorderSide(
color: TEXT_COLOR,
)))),
),
],
),
);
}
Submit function
void formIlet() async {
if (formArguments != null) {
if (_formKey.currentState!.validate()) {
setFormArgumentState();
bool res = await _mySqlHelper.insertData(formArguments!.formData);
if (res) {
_helper.update(formArguments!.formData);
customSnackBar(context, 'Başarıyla gönderildi');
} else {
errorAlert(context);
}
}
} else {
if (_formKey.currentState!.validate()) {
setFormDataState();
isLoading = true;
bool res = await _mySqlHelper.insertData(_formData).then((val) {
setState(() {
isLoading = false;
_formKey.currentState?.dispose();
});
return val;
});
if (res) {
customSnackBar(context, 'Başarıyla gönderildi');
} else {
errorAlert(context);
}
}
}
}
I have solved this problem. I don't know why it happened, but using a ListView inside the Form is not a good idea. That's causing the problem. So, I changed the ListView with a Column and wrapped it with SingleChildScrollView the problem is solved.
In my application user can have multiple home and multiple rooms for each home. On top of my application I have dropdown box which im trying to set default value to selectedHome by user. Below that dropdown box I am showing the rooms in the home selected by user. In firebase I have rooms collection under each home. I'm getting the selected home data from firebase too. Also to show the rooms in selected home i need to query by home name. I have two FutureBuilder as you can see code below. One of them to get the selectedHome data from firebase and other for the getting the rooms in that home from firebase. As I said before to get the rooms in selected home I need to query by name of the home so I have a parameter which is the value of dropdownbox. In my code the problem is getting the rooms part is working before I get the selectedHome data from firebase and assign it to dropdown value. In this case I'm getting "Null check operator used on a null value".
Basicly the question is how can i assign value from future to variable before screen gets build.
Here you can see the code for getting selected home data from firebase;
Future<String> selectedHome() async {
return await database.selectedHome();
}
Future<String> selectedHome() async {
DocumentSnapshot docS =
await firestore.collection("users").doc(auth.currentUser()).get();
String selectedHome = (docS.data() as Map)["selectedHome"];
return selectedHome;
}
Here you can see the code for getting room data based on selectedHome from firebase;
Future<List<Map>> deviceAndRoomInfo() async {
return database.numberOfRooms(_dropdownValue!);
}
Future<List<Map>> numberOfRooms(String selectedHome) async {
List<Map> prodsList = [];
final snapshot = await firestore
.collection("users")
.doc(auth.currentUser())
.collection("homes")
.doc(selectedHome)
.collection("rooms")
.get();
List listOfRooms = snapshot.docs;
for (int a = 1; a <= listOfRooms.length; a++) {
var productsInRoom = await firestore
.collection("users")
.doc(auth.currentUser())
.collection("homes")
.doc(selectedHome)
.collection("rooms")
.doc(listOfRooms[a - 1]["roomName"])
.collection("products")
.get();
List prodList = productsInRoom.docs
.map((e) => DeviceModel.fromMap(e.data()))
.toList();
Map qq = {
"roomName": listOfRooms[a - 1]["roomName"],
"deviceInfo": prodList
};
prodsList.add(qq);
}
return prodsList;
}
Here you can see the code for screen contains 2 future builder that i told;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shelly_ess_production/constants.dart';
import 'package:shelly_ess_production/helper_widgets/loading_widget.dart';
import 'package:shelly_ess_production/screens/home_screen/components/circle_room_data.dart';
import 'package:shelly_ess_production/screens/home_screen/components/device_in_room_card.dart';
import 'package:shelly_ess_production/screens/home_screen/provider/home_screen_provider.dart';
import 'package:shelly_ess_production/screens/models/device_model.dart';
import 'package:shelly_ess_production/size_config.dart';
class Body extends StatefulWidget {
const Body({Key? key}) : super(key: key);
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
#override
Widget build(BuildContext context) {
var providerHelper =
Provider.of<HomeScreenProvider>(context, listen: false);
return SafeArea(
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(0.07)),
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: getProportionateScreenHeight(0.02),
),
Consumer<HomeScreenProvider>(builder: (context, data, child) {
return FutureBuilder<List<String>>(
future: data.getHomesAndSelected(),
builder: (context, snapshot) {
if (snapshot.hasData) {
data.setDropDownValue = snapshot.data![0];
return DropdownButtonHideUnderline(
child: DropdownButton(
iconEnabledColor: kPrimaryColor,
iconDisabledColor: kPrimaryColor,
style: TextStyle(
color: kPrimaryColor,
fontSize: getProportionateScreenHeight(0.05)),
menuMaxHeight: getProportionateScreenHeight(0.4),
borderRadius: BorderRadius.circular(15),
key: UniqueKey(),
value: data.dropdownValue,
isExpanded: true,
icon: const Icon(Icons.arrow_downward),
onChanged: (String? newValue) async {
data.setDropDownValue = newValue;
await data.changeSelectedHome();
},
items: snapshot.data!
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
alignment: Alignment.center,
value: value,
child: Text(value),
);
}).toList(),
),
);
} else {
return Transform.scale(
scale: 0.5,
child: const Center(
child: CircularProgressIndicator(),
),
);
}
});
}),
SizedBox(
height: getProportionateScreenHeight(0.02),
),
SizedBox(
height: getProportionateScreenHeight(0.14),
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: 5,
itemBuilder: (context, index) {
return CircleRoomData(
title: "Oda Sayısı",
icon: Icons.meeting_room,
content: "8",
);
}),
),
Consumer<HomeScreenProvider>(builder: (context, data, snapshot) {
return FutureBuilder<List<Map>>(
future: data.deviceAndRoomInfo(data.dropdownValue!),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
itemCount: snapshot.data!.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return Column(
children: [
Divider(
thickness:
getProportionateScreenHeight(0.002),
),
Text(
snapshot.data![index]["roomName"],
style: TextStyle(
fontWeight: FontWeight.bold,
color: kSecondaryColor,
fontSize:
getProportionateScreenHeight(0.03)),
),
SizedBox(
height: getProportionateScreenHeight(0.01),
),
Text(
"${(snapshot.data![index]["deviceInfo"] as List).length.toString()} Cihaz",
style:
const TextStyle(color: kSecondaryColor),
),
SizedBox(
height: getProportionateScreenHeight(0.02),
),
GridView.builder(
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
itemCount: (snapshot.data![index]
["deviceInfo"] as List)
.length,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, indexx) {
print(index);
return DeviceInRoom(
icon: Icons.light,
productName: ((snapshot.data![index]
["deviceInfo"]
as List)[indexx] as DeviceModel)
.deviceName,
);
})
],
);
});
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
});
}
)
],
)),
),
);
}
}
Am not certain where your error is coming from, but from what I see it maybe as a result of one of your functions returning null and a rendering of your content happens before the data is received.
You could try one of these:
You could declare the return type of your feature as being nullable for example you are expecting a value of type int:
Future<int?> xyz(){
......
return .....;
}
Now because your return type is nullable you wont have an issues as long as the receiving variable is also nullable.
Alternatively:
Future<int?> xyz(){
......
return ..... ?? 10 /*some default value*/;
}
because you know you result could be null you could also provide an optional default value incase your Future call returns a null value.
As my first Flutter project I'm building an app for a newspaper. There are a number of news categories. For those categories, I have created a generic Widget, in the home screen that Widget will be shown multiple times for multiple categories in a Listview, those category Widgets have Listview in them too as I bring multiple news from those categories. The Widget class that has this generic Widget is Newsfeed.dart which will be given below.
This generic Widget is called from a another Widget class WidgetFactory.dart which actually calls API and builds the home screen by using the above mentioned generic Widget for categories. This class uses a Listview.builder which is inside FutureBuilder.
The problem is, when I open the app in the screen the news appears but I can't scroll, it stays fixed. I have checked if the API is actually bringing the news, in console I have printed the API response, all of the news are fetched but still I can't scroll.
the flow is main.dart -> WidgetFactory() -> Newsfeed()
WidgetFactory()
class WidgetFactory extends StatefulWidget {
#override
_WidgetFactoryState createState() => _WidgetFactoryState();
}
class _WidgetFactoryState extends State<WidgetFactory> {
List homeScreenCategories = [4, 14, 13, 23, 8015, 22];
Future<List> newsPostList;
List<List<NewsPost>> categoryNewsPostList;
#override
void initState() {
super.initState();
newsPostList = fetchNews();
}
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Container(
alignment: Alignment.center,
child: Container(
child: RefreshIndicator(
child: FutureBuilder(
future: newsPostList,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.data == null) {
return Container(
child: CircularProgressIndicator()
);
} else {
return ListView.builder(
shrinkWrap: true,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return _getCategoryNews(snapshot, index);
},
);
}
},
),
onRefresh: () {
fetchNews();
}
),
),
);
}
Future<List> fetchNews() async {
String url = "url";
Response response = await Dio().get(url);
if(response.statusCode == 200) {
List newsPostList = [];
for(int i=0; i<response.data.length; i++) {
newsPostList.add(response.data[i]);
}
return newsPostList;
} else {
throw Exception("Failed to fetch category");
}
}
Widget _getCategoryNews(snapshot, int index) {
List<NewsPost> newsPostList = [];
for(var c in snapshot.data[index]['items']) {
NewsPost newsPost = NewsPost.getNewsPostFromAPI(c);
newsPostList.add(newsPost);
}
return Newsfeed(newsPostList, "National");
}
}
Newsfeed()
class Newsfeed extends StatefulWidget {
String categoryName;
List<NewsPost> newsPostList;
Newsfeed(this.newsPostList, this.categoryName);
#override
_NewsfeedState createState() => _NewsfeedState(this.newsPostList, this.categoryName);
}
class _NewsfeedState extends State<Newsfeed> {
final GlobalKey<ScaffoldState> _scaffoldKeyTwo = new GlobalKey<ScaffoldState>(debugLabel: '_MainScreenKey');
String categoryName;
_NewsfeedState(this.newsPostList, this.categoryName);
List<NewsPost> newsPostList;
var dio = new Dio();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Container(
alignment: Alignment.center,
child: ListView.builder(
shrinkWrap: true,
itemCount: newsPostList.length,
itemBuilder: (BuildContext context, int index) {
print(newsPostList[index]);
return _getNewsPostWidgets(index);
}
),
);
}
Widget _getNewsPostWidgets(int index) {
var newsPost = newsPostList[index];
if(index < 5) {
if(index == 0) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
ScaleTransitionRoute(
page: NewsPostDetails(newsPostList, index)
)
);
},
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(10, 0, 0, 0),
//constraints: BoxConstraints(minWidth: double.infinity, maxWidth: double.infinity),
constraints: BoxConstraints.expand(
width: double.infinity,
height: 40
),
color: const Color(0xFF2b4849),
child: Text(
this.categoryName,
style: TextStyle(
fontSize: 33,
color: Colors.white
),
),
),
BlockHeadline(newsPost)
],
)
);
}
else {
return GestureDetector(
onTap: () {
Navigator.push(
context,
ScaleTransitionRoute(
page: NewsPostDetails(newsPostList, index)
)
);
},
child: ListedNews(newsPost),
);
}
}
else {
return Container(
color: const Color(0xFF2b4849),
child: index == 5 ? FlatButton(
child: Text(
"See More",
style: TextStyle(
color: Colors.white
),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => NewsFeedForSpecificCategory(newsPostList)
)
);
},
) : Container(),
);
}
}
openNewsPostDetails(List<NewsPost> newsPostList, int index) {
Navigator.push(
context,
ScaleTransitionRoute(
page: NewsPostDetails(newsPostList, index)
)
);
}
}
What I have tried
I found some questions that relates to this problem a bit. Tried those.
I used shrinkwrap=true in my Listview.builder, but of no use.
Tried using Column inside SingleChildScrollView(), still did not work.
Inside the Listview.builder added physics as AlwaysScrollable(), also in vain.
As I'm new to flutter what I tried might seem dumb.
One more thing is, the news that shows up in the home screen, that takes me to the details page fine and there swiping left right takes me to other news also. It's the home screen that is causing trouble, not scrolling.
It would be great help if you could kindly give some clues.
I have found the solution to this problem. It was quite simple actually.
In the build() method of my _NewsfeedState class I have added ClampingScrollPhysics() as physics.
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Container(
alignment: Alignment.center,
child: ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: newsPostList.length,
itemBuilder: (BuildContext context, int index) {
print(newsPostList[index]);
return _getNewsPostWidgets(index);
}
),
);
}
It worked.
Putting ListView inside ListView is an anti pattern.
There are several solutions for this problem,
Solution 1:
Merge those two list of items into a single list of items and
display it as a single ListView.
Solution 2:
You can use SliverList for your use case. SliverList can wrap multiple SliverLists inside a single CustomScrollView.
This will help you.
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
Thanks.