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();
},
)
Related
guys
I'm trying to Create a list to add and delete items which are already stored in my database(MongoDB). I already created an Animated list that once its clicked on it deletes the item but as soon as i add the function to remove the item from the database it begins to show a loader as I used a ListView.builder
FutureBuilder(
builder: (context, projectSnap) {
if (projectSnap.connectionState == ConnectionState.none &&
projectSnap.hasData == null) {
//print('project snapshot data is: ${projectSnap.data}');
return Text('Nothing To show Here');
}else if(
projectSnap.connectionState == ConnectionState.waiting
) {
return Center(child: CircularProgressIndicator());
} else if(
projectSnap.connectionState == ConnectionState.active
) {
return AnimatedList(
padding: EdgeInsets.zero,
key: _listKey,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
initialItemCount: pairlenght,
itemBuilder: (BuildContext context, int index, animation) {
pair1 = pair[index].split(" ")[1];
pair0 = pair[index].split(" ")[0];
return _buildItem(pair, index, animation);
}
);
}
else{
return AnimatedList(
padding: EdgeInsets.zero,
key: _listKey,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
initialItemCount: pairlenght,
itemBuilder: (BuildContext context, int index, animation) {
pair1 = pair[index].split(" ")[1];
pair0 = pair[index].split(" ")[0];
return _buildItem(pair, index, animation);
}
);
}
},
future: fetchlist(),
),
This is the code for the list
void _removeSingleItems(int index) {
int removeIndex = index;
String removedItem = pair.removeAt(removeIndex);
// This builder is just so that the animation has something
// to work with before it disappears from view since the original
// has already been deleted.
AnimatedListRemovedItemBuilder builder = (context, animation) {
// A method to build the Card widget.
return _buildItem(removedItem, index, animation);
};
_listKey.currentState?.removeItem(removeIndex, builder);
}
This is the code to delete the item using Animated list
This is the how it works
So what I actually need is just a better alternative if listview.builder won't do the job.
Cause whenever I delete it loads again which I don't want
so, I am using a search Delegate to allow user search, however, when user is inputting data, it's really slow, it lags and skips frames as they type, because my search delegate loops through a list that contains about 2000 objects and checks user query against each object's title and main text, making about 4000 search criteria in total, this was causing the lag, so to solve this, I simply spawned a new isolate to handle the suggestions building, as such
Future<List<Data>> allData(String query) async {
List<Data> suggestions;
List<Data> myData(String message) {
return allData.where((data) {
final result = data.dataText.replaceAll(RegExp('[^A-Za-z0-9 ]'), '').toLowerCase();
final result1 = data.dataText.replaceAll(RegExp('[^A-Za-z0-9]'), '').toLowerCase();
final result2 = data.dataText.toLowerCase();
final result3 = data.dataText.replaceAll("th'incarnate", "the incarnate").toLowerCase();
final input = query.toLowerCase();
final result4 = data.dataTitle.toLowerCase();
final result5 = data.dataTitle.replaceAll(RegExp('[^A-Za-z0-9 ]'), '').toLowerCase();
final result6 =data.dataTitle.replaceAll(RegExp('[^A-Za-z0-9]'), '').toLowerCase();
return result.contains(input) ||
result1.contains(input) ||
result2.contains(input) ||
result3.contains(input) ||
result4.contains(input) ||
result5.contains(input) ||
result6.contains(input);
}).toList();
}
query.isEmpty
? suggestions = []
: suggestions = await compute<String, List<Data>>(
myData,
"",
);
if (query.isNotEmpty && suggestions.isEmpty) {
return [
Data(
DataTitle: "No Results",
DataText: "No Results",
),
];
} else {
return suggestions;
}
}
then in buildSuggestions I did this
#override
Widget buildSuggestions(BuildContext context) {
return FutureBuilder<List<Data>>(
future: DataBrain().allData(query),
builder: (context, snapshot) {
List<Data> suggestions;
query.isEmpty ? suggestions = [] : suggestions = snapshot.data!;
return ListView.builder(
itemCount: suggestions.length,
itemBuilder: (context, suggestIndex) {
final suggestion = suggestions[suggestIndex];
if (snapshot.connectionState == ConnectionState.done) {
if (suggestion.dataText.toLowerCase() == "no results") {
return const ListTile(
title: Text("No results"),
subtitle: Text("Please check for spelling mistakes"),
);
} else {
return ListTile(
title: Text(suggestion.dataTitle),
trailing: Text(
'${suggestion.dataBookName} ${suggestion.dataBookNumber == 0 ? '30a' : suggestion.dataBookNumber}',
style: const TextStyle(
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w300,
),
),
onTap: () {
close(context, suggestion);
},
);
}
} else {
return const ListTile(
title: Text('Loading...'),
);
}
});
});
}
it works fine without lags, however now, the suggestions don't build as user types, it waits till user is no longer typing, for like 2 seconds, then it builds suggestions, I think this is because it's a future and in a future builder, I'm not sure why. I'm thinking of turning it into a stream rather and listen to query change and build immediately instead of awaiting for two seconds after user stops querying, is there any workaround, what's a good way to eliminate the delay, i need it to filter the list and build suggestions with each keystroke, as it used to do before i moved it to a new isolate and made it a future
i have two future methods
Future<List> getTrns(String token,int page,) async {
final response = await
http.get(Uri.parse("api1/?params" ),
headers:{
....
});
final String t = response.body;
List list =jsonDecode(t)['data'];
return list;
}
Future<String> getstate(String code,String token,DateTime now) async {
DateTime start_date = new DateTime(now.year, now.month, now.day);
String stat='';
final response = await
http.get(Uri.parse("api2/?params" ),
headers:{
...
});
final String t = response.body;
List l = jsonDecode(t)['data'];
if(l.length == 0){
stat = "absent";
}else {
stat = "present";
}
return stat;
}
this first methode will get the list of employee while the second one will use each employee code to find their state from another api.. the two functions are working fine but displaying them in an Datatable gave me an error of
Instance of future<string>
Only in the stat cell while other cells are working fine
here's how i built the table
body: Column( children: <Widget>[
FutureBuilder<List>(
future: getTrns(widget.token,widget.page),
builder: (ctx,ss){
if(ss.hasError){
print("error");
}
if(ss.hasData){
datalist=ss.data!;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: _createDataTable(),
)
);
}else {
return Center(child: CircularProgressIndicator());
}
}
),
MaterialButton(onPressed: () {
widget.page = widget.page+1;
print(widget.page);
},
child: Text("Next"),
color: Colors.blue,)
]
)
)
);
}
DataTable _createDataTable() {
return DataTable(columns: _createColumns(), rows: _createRows());
}
List<DataColumn> _createColumns() {
return [
DataColumn(label: Text('ID')),
DataColumn(label: Text('Full Name')),
DataColumn(label: Text('State'))
];
}
List<DataRow> _createRows() {
return datalist
.map((list) => DataRow(cells: [
DataCell(
Text(list['emp_code'].toString()),
onTap: (){
Navigator.of(context).push(
MaterialPageRoute(builder: (BuildContext context)=>Details(token: w idget.token, emp:list['emp_code'].toString())
));
}
),
DataCell(
Text(list['first_name'].toString()+" "+list['last_name'].toString()),
onTap: (){
Navigator.of(context).push(
MaterialPageRoute(builder: (BuildContext context)=>Details(token: widget.token, emp:list['emp_code'].toString())
));
}
),
DataCell(Text( getstate(list['emp_code'], widget.token, widget.now).toString() ),
onTap: (){
Navigator.of(context).push(
MaterialPageRoute(builder: (BuildContext context)=>Details(token: widget.token, emp:list['emp_code'].toString())
));
}
)
]))
.toList();
}
}
this is the result
table screenshot
Edit1: new cell code cell code
the error is random, sometimes in first celland sometimes in a different one
first result
another result
You will need another FutureBuilder around getstate just as you did with getTrns.
Your code already demonstrates that you know how to use one, so I will not bore you with it.
If you need a refresher: What is a Future and how do I use it?
You may want to put your Future, into a variable instead of directly refering to them in your FutureBuilders though, because you want to keep the same Future (or rather it's probably already existing result) when the user trigger a rebuild that has nothing to do with your data, for example by tilting the screen of their device or changing the size of their browser or desktop window.
Try this
final Map<String, dynamic> t = response.body;
list =jsonDecode(t)['data'] as Map<String, dynamic>;
return list;
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.
I am trying to launch a 'How to Use' screen if the app launches for the first time using a FutureBuilder().
isFisrtTime() returns true if the app is launched for the first time.
Here is the FutureBuilder code:
body: FutureBuilder(
future: isFirstTime(),
initialData: Container(),
builder: (context, snapshot){
if(snapshot.data == true){
Navigator.pushNamed(context, '/howtouse');
return (currentScreen == 'Home')
? HomeBody()
: CBook(changeScreen: changeCurrentScreen);
}
else {
return (currentScreen == 'Home')
? HomeBody()
: CBook(changeScreen: changeCurrentScreen);
}
},
),
Please Help
You can't push a new Route during build. Move the logic to your initState instead.
#override
void initState() {
isFirstTime().then((data) {
if(data) {
Navigator.pushNamed(context, '/howtouse');
}
}
super.initState();
}