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,
);
}
}