I'm making a flutter App for a project in my School and I have a problem. I have a page where there are widgets representing categories of articles and when a category is clicked a page with articles from that category is displayed. The problem is that once a category is called, the articles in that category remain the same despite the category change.
When the page is called, a controller is created that will execute the query that retrieves the items in the category.
How can I get this controller to remind me every time the page is loaded?
Category page code :
class ProduceCategoryScreen extends StatefulWidget {
static String routeName = "/produceByCategorie";
#override
State<ProduceCategoryScreen> createState() => _ProduceCategoryScreenState();
}
class _ProduceCategoryScreenState extends State<ProduceCategoryScreen> {
static int gridColumn = 1;
static ArticleByCategoryController articleController =
Get.put(ArticleByCategoryController());
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Produitss"),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: Text(
'Green Tomato',
style: TextStyle(
color: Colors.black,
fontSize: 32,
fontWeight: FontWeight.w900,
),
),
),
IconButton(
onPressed: () {
switch (gridColumn) {
case 1:
setState(() {
gridColumn = 2;
});
break;
case 2:
setState(() {
gridColumn = 1;
});
break;
default:
}
},
icon: Icon(Icons.grid_view),
)
],
),
),
Expanded(
child: Obx(
() {
if (articleController.isLoading.value)
return Center(child: CircularProgressIndicator());
else
return AlignedGridView.count(
crossAxisCount: gridColumn,
itemCount: articleController.articleList.length,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
itemBuilder: (context, index) {
return ProductTile(articleController.articleList[index]);
},
);
},
),
),
],
),
);
}
}
The category widget
Category screen
If you want more information you can send me a message on my discord : PascheK7#6324.
I think you are passing same list of data everytime when you execute below line.
"return ProductTile(articleController.articleList[index]);"
articleController.articleList : does this list contain only one category data ?
or it is contain category wise data.
e.g. : articleController.articleList[0] = category 1 list
articleController.articleList[1] = category 2 list
articleController.articleList[2] = category 3 list --> this way, it shouldn't make problem.
If articleList contain only one category data, then issue can happen that you get same data everytime. bcoz you are passing only index, but not category wise data.
Related
My first Flutter project, which is a tricycle booking system, has just begun. Using the ListView widget, I wanted to display all of the active passengers that are saved in my Firebase Database. However, when I attempted to display it and place it in a List, all functions are working fine at first click. When you click the button to view the ListView a second time, all of the saved data are replicated. The list continues after my third click and grows by three. The image below illustrates what takes place when I repeatedly click on the ListView.
These are the blocks of code that are utilized for this functionality:
CODE for Functionality
retrieveOnlinePassengersInformation(List onlineNearestPassengersList) async
{
dList.clear();
DatabaseReference ref = FirebaseDatabase.instance.ref().child("passengers");
for(int i = 0; i<onlineNearestPassengersList.length; i++)
{
await ref.child(onlineNearestPassengersList[i].passengerId.toString())
.once()
.then((dataSnapshot)
{
var passengerKeyInfo = dataSnapshot.snapshot.value;
dList.add(passengerKeyInfo);
print("passengerKey Info: " + dList.toString());
});
}
}
CODE for the UI
body: ListView.builder(
itemCount: dList.length,
itemBuilder: (BuildContext context, int index)
{
return GestureDetector(
onTap: ()
{
setState(() {
chosenPassengerId = dList[index]["id"].toString();
});
Navigator.pop(context, "passengerChoosed");
},
child: Card(
color: Colors.grey,
elevation: 3,
shadowColor: Colors.green,
margin: EdgeInsets.all(8.0),
child: ListTile(
leading: Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Icon(
Icons.account_circle_outlined,
size: 26.sp,
color: Color(0xFF777777),
),
),
title: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
Text(
dList[index]["first_name"] + " " + dList[index]["last_name"],
style: TextStyle(
fontFamily: "Montserrat",
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
Icon(
Icons.verified_rounded,
color: Color(0xFF0CBC8B),
size: 22.sp,
),
],
),
],
),
),
),
);
},
),
Expected Result:
Actual Result AFTER CLICKING MANY TIMES:
Made a demo for you how to call function once on load
class CustomWidgetName extends StatefulWidget {
const CustomWidgetName({Key? key}) : super(key: key);
#override
State<CustomWidgetName> createState() => _CustomWidgetNameState();
}
class _CustomWidgetNameState extends State<CustomWidgetName> {
List? dList = [];
void myDataFunction() async {
// do your data fetch and add to dList
final newList = [];
setState(() {
dList = newList;
});
}
#override
void initState() {
super.initState();
myDataFunction(); // Call your async function here
}
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
Try this solution.
Update SelectNearestActiveDriversScreen() like this:
class SelectNearestActiveDriversScreen extends StatefulWidget
{
DatabaseReference? referenceRideRequest;
final List list;
SelectNearestActiveDriversScreen({this.referenceRideRequest, required this.list});
#override
State<SelectNearestActiveDriversScreen> createState() => _SelectNearestActiveDriversScreenState();
}
In homepage.dart, declare List dList = [];, then change line 378 like this:
Navigator.push(context, MaterialPageRoute(builder: (c)=> SelectNearestActiveDriversScreen(list: dList)));
In SelectNearestActiveDriversScreen(), replace all dList with widget.list.
Finally, if you are using variables in a specific file declare them in that file(not in another file) or pass them in the constructor of the class / file / widget /screen you are calling.
If you would rather use global variables and state managers go for packages like GetX.
say I have a song lyric app and there is just one Scaffold with a Text widget that displays the entire lyric and the lyrics are written in the format
....
Chorus:
...
....
....
and I have a FAB, onClick of which I need the text to auto scroll to the text "Chorus:", this text is literally in every song, but when the verses are a about 4+, they usually go off screen, so, user usually has to manually scroll to the chorus again after each verse that's beyond the screen height, but I need this to be done automatically at the tap of a button
scroll up till the string "chorus" is in view, how would I do this in flutter
TEXT
const kTheAstronomers = '''1. Yeah, you woke up in London
At least that's what you said
I sat here scrollin'
And I saw you with your aunt
A demon on your left
An angel on your right
But you're hypocritical
Always political
Chorus:
Say you mean one thing
But you go ahead and lie
Oh, you lie-lie, lie-lie
And you say you're not the bad type
2. Oh, you posted on Twitter
Said you had other plans
But your mother, she called me
Said, "Come have lunch with the fam"
Guess you didn't tell her that
You should've called me back
I guess you changed your number or somethin\' '''
LYRIC SCREEN
#override
Widget build(BuildContext context) {
return Scaffold(
extendBody: true,
body: SafeArea(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10),
child: Text(
kTheAstronomers,
style: const TextStyle(
fontSize: 30,
fontFamily: 'Montserrat',
fontWeight: FontWeight.w600,
),
),
),
),
)
floatingActionButton: FAB(onPressed: autoScrollToChorus),
,
You can create a GlobalKey and use the currentContext to scroll to the Chorus part.
final _key = GlobalKey()
Inside the autoScrollToChorus method you can add:
final context = _key.currentContext!;
await Scrollable.ensureVisible(context)
I found a way.
I had to change the way I displayed the text, instead of using one text widget, I used a ListView builder to display two texts, but before that, in initState, when my page receives the text, I split the text into a list of two separate texts, one containing the first part and the other containing from the Chorus down, then I give this list to the listview builder (you could also just use a column and create two separate widgets and just pass the scroll key to the second text, knowing it's the second part of the text)
final GlobalKey _key = GlobalKey();
void _autoScrollToChorus() async {
BuildContext context = _key.currentContext!;
await Scrollable.ensureVisible(context);
}
late List<String> lyricList;
#override
initState() {
lyricList =
kTheAstronomers.split(RegExp("(?=chorus)", caseSensitive: false));
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView.builder(
itemCount: lyricList.length,
itemBuilder: (context, idx) {
return Text(
key: idx == 1 ? _key : null,
lyricList[idx],
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 30,
),
);
}),
),
floatingActionButton: lyricList.length > 1 ? FloatingActionButton(
onPressed: _autoScrollToChorus,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text("Chorus"),
),
) : null,
);
}
Thanks to #Priyaank I knew to use the key and scroll to a particular widget
a more advanced solution that makes it possible to hide the button when the chorus is in view USING THE SCROLLABLE_POSITIONED_LIST PACKAGE
final GlobalKey _key = GlobalKey();
final ItemScrollController _itemScrollController = ItemScrollController();
final ItemPositionsListener _itemListener = ItemPositionsListener.create();
late List<String> lyricList;
bool chorusIsVisible = true;
void _autoScrollToChorus() {
// BuildContext context = _key.currentContext!;
// await Scrollable.ensureVisible(context);
_itemScrollController.scrollTo(
index: 1,
duration: const Duration(milliseconds: 500),
alignment: 0.5
);
}
#override
initState() {
lyricList =
kTheAstronomers.split(RegExp("(?=chorus)", caseSensitive: false));
super.initState();
if(lyricList.length > 1) {
_itemListener.itemPositions.addListener(() {
chorusIsVisible = _itemListener.itemPositions.value.where((item) {
final isTopVisible = item.itemLeadingEdge >= 0;
return isTopVisible;
}
).map((item) => item.index).toList().contains(1);
setState(() {});
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ScrollablePositionedList.builder(
itemScrollController: _itemScrollController,
itemPositionsListener: _itemListener,
itemCount: lyricList.length,
itemBuilder: (context, idx) {
return Text(
lyricList[idx],
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 30,
),
);
}),
),
floatingActionButton: lyricList.length > 1 && !chorusIsVisible ? FloatingActionButton(
onPressed: _autoScrollToChorus,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text("Chorus"),
),
) : null,
);
}
}
I have an array which i set as a class like this
class FilterArray {
static var FilterArrayData = [];
}
I am simply adding the values in an array. Issue is i am calling this array in a page when array is null. Then on next Page i am adding values in array. Now issue is when i come back in previous page the array is still null. I need to refresh page for this. Which i dont want thats why i use FutureWidget i though from Future widget when array update it will also update in my screen but thats not working. Need to know what can i do for this here i need to update data when array is update so it can show in a Future Widget.
This is my total code
class _SearchPgState extends State<SearchPg> {
Future getData() async {
var result = FilterArray.FilterArrayData;
if (result.length != 0) {
return result;
} else {
return null;
}
}
#override
Widget build(BuildContext context) {
print(FilterArray.FilterArrayData);
return Scaffold(
appBar: AppBar(
title: Container(
height: 50.0,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 3.0),
child: Center(
child: TextField(
onTap: () => Get.to(SearchPgExtra()),
readOnly: true,
decoration: InputDecoration(
hintText: tr('search.search'),
alignLabelWithHint: true,
hintStyle: Theme.of(context).textTheme.subtitle2,
prefixIcon: Icon(Icons.search),
),
),
),
),
),
actions: [
IconButton(
icon: Icon(
FlutterIcons.sort_descending_mco,
color: Theme.of(context).accentColor,
),
onPressed: navigateToSortPage,
),
IconButton(
icon: Icon(
FlutterIcons.filter_fea,
color: Theme.of(context).primaryColor,
),
onPressed: navigateToFilterPage,
),
],
),
body: FutureBuilder(
future: getData(), // async work
builder: (context, projectSnap) {
print(projectSnap.data);
if (projectSnap.hasData) {
return StaggeredGridView.countBuilder(
itemCount: projectSnap.data.length,
crossAxisCount: 4,
staggeredTileBuilder: (int index) => StaggeredTile.fit(2),
mainAxisSpacing: 15.0,
crossAxisSpacing: 15.0,
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: ScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 18.0),
itemBuilder: (context, index) {
var product = projectSnap.data[0][index];
return FadeInAnimation(
index,
child: ProductCard2(
product: product,
isHorizontalList: false,
),
);
},
);
} else {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
'assets/images/search.png',
width: MediaQuery.of(context).size.width / 2,
),
SizedBox(height: 15.0),
Text(
'search.title',
style: Theme.of(context).textTheme.headline1,
).tr(),
SizedBox(height: 15.0),
Text(
'search.subtitle',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.subtitle1,
).tr(),
SizedBox(
height: MediaQuery.of(context).size.height / 5,
),
],
),
);
}
},
),
);
}
}
In start array is null then ill add values in array then comeback nothing change then i reload the screen then its working fine.
This is the how i am adding array
RangeSlider(
values: _currentRangeValues,
min: 0,
max: 10000,
divisions: 10,
labels: RangeLabels(
_currentRangeValues.start.round().toString(),
_currentRangeValues.end.round().toString(),
),
onChanged: (RangeValues values) {
setState(() {
_currentRangeValues = values;
//print(_currentRangeValues);
});
var data = searchArray.searchArrayData;
for (int i = 0; i < data.length; i++) {
var current = data[i];
if(current['Price'] >= _currentRangeValues.start && current['Price'] <= _currentRangeValues.end){
print(data);
FilterArray.FilterArrayData.add(data);
}
}
},
),
when data add to FilterArrayData ill go back on Page array on that page not updating so then i change the page and comeback again in SearchPg then i can see data
Don't do your validation with the length of your array. It is like trying to do a validation with something that doesn't existe yet. Instead of that, try using
if(snapshot.hasData)
{ return ... ; }
then, after that, now you can do another validation, for instance, sometimes what you receive is data, but an empty array. There is where I would place the other two options. Remember, inside of the first if.
if(array.isNotEmpty)
{ return ... ; }
and
else
{ return ... ; }
After the first if, then you can now also validate, what will happen if you didn't receive data at all. Simply with an else.
else
{ return ... ; }
In summary: use one first validation with hasData and then, inside of that, decide what to do with the received information. Outside all that, decide what to do if you didn't receive any information at all.
Such cases are faced by new developers often. The best way to deal with it is state management packages like Provider, Bloc, etc. Visit the link and you will find all the relevant packages. Personally, I have used Provider a lot. Bloc is also a good option. A lot of people use it. But I haven't had the chance to use it. Riverpod is an up and coming package. But it still requires a lot of fixing.
I'm trying to create a simple vertical scrolling calendar.
Problem is that I can't manage to find a way to reset back to previous state in case I tap on a new container.
Here's the code:
class CalendarBox extends StatelessWidget {
BoxProprieties boxProprieties = BoxProprieties();
Map item;
CalendarBox({this.item});
bool selected = false;
#override
Widget build(BuildContext context) {
return Consumer<Producer>(
builder: (context, producer, child) => GestureDetector(
onTap: () {
print(item['dateTime']);
selected = producer.selectedState(selected);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 100),
color: selected == true ? Colors.blue : Colors.grey[200],
height: 80,
width: 50,
margin: EdgeInsets.only(top: 5),
child: Column(
children: [
Text(
'${item['dayNum']}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: boxProprieties.dayColor(item['dateTime'])),
),
],
),
),
),
);
}
}
Here's the situation:
One way to achieve it is, create a model for boxes and keep a value current selected block, in your model you will have the index assigned to that block,
int currentSelected =1; //initial value
class Block{
int id;
..
.. // any other stuff
}
now in your code, the check modifies to
block.id == currentSelected ? Colors.blue : Colors.grey[200],
your on tap modifies to
onTap: () {
setState(){
currentSelected = block.id
};
},
If you want to prevent the rebuild of the whole thing every time you can use valueNotifire for current selected block. Hope this gives you an idea.
Here's the context:
In my app, users can create a question, and all questions will be displayed on a certain page. This is done with a ListView.builder whose itemBuilder property returns a QuestionTile.
The problem:
If I create a new question, the text of the new question is (usually) displayed as the text of the previous question.
Here's a picture of me adding three questions in order, "testqn123", "testqn456", "testqn789", but all are displayed as "testqn123".
Hot restarting the app will display the correct texts for each question, but hot reloading wont work.
In my _QuestionTileState class, if I change the line responsible for displaying the text of the question on the page, from
child: Text(text)
to
child: Text(widget.text)
the issue will be resolved for good. I'm not super familiar with how hot restart/reload and state works in flutter, but can someone explain all of this?
Here is the code for QuestionTile and its corresponding State class, and the line changed is the very last line with words in it:
class QuestionTile extends StatefulWidget {
final String text;
final String roomName;
final String roomID;
final String questionID; //
QuestionTile({this.questionID, this.text, this.roomName, this.roomID});
#override
_QuestionTileState createState() => _QuestionTileState(text);
}
class _QuestionTileState extends State<QuestionTile> {
final String text;
int netVotes = 0;
bool expand = false;
bool alreadyUpvoted = false;
bool alreadyDownvoted = false;
_QuestionTileState(this.text);
void toggleExpansion() {
setState(() => expand = !expand);
}
#override
Widget build(BuildContext context) {
RoomDbService dbService = RoomDbService(widget.roomName, widget.roomID);
final user = Provider.of<User>(context);
print(widget.text + " with questionID of " + widget.questionID);
return expand
? ExpandedQuestionTile(text, netVotes, toggleExpansion)
: Card(
elevation: 10,
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 7, 15, 7),
child: GestureDetector(
onTap: () => {
Navigator.pushNamed(context, "/ChatRoomPage", arguments: {
"question": widget.text,
"questionID": widget.questionID,
"roomName": widget.roomName,
"roomID": widget.roomID,
})
},
child: new Row(
// crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Column(
// the stack overflow functionality
children: <Widget>[
InkWell(
child: alreadyUpvoted
? Icon(Icons.arrow_drop_up,
color: Colors.blue[500])
: Icon(Icons.arrow_drop_up),
onTap: () {
dynamic result = dbService.upvoteQuestion(
user.uid, widget.questionID);
setState(() {
alreadyUpvoted = !alreadyUpvoted;
if (alreadyDownvoted) {
alreadyDownvoted = false;
}
});
},
),
StreamBuilder<DocumentSnapshot>(
stream: dbService.getQuestionVotes(widget.questionID),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
// print("Current Votes: " + "${snapshot.data.data["votes"]}");
// print("questionID: " + widget.questionID);
return Text("${snapshot.data.data["votes"]}");
}
},
),
InkWell(
child: alreadyDownvoted
? Icon(Icons.arrow_drop_down,
color: Colors.red[500])
: Icon(Icons.arrow_drop_down),
onTap: () {
dbService.downvoteQuestion(
user.uid, widget.questionID);
setState(() {
alreadyDownvoted = !alreadyDownvoted;
if (alreadyUpvoted) {
alreadyUpvoted = false;
}
});
},
),
],
),
Container(
//color: Colors.red[100],
width: 290,
child: Align(
alignment: Alignment.centerLeft,
child: Text(text)), // problem solved if changed to Text(widget.text)
),
}
}
You can wrap your UI with a Stream Builder, this will allow the UI to update every time any value changes from Firestore.
Since you are using an item builder you can wrap the widget that is placed with the item builder.
That Should update the UI