Display ads in every nth index using Listview.builder - flutter

I am trying to place an advertisement after every 3rd index. I got the required output but my problem in my case is i have a two type of List,
List One has 50 datas in it.
List two has only 5 datas.
When i tried with the below code.
ListView.separated(
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.all(15),
shrinkWrap: true,
itemCount: articles.length,
separatorBuilder: (context, index) => SizedBox(
height: 15,
),
itemBuilder: (BuildContext context, int index) {
final Article article = articles[index];
final Sponsor sponsor = sponsors[index];
if(index %3 == 0 && index != 0) return SponsorCard(sponsor: sponsor, scaffoldKey: widget.scaffoldKey);
return Card4(article: article, heroTag: 'recent${article.id}', scaffoldKey: widget.scaffoldKey);
widget.scaffoldKey);
},
),
i am getting range error since sponsor list reached its last position.
error
The following RangeError was thrown building:
RangeError (index): Invalid value: Not in inclusive range 0..4: 49
What i need is that i need to make the sponsor list as loop so that if the List reached its last position it has to move again to the first position.
Can someone please help me on this.

You can create a new combined list as,
and make sure both Article & Sponsor class should be child class of ListItem.
OR
you need to wrap Article & Sponsor classes under ListItem Wrapper class
List items = []
Now use the items with ListView :
ListView.separated(
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.all(15),
shrinkWrap: true,
itemCount: items.length,
separatorBuilder: (context, index) => SizedBox(
height: 15,
),
itemBuilder: (BuildContext context, int index) {
if (item[index] is Articles){
return SponsorCard(..);
}else{
return Card4(...);
}
},
);
Added Example on Request comment:
class _ListItem {
final bool isArticleType;
final dynamic value;
_ListItem(this.isArticleType, this.value);
}
class Article {}
class Sponsor {}
List<_ListItem> createList(List<Article> articles, List<Sponsor> sponsors) {
List<_ListItem> items = [];
items.addAll(articles.map((e) => _ListItem(true, e)).toList());
var index = 0;
var sIndex = 0;
for (var i = 0; i < articles.length && sIndex < sponsors.length; i++) {
if (i != 0 && i % 3 == 0) {
items.insert(index, _ListItem(false, sponsors[sIndex]));
sIndex++;
index++;
}
index++;
}
return items;
}

Thanks for your response guys.
I could achieve by the below code.
itemBuilder: (BuildContext context, int index) {
actualIndex = actualIndex +1;
if(sponsorIndex+1 >= sponsors.length){
sponsorIndex = -1;
}
if(index == 0 ){
articleIndex = -1;
}
if ((actualIndex-tempIndex) % 3 == 0 && actualIndex != 0 && (actualIndex-prevIndex) >=3 ) {
tempIndex = tempIndex +1;
prevIndex = actualIndex;
sponsorIndex = sponsorIndex + 1;
final Sponsor sponsor = sponsors[sponsorIndex];
return SponsorCard(sponsor: sponsor, scaffoldKey: widget.scaffoldKey);
}
articleIndex = articleIndex + 1;
final Article article = articles[articleIndex];
return Card4(article: article, heroTag: 'recent${article.id}', scaffoldKey: widget.scaffoldKey);
},

Related

How to build seperatorBuilder before itemBuilder in listview.seperated

I'm trying to implement a chat screen in my app and I want to display the dates before the conversation ( today, yesterday in most chatting apps ).
To display messages I use the itemBuilder and to show these dates I used seperatorBuilder of Listview.seperated.
The problem is that the date widget appears after the message, which was supposed to indicate the date.
(here, the 'yesterday' widget was supposed to come above the 'hey' message). (the seperatorBuilder builds after itemBuilder)
I thought to switch the codes of seperatorBuilder and itemBuilder (build the chat bubble in seperatorbuilder and dater widget in itemBuilder) to get the build order I want but I doubt its performance.
CODE:
ListView.separated(
separatorBuilder: (context, index) {
if (index == 0 ||
compareDate(
currentSnap: snapshot.data!.docs[index] //snapshot from StreamBuilder
.data()['serverTimeStamp'],
previousSnap: snapshot.data!.docs[index - 1]
.data()['serverTimeStamp'])) // some checks before displaying
{return chatDater(); // the Widget that displays date container
} else {
return SizedBox(
width: 0,
height: 0,
);
}
},
controller: _controller1,
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index)
return MessageBubble(
timestamp:
snapshot.data!.docs[index].data()['serverTimeStamp'],
message: snapshot.data!.docs[index].data()['message'],
IsYou: snapshot.data!.docs[index].data()['sender'] ==
widget.usernameOfFriend
? false
: true,
);
},
)
the compareDate method:
checks if two consecutive messages have different dates, if yes then returns true (so that we can display the dater widget)
bool compareDate(
{required Timestamp currentSnap, required Timestamp previousSnap}) {
var currentDate = currentSnap.toDate();
var previousDate = previousSnap.toDate();
if (currentDate.year != previousDate.year) return true;
if (currentDate.month != previousDate.month) return true;
if (currentDate.day != previousDate.day) return true;
return false;
}
Add timestamp also in itemBuilder when needed. Here is the simplified version:
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: messages.length,
itemBuilder: (BuildContext context, int index) {
final previous = index > 0 ? messages[index - 1] : null;
final current = messages[index];
final date = showDate(current, previous);
return Column(
children: [
if (date != null)
Text(date.toString()),
Text(current.text),
]
);
},
);
}
String? showDate(Message current, Message? previous) {
// TODO control when date is shown
if (previous == null) {
return current.date.toString();
} else {
return null;
}
}

for loop get only last index not correct index flutter

I have one list and this list format is month wise data I'm putting month wise data in list but only show the last index data not proper data, If I print data inside if condition is print perfect but inside widget not shown correct. I'm adding some code:
Declarations:
int monthIndex,
List transactionLists =[];
my method:
myMethod(){
for (var i = 1; i < transactionLists.length; i++) {
if (monthName[monthIndex] == "July") {
data=transactionLists[i].july.toString();
print(data);
} else if (monthName[monthIndex] == "August") {
data = transactionLists[i].august.toString();
} else if (monthName[monthIndex] == "September") {
data = transactionLists[i].september.toString();
} else if (monthName[monthIndex] == "October") {
data = transactionLists[i].october.toString();
} //.... up to all 12 months
}
return ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: transactionLists.length,
itemBuilder: (context, childIndex) {
return Text(
data,
);
}
}
what I have try data[childIndex]
Output - 1245,1245,1245 for all index get same value I want correct value with like 5445,8545,1245 it comes from the list
You are using data in a for loop which constantly update it, that cause your problem. First you need define data as list like this:
List data = [];
then use it like this:
myMethod(){
for (var i = 0; i < transactionLists.length; i++) {
if (monthName[monthIndex] == "July") {
data.add(transactionLists[i].july.toString());//<--- change this
print(data);
} else if (monthName[monthIndex] == "August") {
data.add(transactionLists[i].august.toString());//<--- change this
} else if (monthName[monthIndex] == "September") {
data.add(transactionLists[i].september.toString());//<--- change this
} else if (monthName[monthIndex] == "October") {
data.add(transactionLists[i].october.toString());//<--- change this
} //.... up to all 12 months
}
return ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: data.length,//<--- change this
itemBuilder: (context, childIndex) {
return Text(
data[childIndex],//<--- change this
);
}
}

How to work with listviews correctly for displaying text

I'm trying to display an message, but unfortunately I think I'll need to change my method.
So I've resumed some code
child: FutureBuilder<List<Product>>(
future: products,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
final products = snapshot.data!;
return Padding(
padding: const EdgeInsets.all(10.0),
child: ListView.builder(
itemCount: products.length, // currently has 400 items
itemBuilder: (context, index) {
if (difference == 0) {
return cardUI(
...
);
} else {
return Text('Nothing to display!');
}
}));
})),
How can I manage to return the message only one time? Do I need to change all the code? Since it's displaying almost 250 times 'Nothing to display'
Edit:
This is what I'm using to calculate the difference!
DateTime productExpire = DateTime.parse(products[index].date);
final productDate = productExpire;
final today = DateTime.now();
final difference = calcDays(
today, productDate);
The solution that comes to mind is to make products only equal to the products that have a difference of days from today of zero, so then based on products.length you can either return a Text() (if products.length == 0) or call ListView.builder (if products.length > 0).
Basically:
Instead of this:
All products
products = [thisProdHasDifferenceOfZero, thisOneDoesnt, thisOneDoes, thisOneDoesnt, ...]
(products.length == 400 every time)
You can just have:
Only products that you want to work with
products = [thisProdHasDiffOfZero, thisOneToo, thisOneToo, ...]
(products.length <= 400)
In your code:
Instead of calculating the difference your way, use this:
This method calculates the difference between two dates, the one you're using may run into some bugs... check this answer for more information
int daysBetween(DateTime from, DateTime to) {
from = DateTime(from.year, from.month, from.day);
to = DateTime(to.year, to.month, to.day);
return (to.difference(from).inHours / 24).round();
}
Then:
child: FutureBuilder<List<Product>>(
future: products,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
final today = DateTime.now();
final products = (snapshot.data! as List<Product>).where((product) => daysBetween(product.date, today) == 0).toList;
// Now you know that every product inside 'products', if there's any, has a day difference of 0.
return Padding(
padding: const EdgeInsets.all(10.0),
// 'products' can be empty since you may have 0 products that have the difference you are looking for.
// In that case you return the text.
child: (products.length == 0) ?
return Text('Nothing to display!');
: return ListView.builder( // If products has one or more items...
itemCount: products.length,
itemBuilder: (context, index) {
return cardUI(
...
);
}
)
...
You want to show a single error if the listview.builder does not have any data right? Hope I am not getting you wrong. If so you can use the ternary operator where you have mentioned the listview.builder to make it conditional.
or you can try to update the item count with condition.
itemcount: difference ==0? products.length : 1,
After getting all products filter them and create another list of product that will satisfy difference==0.
final products = snapshot.data!;
List<Product> temp = [];
// temp = products.where((p) => validation(p) == 0).toList();
for(final p in products)
{
int difference = yourValidation(p);
if(difference == 0) temp.add(p);
}
//...
child: ListView.builder(
itemCount: temp.length,
More about List

Flutter: How to add an extra ListTile to ListView manually

What I want to do is to add an extra LisTile to the last of ListView but I still don't figure it out.
My current code is like this.
child: ListView.builder(
itemBuilder: (context, index) {
if (index == 0) {
// Add an extra item to the start
return ListTile(
...
);
}
index -= 1;
final item = _items[index];
// I want to an extra item here as well
if (index == _items.length) {
return ListTile();
}
return ListTile(
...
);
},
itemCount: _items.length + 2,
I've already tried the way above, but it doesn't work. There is an error.
Invalid value: not in range 0..4, inclusive 5
When I changed itemCount to _items.length + 1, it doesn't show the extra ListTile I want to add to the end.
If you want to add to the beginning and the end as well check below
child: ListView.builder(
itemBuilder: (context, index) {
if (index == 0) {
// Add an extra item to the start
return ListTile(
...
);
}
if (index == _items.length + 1) {
return ListTile();
}
index -= 1;
final item = _items[index];
return ListTile(
...
);
},
itemCount: _items.length + 2,
your bug is here:
itemCount: _items.length + 2,
you should increase it by one
because you added 2 elements and for the last call you get index = list.length + 1, and then you subtract 1 from it, and you end up 1 over your length.
ex lets say your list have 5 elements, because you have
itemCount: _items.length + 2
Listview will call your func 7 times, and in 7th call your index is 6 and you are doing index =-1, which equals to 5, and 5 is over your range. (you have form 0 to 4)

How can I implement number of textfields in vertical fashion

Example: https://i.stack.imgur.com/svQfv.jpg
And also the number of text fields are not fixed.
Any help will be appreciated !!
Edit:
I declared an array of controllers and an array for storing the textfield values.
List<StringBuffer> _gpas;
List<TextEditingController> _controllers;
And this is how I'm generating the fields
ListView.builder(
itemCount: widget.semestersDone,
itemBuilder: (BuildContext context, int index) {
return _createGPAInputField(
context,
'Semester ${index + 1}',
_controllers[index],
onChanged: _updateCGPA,
);
})
And this is how I'm trying to access their values in _updateCGPA function
void _updateCGPA(dynamic gpa) {
for (int i = 0; i < widget.semestersDone; ++i) {
_gpas[i].clear();
_gpas[i].write(_controllers[i].text);
}
...
}