Flutter CircularProgressIndicator on top of syncfusion_calendar - android-studio

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

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(...)

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.

Related

Creating multiple Flutter nofitications with flutter_local_notification

Currently I'm using a loading page to avoid having a big lag when the user is being redirected to the home page while the notifications are being created on the same thread (the UI thread which leads to lots of dropped frames).
I tried using the compute dart function but the issue is that this function requires using static methods and you can't pass it objects.
So I would appreciate some hints on how to use a thread to create the notifications.
PS: in the worst scenario the app is creating 7*24 notifications(24 for each day of the week) which is slow even on high end devices.
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter/material.dart';
import '../pages/home_page/home_page.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import 'data.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:auto_size_text/auto_size_text.dart';
class NotificationLoading extends StatefulWidget {
const NotificationLoading({Key? key}) : super(key: key);
#override
_NotificationLoadingState createState() => _NotificationLoadingState();
}
class _NotificationLoadingState extends State<NotificationLoading> {
#override
void initState() {
super.initState();
manageNotifications();
}
Future<void> manageNotifications() async {
await Future.delayed(
const Duration(seconds: 1),
); // Let time to build the widget
await Notifications(ctx: context).manageNotifications();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
CircularProgressIndicator(),
Padding(
padding: EdgeInsets.fromLTRB(0, 15, 0, 0),
child: AutoSizeText(
"Loading notifs",
style: TextStyle(fontSize: 30),
),
)
],
),
),
);
}
}
class Notifications {
static const channelId = "coolID";
static const channelName = "cool";
Data data = Data();
int id = 0;
BuildContext ctx;
Notifications({required this.ctx});
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
// check if notifications are already setup, if not setup notifications
// otherwise notifications only need to be changed inside the timer_page
Future<void> manageNotifications() async {
final prefs = await SharedPreferences.getInstance();
bool isNotificationSetup = prefs.getBool('isNotificationSetup') ?? false;
if (!isNotificationSetup) {
await _initialization();
await _scheduleNotifications();
await prefs.setBool('isNotificationSetup', true);
Navigator.pop(ctx);
await Navigator.push(
ctx,
MaterialPageRoute<void>(builder: (context) => const HomePage()),
);
}
}
Future<void> _initialization() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('app_icon');
const InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: _selectNotification);
}
// Schedule notifications based on user settings
Future<void> _scheduleNotifications() async {
// Init the time zone, needed for notification scheduling
tz.initializeTimeZones();
final String? timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
tz.setLocalLocation(tz.getLocation(timeZoneName!));
await data.getData();
int delta = (data.endTime.minute + data.endTime.hour * 60) -
(data.startTime.minute + data.startTime.hour * 60);
double interval = delta / data.reminderNumber;
data.checkedDays.forEach((day, values) {
if (values[1]) {
double minute = data.startTime.minute + (data.startTime.hour * 60);
for (int reminder = 0; reminder < data.reminderNumber; reminder++) {
int tmpHour = (minute - minute % 60) ~/ 60;
int tmpMinute = (minute.round()) % 60;
_createScheduledNotification(
_nextInstanceOfDayHourMinute(tmpHour, tmpMinute, values[0]), id);
minute += interval;
id++;
}
}
});
}
// Create a scheduled notification
void _createScheduledNotification(tz.TZDateTime time, int id) async {
await flutterLocalNotificationsPlugin.zonedSchedule(
id,
AppLocalizations.of(ctx)!.notificationTitle,
AppLocalizations.of(ctx)!.notificationMessage,
time,
const NotificationDetails(
android: AndroidNotificationDetails(
'weekly notification channel id',
'New citation message',
channelDescription:
'Notifications for new citations configured in the timer page.',
sound: RawResourceAndroidNotificationSound('notification_sound'),
groupKey: "meditation invitation",
),
),
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.dayOfWeekAndTime);
}
// Find next instance DateTime object
tz.TZDateTime _nextInstanceOfHourMinute(int hour, int minute) {
final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
tz.TZDateTime scheduledDate =
tz.TZDateTime(tz.local, now.year, now.month, now.day, hour, minute);
if (scheduledDate.isBefore(now)) {
scheduledDate = scheduledDate.add(const Duration(days: 1));
}
return scheduledDate;
}
// Find next instance DateTime object
tz.TZDateTime _nextInstanceOfDayHourMinute(int hour, int minute, int day) {
tz.TZDateTime scheduledDate = _nextInstanceOfHourMinute(hour, minute);
while (scheduledDate.weekday != day) {
scheduledDate = scheduledDate.add(const Duration(days: 1));
}
return scheduledDate;
}
// triggered function when the user tap on a notification
void _selectNotification(String? payload) async {
if (payload != null) {
debugPrint('notification payload: $payload');
}
await Navigator.push(
ctx,
MaterialPageRoute<void>(builder: (context) => const HomePage()),
);
}
}
You can't handle too many notifications at time in iOS platform. Instead of that you need again set or schedule notifications after few old notification received otherwise old notifications overlaps & it will not schedule.
You can check this link & my answer I hope it will help you.
Dart/Flutter is single threaded and not possible multi threading. As each isolate has its own memory,space and everything. To make it work like multi threaded you have to use isolates and the communication will be used through ports by sending message to one another. If you not want to use Future you can use isolates.
Read
https://medium.com/flutter-community/flutter-threading-5c3a7b0c065f
https://www.tutorialspoint.com/dart_programming/dart_programming_concurrency.htm
https://pub.dev/packages/threading
So you can use Future Or isolate.
Great François. You already in a good path. I guess i could give you some hints to accomplish what you want.
First of all Flutter require top level functions to run isolates so you have to put it outside of a class.
To transfer data to an isolate it must be serialized. In my example i use jsonEncode to send it as a string and parse it with jsonDecode to retrieve as a dynamic list inside the isolate runner.
When i wrote this code i read about some limitations of the use of plugins inside isolate (I don`t know about the current state). So i found a solution for this using Flutter Isolate plugin (https://pub.dev/packages/isolate_handler)
I use the function killScheduleNotifications as a way to control when the code is running and avoid to create duplications. I always cancel all the scheduled and recreate it every time.
For reference (https://gist.github.com/taciomedeiros/50472cf94c742befba720853e9d598b6)
final IsolateHandler isolateHandler = IsolateHandler();
void scheduleNotificationsIsolate(String _reminders) async {
await new Future.delayed(new Duration(milliseconds: 500));
// ... (describe settings)
flutterLocalNotificationsPlugin.initialize(
settings,
onSelectNotification: onSelectNotification,
);
await flutterLocalNotificationsPlugin.cancelAll();
List<dynamic> _remindersParsed = jsonDecode(_reminders);
for (// iterate over your entities to show the message) {
int generatedId = id ?? random.nextInt(1000000000);
await flutterLocalNotificationsPlugin.schedule(
generatedId,
title,
message,
scheduledNotificationDateTime,
platformSpecifics,
payload: payload,
);
}
killCurrentScheduleNotifications();
}
startScheduleNotifications(String _remindersAsString) {
killCurrentScheduleNotifications();
isolateHandler.spawn<String>(
entryPoint,
name: "scheduleNotifications",
onReceive: scheduleNotificationsIsolate,
onInitialized: () => isolateHandler.send(
_remindersAsString,
to: "scheduleNotifications",
),
);
}
void killCurrentScheduleNotifications() {
if (isolateHandler.isolates.containsKey('scheduleNotifications'))
isolateHandler.kill('scheduleNotifications');
}
void entryPoint(Map<String, dynamic> context) {
final messenger = HandledIsolate.initialize(context);
messenger.listen((message) {
messenger.send(message);
});
}

How to find out the words start with # in string

I want to find out all the words which starts from #.
For example = This is a #tag
I want to create this(#tag) tappable and change its color to blue.
Please advise something!
Text("This is a #tag"),
You can achieve the desired result using RegEx in only 2 lines:
List<String> extractHashtags(String text) {
Iterable<Match> matches = RegExp(r"\B(\#[a-zA-Z]+\b)").allMatches(text);
return matches.map((m) => m[0]).toList();
}
You can use Regex to find all words starting with the character #. Then use Text.rich or RichText's InlineSpan widgets to show just the matched words in blue and WidgetSpan widget to make the matched words tappable. Please see the code below or directly run it on DartPad https://dartpad.dev/c1fde787afc2792efd973752a7284d03
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text("Flutter Demo"),
),
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final List<InlineSpan> textSpans = [];
final String text = "This is a #tag. This is #tag2. This is #tag3.";
final RegExp regex = RegExp(r"\#(\w+)");
final Iterable<Match> matches = regex.allMatches(text);
int start = 0;
for (final Match match in matches) {
textSpans.add(TextSpan(text: text.substring(start, match.start)));
textSpans.add(WidgetSpan(
child: GestureDetector(
onTap: () => print("You tapped #${match.group(1)}"),
child: Text('#${match.group(1)}',
style: const TextStyle(color: Colors.blue)))));
start = match.end;
}
textSpans.add(TextSpan(text: text.substring(start, text.length)));
return Text.rich(TextSpan(children: textSpans));
}
}
here is the code I wrote. haven't tested it but hope it works!
List<String> hashAdder(String value) {
List<String> tags = [];
bool hashExist = value.contains('#');
if (hashExist) {
for (var i = 0; i < value.length; i++) {
int hashIndex = value.indexOf('#');
int hashEnd = value.indexOf(' ', hashIndex);
tags.add(value.substring(hashIndex, hashEnd));
value = value.substring(hashIndex);
}
}
return tags;
}
First, you can use split() method to get a list of words in your sentence. Then, you can check for each word in that list, and if that starts with #, then you can add that into the hashTag list. It will look like this:
String s = "This is a #tag";
List<String> splitted = s.split(" ");
List<String> hashTags = List<String>();
for (var item in splitted) {
if (item.startsWith("#")) {
hashTags.add(item);
}
}
print(hashTags);
// Expected output is: [#tag]

Loading URL with unique Android or iOS device id in webview

I know how to get the device unique id in flutter, but when I add it as a parameter to url and call it with webview, I can't get the result I want. When I call the following function, webview is loaded with empty device id parameter because it is working ASYNC and not yet completed and not returning any value.
Future<String> _getId() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if (Theme.of(context).platform == TargetPlatform.iOS) {
IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo;
return iosDeviceInfo.identifierForVendor; // unique ID on iOS
} else {
AndroidDeviceInfo androidDeviceInfo = await deviceInfo.androidInfo;
return androidDeviceInfo.androidId; // unique ID on Android
}
}
The webview loading function is below;
String webViewUrl;
_getId().then((id) {
webViewUrl = "http://websiteblabla.com/?deviceid="+id;
return WebviewScaffold(
url: webViewUrl,
withJavascript: true,
withZoom: false
);
}
How can I get URL with unique Android or iOS device id in webview when it is ready?
You can create Future function or method which will return WebviewScaffold widget when everything is ready. And the use it with FutureBuilder. It would look something like this.
FutureBuilder(
future: _buildWebview(),
builder: (context, snapshot) {
if(!snapshot.hasData) return Center(
child: CircularProgressIndicator(),
);
return snapshot.data;
},
),
Future<Widget> _buildWebview() async {
await _getId().then((id) {
webViewUrl = "http://websiteblabla.com/?deviceid="+id;
return WebviewScaffold(
url: webViewUrl,
withJavascript: true,
withZoom: false
);
}
}

Dart Map and Parsing JSON

As a new Dart Fan, I would like to understand the concept of Map/List.
I tried to do HTTP requests, getting JSON data. And it's ok till I have to assign to the Map.
Let me show you the example of JSON data:
{
"error":"",
"error_number":"",
"response_code":200,
"result":[
{
"id":1,
"name":"Great Deal",
"day_aired":"2015-07-05 11:06:09",
"trend":"Noone",
"trend_details": [{
"name":"Great Deal",
}
]
},
{
"id":2,
....
}
]
}
The code:
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<ApiResponse> fetchData(String command, Map params) async {
final String url =
'https://example.com/api/v2/....';
final response = await http.get(url);
if (response.statusCode == 200) {
return ApiResponse.fromJson(json.decode(response.body));
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
}
final response = await http.get(url);
dynamic data = json.decode(response.body);
List<String> parsed = data['result'] as List<String>;
// List<String> parsedList = new List<String>.from(parsed);
if (response.statusCode == 200) {
//return json.decode(response.body);
List<ApiResponse> list = List<ApiResponse>.from(
parsed.map((i) => ApiResponse.fromJson(i as Map<String, dynamic>)));
}
I do the same as this article. But I read this article too. I'm trying to create Future<ApiResponse> with data from json.decode(response.body) (result entry inside of it).
factory ApiResponse.fromJson(Map<String, dynamic> json) {...}
But as I understand, result is not Map<String, dynamic> but when I try to invoke the code below it says:
Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'List<String>' in type cast and it referred to List<String> parsed = data['result'] as List<String>;.
I'm confused and I know the code is a mess. I read in the second article that I should do an additional cast to trend_details but it did not work as I expected. Obviously data['result'] is an array but how to cast it properly? What are the good practices?
result stores a list of Map<String, dynamic>
final parsed = data['result'] as List<Map<String, dynamic>>;
You can parse json string with the following structure and code
You can see picture display correct result name
code snippet
var payload = payloadFromJson(jsonStr);
print('${payload.result[0].name}');
related class
// To parse this JSON data, do
//
// final payload = payloadFromJson(jsonString);
import 'dart:convert';
Payload payloadFromJson(String str) => Payload.fromJson(json.decode(str));
String payloadToJson(Payload data) => json.encode(data.toJson());
class Payload {
String error;
String errorNumber;
int responseCode;
List<Result> result;
Payload({
this.error,
this.errorNumber,
this.responseCode,
this.result,
});
factory Payload.fromJson(Map<String, dynamic> json) => Payload(
error: json["error"] == null ? null : json["error"],
errorNumber: json["error_number"] == null ? null : json["error_number"],
responseCode: json["response_code"] == null ? null : json["response_code"],
result: json["result"] == null ? null : List<Result>.from(json["result"].map((x) => Result.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"error": error == null ? null : error,
"error_number": errorNumber == null ? null : errorNumber,
"response_code": responseCode == null ? null : responseCode,
"result": result == null ? null : List<dynamic>.from(result.map((x) => x.toJson())),
};
}
class Result {
int id;
String name;
DateTime dayAired;
String trend;
List<TrendDetail> trendDetails;
Result({
this.id,
this.name,
this.dayAired,
this.trend,
this.trendDetails,
});
factory Result.fromJson(Map<String, dynamic> json) => Result(
id: json["id"] == null ? null : json["id"],
name: json["name"] == null ? null : json["name"],
dayAired: json["day_aired"] == null ? null : DateTime.parse(json["day_aired"]),
trend: json["trend"] == null ? null : json["trend"],
trendDetails: json["trend_details"] == null ? null : List<TrendDetail>.from(json["trend_details"].map((x) => TrendDetail.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"id": id == null ? null : id,
"name": name == null ? null : name,
"day_aired": dayAired == null ? null : dayAired.toIso8601String(),
"trend": trend == null ? null : trend,
"trend_details": trendDetails == null ? null : List<dynamic>.from(trendDetails.map((x) => x.toJson())),
};
}
class TrendDetail {
String name;
TrendDetail({
this.name,
});
factory TrendDetail.fromJson(Map<String, dynamic> json) => TrendDetail(
name: json["name"] == null ? null : json["name"],
);
Map<String, dynamic> toJson() => {
"name": name == null ? null : name,
};
}
full code
import 'package:flutter/material.dart';
// To parse this JSON data, do
//
// final payload = payloadFromJson(jsonString);
import 'dart:convert';
Payload payloadFromJson(String str) => Payload.fromJson(json.decode(str));
String payloadToJson(Payload data) => json.encode(data.toJson());
class Payload {
String error;
String errorNumber;
int responseCode;
List<Result> result;
Payload({
this.error,
this.errorNumber,
this.responseCode,
this.result,
});
factory Payload.fromJson(Map<String, dynamic> json) => Payload(
error: json["error"] == null ? null : json["error"],
errorNumber: json["error_number"] == null ? null : json["error_number"],
responseCode: json["response_code"] == null ? null : json["response_code"],
result: json["result"] == null ? null : List<Result>.from(json["result"].map((x) => Result.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"error": error == null ? null : error,
"error_number": errorNumber == null ? null : errorNumber,
"response_code": responseCode == null ? null : responseCode,
"result": result == null ? null : List<dynamic>.from(result.map((x) => x.toJson())),
};
}
class Result {
int id;
String name;
DateTime dayAired;
String trend;
List<TrendDetail> trendDetails;
Result({
this.id,
this.name,
this.dayAired,
this.trend,
this.trendDetails,
});
factory Result.fromJson(Map<String, dynamic> json) => Result(
id: json["id"] == null ? null : json["id"],
name: json["name"] == null ? null : json["name"],
dayAired: json["day_aired"] == null ? null : DateTime.parse(json["day_aired"]),
trend: json["trend"] == null ? null : json["trend"],
trendDetails: json["trend_details"] == null ? null : List<TrendDetail>.from(json["trend_details"].map((x) => TrendDetail.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"id": id == null ? null : id,
"name": name == null ? null : name,
"day_aired": dayAired == null ? null : dayAired.toIso8601String(),
"trend": trend == null ? null : trend,
"trend_details": trendDetails == null ? null : List<dynamic>.from(trendDetails.map((x) => x.toJson())),
};
}
class TrendDetail {
String name;
TrendDetail({
this.name,
});
factory TrendDetail.fromJson(Map<String, dynamic> json) => TrendDetail(
name: json["name"] == null ? null : json["name"],
);
Map<String, dynamic> toJson() => {
"name": name == null ? null : name,
};
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
String jsonStr = '''
{
"error":"",
"error_number":"",
"response_code":200,
"result":[
{
"id":1,
"name":"Great Deal",
"day_aired":"2015-07-05 11:06:09",
"trend":"Noone",
"trend_details": [{
"name":"Great Deal"
}
]
}
]
}
''';
void _incrementCounter() {
var payload = payloadFromJson(jsonStr);
print('${payload.result[0].name}');
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}

How to setState() on a ModalRoute?

I write an app in flutter. I want to change the state of a string variable. After I set the state of the string variable, the ModalRoute PopUpMenu does not show the changed variable. If I close the ModalRoute PopUpMenu and open it again, I can see the changed variable.
I tried to pop the context, but I want the change on the PopUpMenu. I've got my own Overlay widget.
class MyOverlay extends ModalRoute {
...
}
// this is my main.dart:
List<String> categories = ['please', 'help', 'me'];
String _selectedCategory = 'category';
// this is where the PopUpMenu starts
floatingActionButton: FloatingActionButton(
child: ...,
onPressed: () {
_showPopup(context, _popupBody(), 'Add');
},
),
_showPopup(BuildContext context, Widget widget, String title, {BuildContext popupContext}) {
Navigator.push(
context,
MyOverlay(
...
onPressed: () {
try {
Navigator.pop(context); //close the popup
} catch (e) {
print(e);
}
},
...
body: widget,
) ...
);
}
Widget _popupBody() {
...
PopupMenuButton<String>(
// HERE IS THE PROBLEM THIS SHOULD CHANGE WHEN I SELECT
child: Text('$_selectedCategory'),
itemBuilder: (BuildContext context) {
return categories.map((String choice) {
return PopupMenuItem<String>(
value: choice,
child: Text(choice),
);
}).toList();
},
onSelected: _selectCategory,
),
...
}
void _selectCategory(String category) {
setState(() => this._selectedCategory = category);
}
The text widget does not change if I select the PopupMenuItem.
I have the same problem, I fixed for the moment using changedExternalState (); to force a rebuild, but i think maybe this isn't optimal.
Example:
CheckboxListTile(
value: _checkboxValue,
title: Text(phone),
onChanged: (value){
_checkboxValue = value;
//Fix
changedExternalState();
},
)

Resources