Flutter: Need to load values and then make a firebase query for Futurebuilder caused RangeError (index) - flutter

I'm trying to load the geo location first. Then I use this value to start a query which events are in a certain radius from this location.
I want to display these events in a FutureBuilder.
My problem:
I have to initialize Future<List> futureEvents in the onInit state, otherwise Flutter complains. At the time he didn't have the location yet.
So I call the function again at the end.
So it happens that the error "RangeError (index): Invalid value: Valid value range is empty :1" is thrown until the method is called again after receiving the geo-location and I get the data.
Sorry for bad coding. One of my first Projects
The InitState:
class _LocationPageState extends State<LocationPage> {
String? _currentAddress;
Position? _currentPosition;
late Future<List<Events>> futureEvents;
double locationRadius = 5;
#override
void initState() {
super.initState();
_getCurrentPosition();
futureEvents = _getEvents();
}
The called functions:
Future<void> _getCurrentPosition() async {
final hasPermission = await _handleLocationPermission();
if (!hasPermission) return;
await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high)
.then((Position position) {
setState(() => _currentPosition = position);
_getAddressFromLatLng(_currentPosition!);
}).catchError((e) {
debugPrint(e);
});
}
Future<void> _getAddressFromLatLng(Position position) async {
await placemarkFromCoordinates(
_currentPosition!.latitude, _currentPosition!.longitude)
.then((List<Placemark> placemarks) {
Placemark place = placemarks[0];
setState(() {
_currentAddress = ' ${place.postalCode} ${place.locality} ';
});
currentPLZ = place.postalCode.toString();
futureEvents = _getEvents() as Future<List<Events>>;
}).catchError((e) {
debugPrint(e);
});
}
Future<List<Events>> _getEvents() async {
// get all PLZ in a radius
final response = await http.get(Uri.parse(
'https://www.suche-postleitzahl.org/geo-api.json?action=plz-umkreis&subaction=umkreis-osm&plz=' +
currentPLZ +
'&radius=' + locationRadius.toString()));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
var jsondataPlz = jsonDecode(response.body);
List<PLZData> plzdataList = [];
for (var u in jsondataPlz) {
PLZData plzData = PLZData(u['postal_code'], u['name'], u['distance']);
plzdataList.add(plzData);
}
print(plzdataList.length);
print(plzdataList[1].name +
" Distanz:" +
plzdataList[1].distance +
" PLZ only" +
plzdataList[1].postal_code);
// get all events in the radius
List<Events> events = [];
if (plzdataList.isNotEmpty) {
for (var i = 0; plzdataList.length > i; i++) {
var singleEvent = await FirebaseFirestore.instance
.collection('Events')
.where('postCode', isEqualTo: plzdataList[i].postal_code)
.get();
if (singleEvent.docs.isNotEmpty) {
var singleEventList =singleEvent.docs.map((d) => Events.fromJson(d.data())).toList();
//add distance and regionname
for(var j = 0; singleEventList.length > j; j++){
singleEventList[j].distance = plzdataList[i].distance;
singleEventList[j].regionName = plzdataList[i].name;
}
events = events + singleEventList;
if (events[0].userID != null) {
print(events[0].userID);
print(events[i].distance);
}
}
}
}
//get userdata to the events
if (events.isEmpty) {
print("Es wurden keine Events gefunden");
} else {
for (var i = 0; events.length > i; i++) {
var userInformationSnap = await FirebaseFirestore.instance
.collection('users')
.where('__name__', isEqualTo: events[i].userID)
.get();
events[i].userInformation = userInformationSnap.docs
.map((d) => UsersForPosts.fromJson(d.data()))
.toList();
print(events[i].userInformation[0].username);
}
}
return events;
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load');
}
}
The FutureBuilder
FutureBuilder<List<Events>>(
future: futureEvents,
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), //<--here
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return Eventcard(
userPhoto: snapshot.data?[index].userInformation[0]
.userPhoto ?? "keine Info",
age: snapshot.data?[index].userInformation[0].age ??
"keine Info",
username: snapshot.data?[index].userInformation[0]
.username ?? "keine Info",
gender: snapshot.data?[index].userInformation[0]
.gender ?? "keine Info",
meetState: snapshot.data?[index].meetState ??
"keine Info",
postCode: snapshot.data?[index].postCode ??
"keine Info",
distance: snapshot.data?[index].distance ??
"keine Info",
regionName: snapshot.data?[index].regionName ??
"keine Info",
comment: snapshot.data?[index].comment ??
"keine Info",
headline: snapshot.data?[index].headline ??
"keine Info",
);
},
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
} else {
// By default, show a loading spinner.
return const CircularProgressIndicator();
}
},
),

Have you tried to just put the _getEvents() Future inside the FutureBuilder directly instead of using it as a late variable? I’m also confused by this format… why use a Future and a FutureBuilder? It seems like you could just create an empty list events = [] then in the initState call a new async function where you fetch the events from Firestore and use that data to update the events list through setState. Then take away the FutureBuilder and just use a ListView.builder. Just search how to create a ListView from a list, it’s very simple.

Related

Concurrent modification during iteration: Instance(length:3) of '_GrowableList'

I am new in flutter and i want to do pagination. I am using one package which name is pull to refresh. All work are doing well but when I reach last data of list then current page is increment with +1 and api call of page=2. in my code its open new page, i want to append with current listview. When i write code for append its throw error "Concurrent modification during iteration: Instance(length:3) of '_GrowableList'."(my english is not good but I hope you are understood).
Following are my code.
int currentPage = 1;
bool isRefersh = false;
Future<UserPost> getUserPost() async {
var url =
"https://myApiLink/getUserPost.php?page=$currentPage";
var response = await http.get(Uri.parse(url));
var jsondata = jsonDecode(response.body.toString());
var _apiData = UserPost.fromJson(jsondata);
if (response.statusCode == 200) {
if(isRefersh == true){
setState((){
isRefersh = false;
});
refreshController.refreshCompleted();
return UserPost.fromJson(jsondata);
}
else{
print(_apiData.hasNextPage.toString());
if(_apiData.hasNextPage == 0){
refreshController.loadNoData();
}else{
refreshController.loadComplete();
}
return UserPost.fromJson(jsondata);
}
} else {
return UserPost.fromJson(jsondata);
}
}
My list view code:
FutureBuilder<UserPost>(
future: getUserPost(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return SmartRefresher(
controller: refreshController,
enablePullUp: true,
child: ListView.builder(
//close keyboard while scroll
keyboardDismissBehavior:
ScrollViewKeyboardDismissBehavior.onDrag,
//itemCount: isFilter? ComparisonText.length : snapshot.data!.data!.length,
itemCount: snapshot.data!.data!.length,
itemBuilder: (context, i) {
var postList = snapshot.data!.data![i];
String tempSearch =
postList.azakhanaName.toString();
)}
onRefersh for refreshing and onLoading for pagination.
onRefresh: () async{
await Future.delayed(Duration(milliseconds: 1000));
setState(() {
isRefersh = true;
currentPage = 1;
});
},
onLoading: () async {
if(snapshot.data!.hasNextPage == 0){
refreshController.loadNoData();
}else{
setState(() {
currentPage++;
snapshot.data!.data!.addAll(snapshot.data!.data!);
});
await Future.delayed(Duration(milliseconds: 1000));
refreshController.loadComplete();
}
},
I am trying to append listview with new data like this and and error also throw in this code:
snapshot.data!.data!.addAll(snapshot.data!.data!);

Future builder returns null although my list is not empty

I have this future builder which loads a list of movies in my provider class. Whenever I reload my screen, the movies do not get returned. Below is the future builder
FutureBuilder(
future: movieData.getTrendingMovies(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasData) {
return Swiper(
itemBuilder: (BuildContext context, i) {
return ChangeNotifierProvider(
create: (context) => Movie(),
child: MovieContainer(
imageUrl: movieData.movies[i].imageUrl,
id: movieData.movies[i].id,
rate: movieData.movies[i].rate,
title: movieData.movies[i].title,
),
);
},
itemCount: movieData.movies.length,
viewportFraction: 0.25,
scale: 0.4,
);
} else {
return Text(snapshot.error.toString()); // it returns null on the screen
}
}),
Also in my homescreen where I display my movies, after the build method, I create a listener(moviesData) to listen to all changes in the movies provider.
final movieData = Provider.of<Movies>(context, listen: false);
Below is also the methos which fetches the movies from a restfulAPI using http get request
Future<void> getTrendingMovies() async {
List<String> movieTitles = [];
List<String> movieImageUrls = [];
List<String> movieDescriptions = [];
List<String> movieReleaseDates = [];
List<String> movieRates = [];
List<String> movieIds = [];
const _apiKey = '******************************';
const url =
'https://api.themoviedb.org/3/trending/all/week?api_key=$_apiKey';
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode >= 400) {
print(response.statusCode);
return;
}
final extractedData = json.decode(response.body);
List moviesList = extractedData['results'] as List;
List<Movie> loadedMovies = [];
for (int i = 0; i < moviesList.length; i++) {
String movieTitle = moviesList[i]['original_title'] ?? '';
String? movieImage =
'https://image.tmdb.org/t/p/w400${moviesList[i]['poster_path']}'; //results[0].poster_path
String movieDescription =
moviesList[i]['overview'] ?? ''; //results[0].overview
String movieReleaseDate = moviesList[i]['release_date'] ?? '';
String? movieRate = moviesList[i]['vote_average'].toString();
String? movieId = moviesList[i]['id'].toString();
movieTitles.add(movieTitle);
movieImageUrls.add(movieImage);
movieDescriptions.add(movieDescription);
movieReleaseDates.add(movieReleaseDate);
movieRates.add(movieRate);
movieIds.add(movieId);
loadedMovies.add(
Movie(
id: movieIds[i],
title: movieTitles[i],
imageUrl: movieImageUrls[i],
description: movieDescriptions[i],
rate: double.parse(movieRates[i]),
releaseDate: movieReleaseDates[i],
),
);
}
_movies = loadedMovies;
notifyListeners();
//print(_movies.last.title); //This prints the name of the last movie perfectly....This gets called unlimited times whenever I set the listen of the **moviesData** to true
} catch (error) {
print(error);
}
}
There's a couple of things to unpack here.
Instead of a ChangeNotifierProvider, I believe you should use a Consumer widget that listens to your Movies provided service when you call the notifyListeners call, so make it Consumer<Movie>.
You can still call it using the Provider.of above for the sake of making the async call via the FutureBuilder, but I believe because you're not returning anything out of the getTrendingMovies and is just a Future<void> and you're querying the snapshot.hasData, well there is no data coming through the snapshot. Maybe instead you should call snapshot.connectionState == ConnectionState.done as opposed to querying for whether it has data.
Make sure that the response.body is truly returning a JSON value, but I believe your issue is in one of the points above.

StreamBuilder doesn't updating items when I get more

I have a list with all the items from that folder, and I'm retrieving the data with 10 items per time (first load 10, when user reach the list finish, it loads more 10). The problem Is, when list have to be updated, it is not.
It doesn't add the new items in the list.
This is the method I get data from firebase:
Future<void> loadnovo(
{String submenu,
int limit = 10,
bool cls = false,
bool initialLoad = false,
int lastIndex}) async {
if (cls) {
conteudo.clear();
hasMore = true;
}
if (_isLoading || !hasMore) {
return Future.value();
}
_isLoading = true;
var parts = submenu.split('/');
var pathSlashless = parts[0].trim();
var subPathSlashless = parts.sublist(1).join('/').trim();
var snapshot = await _storage.ref().child("/${submenu}");
var retorno = await snapshot.listAll();
if (subPathSlashless.isEmpty || subPathSlashless == null) {
retorno.prefixes.forEach((element) {
conteudo.add(
ItemLab(
tipo: 'PASTA',
elemento: element,
),
);
_streamController.add(conteudo);
});
}
for (int i = lastIndex; i < lastIndex + limit; i++) {
var url = await retorno.items[i].getDownloadURL();
conteudo.add(
ItemLab(
tipo: 'FILE',
elemento: retorno.items[i],
imageUrl: url,
),
);
print(conteudo);
print(conteudo.length);
_streamController.add(conteudo);
}
hasMore = true;
}
This is my Screen with the Stream builder, a gridView (which show the items) and the scrollListener:
LabController ctrlLab;
final lab = LabMdScreen();
inal scrollController = ScrollController();
int lastIndex = 0;
scrollListener() async {
if (scrollController.position.maxScrollExtent == scrollController.offset) {
lastIndex += 10;
ctrlLab.loadList(submenu: "ph/Res", lastIndex: lastIndex);
}
}
#override
void initState() {
ctrlLab = LabController();
ctrlLab.loadList(submenu: "ph/Res", lastIndex: lastIndex,cls: true, initialLoad: true);
scrollController.addListener(scrollListener);
super.initState();
}
#override
void dispose() {
scrollController.removeListener(scrollListener);
super.dispose();
}
loadBasicStructureDetail(submenu ,callback, context, deviceSize){
return StreamBuilder(
stream: ctrlLab.stream,
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.error != null) {
print(snapshot.error);
return Center(child: Text('Ocorreu um erro!'));
}else {
return GridView.builder(
padding: EdgeInsets.all(10.0),
controller: scrollController,
itemCount: snapshot.data.length +1,
itemBuilder: (ctx, i) {
path = callback;
if (i < snapshot.data.length) {
ItemLab item = snapshot.data[i];
>>>> here my code to format the tiles...
What I'm missing here
Try removing
if (_isLoading || !hasMore) {
return Future.value();
}

Pull-to-refresh in Flutter don't work when no internet connection

I have a class that displays in a list view some JSON data (events) that I get with an API request and save them to the storage of the device so to not be downloaded every time, UNLESS the user makes a Pull-to-refresh operation so to download news events.
In case during the operation of download there is no internet connection the app display "Impossible to download the events list: check your internet connection!".
So I aspect that if it is the first time the user opens the app, it should download the events or show in case of internet connection missing the message mentioned above (or that there are no events in case the length of the events array downloaded == 0). If it is not the first time show the list of the events previously downloaded and saved.
My problem is that if, for example, I have internet turned off and after I turned on, the pull to refresh doesn't work, instead when I have the list downloaded I can make a pull to refresh operation.
This is my code:
class EventDetails {
String data;
int id;
String name;
String description;
String applicationStatus;
String applicationStarts;
String applicationEnd;
String starts;
String ends;
int fee;
EventDetails({
this.data,
this.id,
this.name,
this.description,
this.applicationStatus,
this.applicationStarts,
this.applicationEnd,
this.starts,
this.ends,
this.fee,
});
EventDetails.fromJson(Map<String, dynamic> json) {
data = json['data'];
id = json['id'];
name = json['name'];
description = json['description'];
applicationStatus = json['application_status'];
applicationStarts = json['application_starts'];
applicationEnd = json['application_ends'];
starts = json['starts'];
ends = json['ends'];
fee = json['fee'];
}
}
class EventsListView extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _EventListState();
}
}
class _EventListState extends State<EventsListView> {
List<EventDetails> list;
Storage storage = Storage();
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _getData,
child: FutureBuilder<List<EventDetails>>(
future: loadEvents(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<EventDetails> data = snapshot.data;
if (data.length == 0) {
return Text("No events found",
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
));
} else {
return _eventsListView(data);
}
} else if (snapshot.hasError) {
if (snapshot.error.runtimeType.toString() == "SocketException") {
return Text(
"Impossible to download the events list: check your internet connection!",
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
));
} else {
return Text("${snapshot.error}");
}
}
return CircularProgressIndicator();
},
),
);
}
Future<List<EventDetails>> loadEvents() async {
String content = await storage.readList();
if (content != 'no file available') {
list = getListFromData(content, list);
}
if ((list != null) && (list.length != 0)) {
print('not empty');
return list;
} else {
return await downloadEvents(list, storage);
}
}
Future<List<EventDetails>> downloadEvents(
List<EventDetails> list, Storage storage) async {
String url = "https://myurl";
final response = await http.get(url);
if (response.statusCode == 200) {
String responseResult = response.body;
list = getListFromData(responseResult, list);
storage.writeList(response.body);
return list;
} else {
throw Exception('Failed to load events from API');
}
}
List<EventDetails> getListFromData(String response, List<EventDetails> list) {
Map<String, dynamic> map = json.decode(response);
List<dynamic> jsonResponse = map["data"];
list = jsonResponse.map((job) => new EventDetails.fromJson(job)).toList();
return list;
}
ListView _eventsListView(data) {
return ListView.separated(
itemCount: data.length,
separatorBuilder: (context, index) => Divider(
color: const Color(0xFFCCCCCC),
),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
child: _tile(data[index].name),
onTap: () {
Navigator.pushNamed(
context,
SingleEvent.routeName,
arguments: ScreenArguments(
data[index].name,
data[index].description,
data[index].starts,
data[index].ends,
),
);
});
});
}
Future<void> _getData() async {
setState(() {
downloadEvents(list,storage);
});
}
#override
void initState() {
super.initState();
loadEvents();
}
ListTile _tile(String title) => ListTile(
title: Text(title,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
)),
);
}
I am really new in Flutter, what I am doing wrong?
FutureBuilder will not refresh once the future is evaluated. If you want the pull to refresh to work, you could just store the list data as a state of the widget and render different UI based on the state.
In addition to that, RefreshIndicator will not work if the child is not scrollable. Instead returning plain Text widget when there is no data, return SingleChildScrollView with a text inside so that you have a scrollable inside your RefreshIndicator.
Here is an example:
class EventsListView extends StatefulWidget {
#override
_EventsListViewState createState() => _EventsListViewState();
}
class _EventsListViewState extends State<EventsListView> {
List list;
Storage storage = Storage();
String errorMessage;
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: downloadEvents,
child: listWidget(),
);
}
Widget listWidget() {
if (list != null) {
return ListView(); // here you would return the list view with contents in it
} else {
return SingleChildScrollView(child: Text('noData')); // You need to return a scrollable widget for the refresh to work.
}
}
Future<void> loadEvents() async {
String content = await storage.readList();
if (content != 'no file available') {
list = getListFromData(content, list);
}
if ((list != null) && (list.length != 0)) {
print('not empty');
errorMessage = null;
setState(() {});
} else {
await downloadEvents();
}
}
Future<void> downloadEvents() async {
String url = "https://myurl";
final response = await http.get(url);
if (response.statusCode == 200) {
String responseResult = response.body;
list = getListFromData(responseResult, list);
storage.writeList(response.body);
errorMessage = null;
setState(() {});
} else {
setState(() {
errorMessage =
'Error occured'; // here, you would actually add more if, else statements to show better error message
});
throw Exception('Failed to load events from API');
}
}
List<EventDetails> getListFromData(String response, List<EventDetails> list) {
Map<String, dynamic> map = json.decode(response);
List<dynamic> jsonResponse = map["data"];
list = jsonResponse.map((job) => new EventDetails.fromJson(job)).toList();
return list;
}
#override
void initState() {
super.initState();
loadEvents();
}
}

How to return Future List from DataSnapshot

I want to return a Future List from Firebase Database snapshot and this is my code but I cant get it work properly:
Future<List<CocheDetailItem>> getCoches(ids) async {
List<CocheDetailItem> coches = [];
final dbRef = FirebaseDatabase.instance.reference().child('17082019');
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
dbRef.child(id).once().then((DataSnapshot snapshot) {
if (snapshot.value != null) {
Map<dynamic, dynamic> jsres = snapshot.value;
CocheDetailItem coche = CocheDetailItem.fromJson(jsres);
coches.add(coche);
}
});
print('here is i ${ids[i]} ');
}
return coches;
}
The return I get is empty Area. Can anyone help me with this, please?
Note, dbRef.child(id).once(); is a async function, so you must wait it ends to get your data. Use await keyword to do it.
Future<List<CocheDetailItem>> getCoches(ids) async {
List<CocheDetailItem> coches = [];
final dbRef = FirebaseDatabase.instance.reference().child('17082019');
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var dataSnapshot = await dbRef.child(id).once();
if (dataSnapshot.value != null) {
Map<dynamic, dynamic> jsres = dataSnapshot.value;
CocheDetailItem coche = CocheDetailItem.fromJson(jsres);
coches.add(coche);
}
print('here is i ${ids[i]} ');
}
return coches;
}
well.. I don't use firebase but I send a request to my database with this (you have to use async and await)
Future<List<PlaceModel>> getPlaces(String ciudad, String tipo) async {
Uri request = Uri.http('domain.com', '/getPlaces/$ciudad/$tipo');
ResponseModel response = ResponseModel.fromJsonMap(json.decode((await http.get(request)).body));
List<PlaceModel> items = [];
if(response.res) {
if(response.value != null) {
for(var item in response.value) {
final place = PlaceModel.fromJsonMap(item);
items.add(place);
}
}
}
print("Places Loaded: ${items.length}");
return items;
}
I use my ResponseModel to convert the json answer in an object.
Then I show it with the future builder:
class PlacesListPage extends StatelessWidget{
final _selectedLocation, _selectedList;
PlacesListPage(this._selectedLocation, this._selectedList);
final _provider = PlaceProvider();
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8.0),
child: FutureBuilder(
future: _provider.getPlaces(_selectedLocation, _selectedList), // async request to database
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) { // check when your request is done
if(snapshot.data.length != 0) { // check if any data has been downloaded
return ListView.builder( // build a listview of any widget with snapshot data
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
// i just return containers but you can use any custom widget, it's like a forEach and use the index var
return Container(
child: Text(snapshot.data[index]),
);
},
);
} else {
// If you don't have anything in your response shows a message
return Text('No data');
}
} else {
// shows a charge indicator while the request is made
return Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}