Flutter CircularProgressIndicator on top of syncfusion_calendar

671 views Asked by At

I am struggling with correct futurebuilder positioning for last few days. Im using syncfusion_calendar package to display json data from my API, where i call a new reqest to API every time user changes calendars month. The problem is that user is not being told about ongoing data downlad and i would love to do that by showing CircularProgressIndicator instead of calendar while its loading.

my pubspec file just in case :

name: flutter_viaapp_startmenu
description: A new Flutter application.

# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  firebase_core: ^0.5.0+1
  cloud_firestore: ^0.14.4
  firebase_messaging: ^7.0.3
  firebase_in_app_messaging: 0.2.3
  webview_flutter: ^1.0.7
  flutter_staggered_grid_view: ^0.3.3
  easy_localization: ^2.3.2
  intl: ^0.16.1
  http: ^0.12.2
  syncfusion_flutter_calendar: ^18.3.51
  shared_preferences: ^0.5.12
  flutter_local_notifications: ^3.0.3

  flutter_localizations:
    sdk: flutter
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter.
flutter:
  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  assets:
    - assets/menu.jpg
    - assets/welcome.jpg
    - assets/translations/en.json
    - assets/translations/lv.json

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware.

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

My main calendar file :

import 'dart:async';
import 'dart:convert';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
import 'package:intl/intl.dart';

class Lecture_graph extends StatefulWidget {
  Lecture_graph({Key key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _MyLecturesGraphState();
}

class _MyLecturesGraphState extends State<Lecture_graph> {
  Future<List<Lecture>> _future;
  List<Lecture> lectures;
  DateTime _selectedDate = new DateTime.now();
  List<LectureTime> _times;

  //TODO make this empty after SO post
  var coursecode = "IT3";

  @override
  void initState() {
    _selectedDate = new DateTime(
        _selectedDate.year,
        _selectedDate.month,
        15,
        _selectedDate.hour,
        _selectedDate.minute,
        _selectedDate.second,
        _selectedDate.millisecond,
        _selectedDate.microsecond);
    _future = downloadData();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(coursecode + " | Lectures"),
          actions: [
            LecturesNavigationControls(),
          ],
        ),
        body: lectureGraphList());
  }

  Future<String> _checkSavedCourse() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();

    String _coursecode = prefs.getString('savedCourse');

    if (_coursecode == "" || _coursecode == null) {
      Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => CourseSelectionPage()),
      );
      return null;
    } else {
      return _coursecode;
    }
  }

  Widget lectureGraphList() {
    return FutureBuilder<List<Lecture>>(
      future: _future,
      builder: (BuildContext context, AsyncSnapshot<List<Lecture>> snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Center(
            child: SizedBox(
              child: const Expanded(
                  child: Center(child: const CircularProgressIndicator())),
              width: 100,
              height: 100,
            ),
          );
        } else {
          if (snapshot.hasError)
            return Center(child: Text('Error: ${snapshot.error}'));
          else
            return showLectures(lectures);
        }
      },
    );
  }


//THIS FUNCTION IS CALLED EVERY TIME TO DOWNLOAD NEW DATA FROM API

  Future<List<Lecture>> downloadData() async {
    //get saved course
    if (coursecode == "" || coursecode == null) {
      coursecode = await _checkSavedCourse();
      //debug
      print('courscode recieved from sharedprefs');
    }

    //if there is no date selected, select today
    if (_selectedDate == null) _selectedDate = new DateTime.now();

    //build request URL
    var requestURL =
        'https://lekcijas.va.lv/lekcijas_android/getMonthLectures.php?date=' +
            DateFormat('yyyy-MM').format(_selectedDate) +
            "&breaks&program=" +
            coursecode;
    //wait for response
    var response = await http.get(requestURL);
    var data = json.decode(response.body)["result"];

    //clear array after each request
    if (lectures != null) lectures.clear();

    try {
      //create lectures from json response
      lectures = List<Lecture>.from(data.map((x) => Lecture.fromJson(x)));
      _getDataSource(lectures);
    } catch (e) {
      print(e.toString());
    }

    return Future.value(lectures);
  }

  Widget showLectures(List<Lecture> lectures) {
    return Card(
      child: Row(
        children: [
          Expanded(
              child: SfCalendar(
                  view: CalendarView.month,
                  firstDayOfWeek: 1,
                  onViewChanged: (ViewChangedDetails details) {
                    if (_selectedDate.month != details.visibleDates[15].month) {
                      WidgetsBinding.instance.addPostFrameCallback((_) {
                        _selectedDate = details.visibleDates[15];


                          setState(() {



//CALENDAR MONTH CHANGE IS CALLED HERE 



                              downloadData();
                

        });
                      });
                    }
                  },
                  dataSource: LectureTimeDataSource(_times),
                  monthViewSettings: MonthViewSettings(
                      appointmentDisplayMode:
                          MonthAppointmentDisplayMode.indicator,
                      showAgenda: true,
                      agendaStyle: AgendaStyle(
                          appointmentTextStyle:
                              TextStyle(color: Colors.black))),
                  showNavigationArrow: true))
        ],
      ),
    );
  }

  void _getDataSource(List<Lecture> lectures) {
    var lectureTimes = <LectureTime>[];
    lectures.forEach((element) {
      lectureTimes.add(LectureTime(
          (element.classroom + "  " + element.lecture),
          DateTime.parse(element.datums + " " + element.start),
          DateTime.parse(element.datums + " " + element.end),
          hexToColor(element.color),
          false));
    });

    setState(() {
      _times = lectureTimes;
    });
  }
}

class LecturesNavigationControls extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        IconButton(
          //TODO find normal icon
          icon: const Icon(Icons.wheelchair_pickup),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => CourseSelectionPage()),
            );
          },
        ),
      ],
    );
  }
}

class LectureTimeDataSource extends CalendarDataSource {
  LectureTimeDataSource(List<LectureTime> source) {
    appointments = source;
  }

  @override
  DateTime getStartTime(int index) {
    return appointments[index].from;
  }

  @override
  DateTime getEndTime(int index) {
    return appointments[index].to;
  }

  @override
  String getSubject(int index) {
    return appointments[index].eventName;
  }

  @override
  Color getColor(int index) {
    return appointments[index].background;
  }

  @override
  bool isAllDay(int index) {
    return appointments[index].isAllDay;
  }
}

class LectureTime {
  LectureTime(
      this.eventName, this.from, this.to, this.background, this.isAllDay);

  String eventName;
  DateTime from;
  DateTime to;
  Color background;
  bool isAllDay;
}

Color hexToColor(String code) {
  return new Color(int.parse(code.substring(1, 7), radix: 16) + 0xFF000000);
}


class Lecture{
  final String programs;
  final String lecture;
  final String lecturer;
  final String start;
  final String end;
  final String classroom;
  final String color;
  final String datums;

  Lecture({this.programs, this.lecture, this.lecturer, this.start, this.end, this.classroom, this.color, this.datums});

  factory Lecture.fromJson(Map<String, dynamic> json) {
    return Lecture(
      programs: json['nodala'] as String,
      lecture: json['kurss'] as String,
      lecturer : json['lektors'] as String,
      start: json['sakums'] as String,
      end: json['beigas'] as String,
      classroom: json['nosaukums'] as String,
      color: json['iela'] as String,
      datums: json['datums'] as String,
    );
  }
}


class LectureTime {
  LectureTime(
      this.eventName, this.from, this.to, this.background, this.isAllDay);

  String eventName;
  DateTime from;
  DateTime to;
  Color background;
  bool isAllDay;
}

please use "IT3" string as coursecode for API. API request url example here

2

There are 2 answers

3
user14625508 On

Based on the provided information and code snippet, we have checked, and your requirement is “Showing the CircularProgressIndicator when loading calendar”. We have prepared a simple sample for loading circular progress indicator with loading online data to the calendar. Please find the sample from the following link,

Sample link: https://www.syncfusion.com/downloads/support/directtrac/312448/ze/minimum_appointmentduration298839661.zip

Also, we have a KB document for loading the online data to Flutter calendar with a simple loading text message. In the same way, you will use CircularProgressIndicator.

KB link: https://www.syncfusion.com/kb/11568/how-to-load-the-json-data-online-to-the-flutter-event-calendar-sfcalendar-appointments

We hope that the above sample and KB helps you. Please let us know if you need further assistance.

2
scheinpablo On

You can add a variable called downloadingData and set it to false by default. Then, before calling downloadData() set it to true and when the function finishes, set it back to false. Finally, inside the build method: child: downloadingData ? CircularProgressIndicator() : SfCalendar(...)