Reputation: 39
The following ListView returns a horizontal list of items of type CourseTile. Each item occupies around half the screen width. So at a time there are 2 CourseTiles visible on screen.
When I scroll through the list it functions properly till it reaches the last 2 items. Then when I start to scroll backwards and the third item from the end is about to be displayed on the screen, the ListView refreshes to its initial position, ie. rather than scrolling backwards, it just resets to the first items, skipping the items in between.
This is the ListView
child: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: false,
itemCount: this.courses != null && this.courses.length > 0
? this.courses.length
: 0,
itemBuilder: (context, position) {
final document = this.courses[position];
return CourseTile(
course: document,
minWidth: this.minWidth,
minHeight: this.minHeight,
isCohort: widget.isCohort,
);
},
),
This is the CourseTile file-
import 'dart:convert';
import 'package:intl/intl.dart';
import 'package:flutter/material.dart';
import 'package:masterlife/common_widgets/shimmer_Image.dart';
import 'package:masterlife/components/coming_soon.dart';
import 'package:masterlife/screens/course_detail/course_detail.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:masterlife/services/backend_api_service.dart';
class CourseTile extends StatefulWidget {
var course;
String toColor;
String fromColor;
double minWidth;
double minHeight;
bool isCohort;
bool fromAssessment;
CourseTile({
this.course,
this.minWidth,
this.minHeight,
this.fromColor,
this.toColor,
this.isCohort,
this.fromAssessment = false,
});
@override
_CourseTileState createState() => _CourseTileState();
}
class _CourseTileState extends State<CourseTile> {
List<DocumentSnapshot> tags = [];
List cohortDates = [];
final courseService = new BackendAPIService();
var fromDate, toDate, slots, course;
Map cohortInstances = {};
var category;
@override
void initState() {
super.initState();
if (this.widget.course.runtimeType.toString() != "DocumentSnapshot") {
this.category = this.widget.course["category"];
} else {
this.widget.course["category"].get().then((currentCategory) {
if (!mounted) return;
//This is firing unexpectedly when scrolling backwards
setState(() {
this.category = currentCategory;
});
});
}
if (widget.isCohort == true) {
courseService.listUpcomingCohort("instructor", null).then((e) {
if (!mounted) return;
setState(() {
print('statnig');
this.cohortDates = json.decode(e);
});
loadData();
});
}
}
loadData() {
for (var i = 0; i < cohortDates.length; i++) {
var from = new DateFormat("d MMM").format(
new DateTime.fromMillisecondsSinceEpoch(
cohortDates[i]["from"]["_seconds"] * 1000));
var to = new DateFormat("d MMM").format(
new DateTime.fromMillisecondsSinceEpoch(
cohortDates[i]["to"]["_seconds"] * 1000));
var spotsRemaining = cohortDates[i]["remainingSeats"].toString();
var courseId = cohortDates[i]["course"]["id"].toString();
cohortInstances[courseId] = {
"fromDate": from,
"toDate": to,
"remainingSlots": spotsRemaining
};
}
}
@override
Widget build(BuildContext context) {
bool isDocSnap =
this.widget.course.runtimeType.toString() == "DocumentSnapshot";
if (isDocSnap) if (this.category == null) {
return Container();
}
Widget mainComponent = GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CourseDetail(
course: isDocSnap ? this.widget.course : null,
courseId: isDocSnap ? null : this.widget.course["id"],
courseCategory: this.category["name"]),
settings: RouteSettings(
name: "Course Details of ${this.widget.course["name"]}")),
);
},
child: Container(
margin: EdgeInsets.only(right: 10),
child: Stack(
children: <Widget>[
ClipRRect(
borderRadius: new BorderRadius.circular(14),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color(int.parse(
'0x${this.widget.fromColor == null ? this.category["fromColor"] : this.widget.fromColor}'))
.withOpacity(0.75),
Color(int.parse(
'0x${this.widget.fromColor == null ? this.category["toColor"] : this.widget.fromColor}'))
.withOpacity(0.75)
],
),
),
child: this.widget.course["coming_soon"] == true
? Container(
width: MediaQuery.of(context).size.width * 0.6,
height: MediaQuery.of(context).size.width * 2,
)
: ShimmerImage(
imageUrl: this.widget.course["image"],
cornerRadius: 14,
fit: BoxFit.cover,
width: MediaQuery.of(context).size.width * 0.6,
height: MediaQuery.of(context).size.width * 2,
)),
),
ClipRRect(
borderRadius: new BorderRadius.circular(14),
child: Container(
padding: (widget.fromAssessment == false)
? (cohortInstances
.containsKey(this.widget.course.documentID))
? EdgeInsets.all(0)
: EdgeInsets.only(bottom: 15, left: 8)
: EdgeInsets.only(bottom: 15, left: 8),
alignment: Alignment.bottomCenter,
width: MediaQuery.of(context).size.width * 0.6,
height: MediaQuery.of(context).size.width * 0.6,
child: Container(
child: Stack(
children: <Widget>[
if (this.widget.course["is_free"] == true)
Container(
padding: EdgeInsets.only(right: 10, top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ClipRRect(
borderRadius: new BorderRadius.circular(4),
child: Container(
color: Colors.grey.withOpacity(0.6),
padding: EdgeInsets.only(
top: 2, bottom: 2, left: 5, right: 5),
child: Text(
"Free",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
letterSpacing: 0.36,
),
),
),
),
],
),
),
Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
if (this.widget.course["coming_soon"] == true)
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
"Coming soon",
style: Theme.of(context)
.textTheme
.button
.copyWith(
letterSpacing: 0.4,
),
),
),
Padding(
padding: EdgeInsets.only(left: 8, right: 4),
child: Text(
this.widget.course["name"],
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(
letterSpacing: 0.4,
fontWeight: FontWeight.w600,
),
),
),
if (widget.fromAssessment == false)
if (cohortInstances.containsKey(
this.widget.course.documentID) &&
widget.isCohort == true)
Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
height: MediaQuery.of(context)
.size
.height *
0.06,
width: MediaQuery.of(context)
.size
.width *
0.60,
child: ClipRRect(
borderRadius: new BorderRadius.only(
topLeft: Radius.circular(0),
topRight: Radius.circular(0),
bottomLeft: Radius.circular(14),
bottomRight: Radius.circular(14),
),
child: Container(
padding: EdgeInsets.only(
left: 10, right: 8),
color: Color(int.parse(
'0x${this.category['fromColor']}'))
.withOpacity(0.9),
width: MediaQuery.of(context)
.size
.width,
height: MediaQuery.of(context)
.size
.height,
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text(
"${cohortInstances[this.widget.course.documentID]["remainingSlots"] == "0" ? "Spots Filled" : cohortInstances[this.widget.course.documentID]["remainingSlots"] + " Spots Left"}",
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(
color: Colors
.black,
fontSize: 12,
fontWeight:
FontWeight
.bold),
),
Text(
"${cohortInstances[this.widget.course.documentID]["fromDate"]} - ${cohortInstances[this.widget.course.documentID]["toDate"]}",
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(
color:
Colors.black,
fontSize: 12,
fontWeight:
FontWeight
.w600,
),
)
],
),
Text(
"Join Now",
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(
color: Colors.black,
fontSize: 12,
),
),
],
),
),
),
),
],
),
),
],
),
],
),
],
),
),
),
),
],
),
),
);
if (this.widget.course["coming_soon"] == null ||
this.widget.course["coming_soon"] == false) {
return mainComponent;
}
return ComingSoon(
child: mainComponent,
type: "Course",
featureName: this.widget.course["name"],
);
}
}
The setState function is firing up when I start scrolling backwards(when the third item from the end is about to appear on screen)
How do I fix this?
Upvotes: 0
Views: 1182
Reputation: 318
To keep the state, you can try to use AutomaticKeepAliveClientMixin
class _CourseTileState extends State<CourseTile> with AutomaticKeepAliveClientMixin{
Your code here
@override
bool get wantKeepAlive => true;
}
Upvotes: 2