tinuggunit
tinuggunit

Reputation: 39

ListView.builder returns to starting position after reaching the end of list and scrolling backwards

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

Answers (1)

Alperen Ekin
Alperen Ekin

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

Related Questions