Flutter List View through Future Provider Value - flutter

I am trying to call the output of a Future Provider of type Future> into a List View builder. I think I am very near as I am able to render the final List View itself, however, prior that, an error appears and is quickly replaced by the List View after completing the Future. I believe there may be something wrong with my implementation there.
Here's what I've got so far (these are derivatives of my actual code, there's too many going on there that aren't necessary, I tried to simplify it):
class TempProvider extends ChangeNotifier(){
List<Widget> _list = <Widget>[];
List<Widget get list => _list;
Future<List<Widget>> getList() async{
List _result = await db....
_result.forEach((_item){
addToList(_item);
});
}
addToList(Widget widget){
_list.add(widget);
notifyListeners();
}
}
class Parent extends StatelessWidget{
#override
Widget build(BuildContext context) {
return FutureProvider(
create: (context) => TempProvider().getList(),
child: Child(),
);
}
}
class Child extends StatelessWidget{
#override
Widget build(BuildContext context) {
var futureProvider = Provider.of<List<Widget>>(context);
return FutureBuilder(
initialData: <Widget>[],
future: TempProvider().getList(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.none &&
snapshot.hasData == true) {
return ListView.builder(
itemCount: futureProvider.length,
itemBuilder: (BuildContext context, int index) {
return futureProvider[index];
},
);
} else {
return Text('ALAWS');
}
},
);
}
}
So basically, the output of my Future will be a list of widgets that will populate a List View that I am trying to build. Though I am able to render the list view in the end, the error below appears in between:
The getter 'length' was called on null.
Receiver: null
Tried calling: length
The relevant error-causing widget was
FutureBuilder<List<Widget>>
Hoping someone can help with this one or at least give a better example.
Thank you so much!

Not sure but, this is happening because your widget might be building twice and at first futureProvider is null and in second time it has some value.
Workaround:
Replace this:
futureProvider.length
With this:
futureProvider?.length ?? 0
What the above code does?
futureProvider?.length: if futureProvider is null don't access it's length.
Now the value returned will be null.
?? 0: if the value returned is null then return 0;
You need to think over following things and edit your code.
At first place futureProvider should not be null.
Why are you not using snaphot.data when you are using FutureBuilder.

So I have been doing my research and found the article below which shows a definite implementation based on what I need:
Flutter Provider Examples - Codetober
Credits to Douglas Tober for the article. Thanks again to Kalpesh for the quick help!

Related

how to initize late variable in flutter? LateInitializationError

I have an initialization error of my variable late Future<List?> listTest;. I understand that I have an error because I try to use the variable inside a futureBuilder but it has not yet been initialized. I tried to make the variable nullable (Future<List?>? listTest;), giving no more compilation error, but not working in my case.
Searching the internet I saw that I have to initialize it within the void initState, but I didn't understand how to do that. Can anybody help me?
Piece of code:
late Future<List<CursoTO>?> listTest;
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
setState(() {
final routeArgs1 =
ModalRoute.of(context)!.settings.arguments as Map<String, String>;
var curso = Curso();
listTest= curso.lista(
nomeImagem!,
idSite!);
});
});
}
#override
Widget build(BuildContext context) {
var futureBuilder = FutureBuilder(
future: listTest,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return createScreen(context, snapshot);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return const Center(child: CircularProgressIndicator());
},
);
return Scaffold(
body: futureBuilder);
}
From a syntactical perspective (I can't test your code), there's no need to use the keyword late as you're handling the various states of this object in the builder function already. Simply initialise an empty list then update it in the initState() method.
// late Future<List<CursoTO>?> listTest;
Future<List<CursoTO>?> listTest = Future.value([]);
Try to initialize listTest in constructor.
Otherwise make you variable to static and initialize with empty list and aging put value in initstate

Consumer returns empty List instead of actual data

I am using the provider package for flutter.
https://pub.dev/packages/provider
On Appstart, I use the following provider:
late Future<List<Art>> art;
late Future<List<Music>> music;
late Future<List<Furniture>> furniture;
late Future<List<Clothing>> clothing;
late Future<List<Flower>> flowers;
late Future<List<Interieur>> interieur;
late Future<List<Collectible>> allCollectibles;
#override
void initState() {
super.initState();
art = getAllArts();
music = getAllMusic();
furniture = getAllFurniture();
clothing = getAllClothing();
flowers = getAllFlowers();
interieur = getAllInterieur();
allCollectibles = () async {
return [
...await art,
...await music,
...await furniture,
...await clothing,
...await flowers,
...await interieur,
];
}();
}
#override
Widget build(BuildContext context) {
timeDilation = 1;
return FutureBuilder(
future: settings,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Center(child: CircularProgressIndicator());
}
return FutureProvider<List<Collectible>>(
create: (context) => allCollectibles, initialData: [],
),});
later on, I use this consumer to retreive it:
#override
Widget build(BuildContext context) {
return Consumer<List<Collectible>>(builder: (context, collectibles, child) {
return sendCollectibleDataFull(collectibles),
});
}
The list in the Method sendCollectibleDataFull is sometimes there and sometimes not.
If it is empty on call, it will stay empty.
I have updated to nullsafety hence the initialData: [], got mandatory. Before that, I always got a list from this.
Can I tell my Consumer/Provider to await the data before I retreive it?
Hi #lellek and welcome.
Okay there are a couple of things I want to point out.
You're using the default FutureProvider constructor with a create callback but in fact you're providing a value that already exists.
return FutureProvider<List<Collectible>>(
create: (context) => allCollectibles, initialData: [],
),});
If you already have a value and you're not creating it with the Provider, you should use the .value constructor.
However, you could use the default constructor with the create callback and do all the work there instead of having this StatefulWidget doing some of the work (unless you just need these instances here as well).
This is not tested but it should look something like this:
FutureProvider<List<Collectible>>(
create: (context) async {
/// get collectibles in parallel
final List<List<Collectible>> results = await Future.wait([
getAllArts(),
getAllMusic(),
getAllFurniture(),
getAllClothing(),
getAllFlowers(),
getAllInterieur(),
]);
/// flatten the results
final allCollectables = [for (final collectibles in results) ...collectibles];
return allCollectables;
},
initialData: [],
)
Can I tell my Consumer/Provider to await the data before I retreive it?
No, the point is that the initial value, in this case [], will be available from the provider and the tree will update when the future is resolved.

Trouble initializing a <Position> variable in Flutter LateInitializationError: Field '____ ' has not been initialized

newbie to Flutter. My code runs but encounters a
The following LateError was thrown building
FutureBuilder(dirty, state:
_FutureBuilderState#e1a6f):
LateInitializationError: Field 'initialPosition' has not been
initialized.
The code is to set up a GoogleMap widget that takes initial position from the device. I get the red screen with that error, but after a few seconds the coordinates gets received and proceeds as normal and displays the map and position correctly.
Tried future as well but but I get other errors. Is it supposed to be under the FutureBuilder? In a wrapper.dart or my main.dart?
home.dart:
import 'package:flutter/material.dart';
import 'package:something/services/auth.dart';
import 'screens/map.dart';
import 'package:something/services/geolocator_service.dart';
class LakoApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<LakoApp> {
final AuthService _auth = AuthService();
final _geolocatorService = GeolocatorService();
late var initialPosition;
// #override
Future getInitialPosition <Position>() async {
initialPosition = await _geolocatorService.getInitialLocation();
return initialPosition;
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: FittedBox(
child: Text('Something something'),
),
actions: <Widget>[
// irrelevant code
// .....
],
body:
FutureBuilder(
future: getInitialPosition(),
builder: (context, snapshot) {
return Map(initialPosition);
}
)
);
}
}
Future Builders are built even before getting the data. So, you should check whether it has data.
if (snapshot.hasData) {
return Map(initialPosition); //Or snapshot.data.
}else{
return CircularProgressIndicator();
}
There are other problems here. I will show some further code to improve your own code.
Your method returns a Future of any type receiving a generic parameter called Position. I think you want to use a data type called position for that you need to move <Position> here as right now the way you are writing it is useless for your specific example.
Future<Position> getInitialPosition () async {
initialPosition = await _geolocatorService.getInitialLocation();
return initialPosition;
}
The FutureBuilder can be like this.
FutureBuilder<Position>(
future: getInitialPosition(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Map(snapshot.data);
}else{
return CircularProgressIndicator();
//Display loading, you may adapt this widget to your interface or use some state management solution
}
}
)
Edited the code according to suggestions: got rid of the method and variable, because its redundant
body: FutureBuilder <Position> (
future: _geolocatorService.getInitialLocation(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Map(snapshot.data!);
}else {
return Loading();

How to create and update the value of Dynamic Widgets through Flutter Provider

So I am implementing something like below:
class TempProvider extends ChangeNotifier(){
List<Widget> _list = <Widget>[];
List<Widget get list => _list;
int _count = 0;
int get count => _count;
Future<List<Widget>> getList() async{
addToList(Text('$count'));
List _result = await db....
_result.forEach((_item){
addToList(Button(
onTap: () => increment();
child: Text('Press'),
));
});
}
addToList(Widget widget){
_list.add(widget);
notifyListeners();
}
increment(){
_count += 1;
notifyListeners();
}
}
class Parent extends StatelessWidget{
#override
Widget build(BuildContext context) {
return FutureProvider(
create: (context) => TempProvider().getList(),
child: Child(),
);
}
}
class Child extends StatelessWidget{
#override
Widget build(BuildContext context) {
var futureProvider = Provider.of<List<Widget>>(context);
return Container(
child: futureProvider == null
? Text('Loading...'
: ListView.builder(
itemCount: futureProvider.length,
itemBuilder: (BuildContext context, int index){
return futureProvider[index];
}
),
));
}
}
Basically, what this does is that a List of Widgets from a Future is the content of ListView Builder that I have as its objects are generated from a database query. Those widgets are buttons that when pressed should update the "Count" value and should update the Text Widget displaying the latest "Count" value.
I was able to test the buttons and they seem to work and are incrementing the _count value via backend, however, the displayed "Count" on the Text Widget is not updating even if the Provider values are updated.
I'd like to ask for your help for what's wrong here, with my understanding, things should just update whenever the value changes, is this a Provider anti-pattern, do I have to rebuild the entire ListView, or I missed something else?
I'm still getting myself acquainted with this package and dart/flutter in general, hoping you can share me your expertise on this. Thank you very much in advance.
so I have been on a lot of research and a lot of trial and errors last night and this morning, and I just accidentally bumped into an idea that worked!
You just have to have put the listening value on a consumer widget making sure it listens to the nearest Provider that we have already implemented higher in the widget tree. (Considering that I have already finished drawing my ListView builder below the FutureProvider Widget)
..getList() async{
Consumer<ChallengeViewProvider>(
builder: (_, foo, __) => Text(
'${foo.count}',
),
);
List _result = await db....
_result.forEach((_item){
addToList(Button(
onTap: () => increment();
child: Text('Press'),
));
});
}
I have also refactored my widgets and pulled out the Button as a stateless widget for reuse. Though make sure that referenced Buttons are subscribed to the same parent provider having the Counter value and have the onTap property call out the increment() function through Provider<>.of
Hoping this will help anyone in the future!

Flutter Bloc: BlocBuilder not getting called after an update, ListView still displays old data

I'm using flutter_bloc for state management and landed on this issue. When updating a field and saving it, the BlocBuilder is not refreshing the page. It is working fine when Adding or Deleting. I'm not sure what I'm doing wrong here.
Even if I go to a different screen and returning to this screen it still displays the old data even though the file was updated.
I spent more than 2 hours trying to debug this to no avail. I tried initializing the updatedTodos = [] then adding each todo one by one, to see if that does something, but that didn't work either.
Any help here would be appreciated.
TodosBloc.dart:
Stream<TodosState> _mapUpdateTodoToState(
TodosLoaded currentState,
UpdateTodo event,
) async* {
if (currentState is TodosLoaded) {
final index = currentState.Todos
.indexWhere((todo) => event.todo.id == todo.id);
final List<TodoModel> updatedTodos =
List.from(currentState.todos)
..removeAt(index)
..insert(index, event.todo);
yield TodosLoaded(updatedTodos);
_saveTodos(updatedTodos);
}
}
todos_screen.dart:
...
Widget build(BuildContext context) {
return BlocBuilder(
bloc: _todosBloc,
builder: (BuildContext context, TodosState state) {
List<TodoModel> todos = const [];
String _strings = "";
if (state is TodosLoaded) {
todos = state.todos;
}
return Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (BuildContext ctnx, int index) {
return Dismissible(
key: Key(todo.toString()),
child: DetailCard(
todo: todos[index],
),
);
},
),
);
...
I'm expecting when the BlocBuilder to be called and refreshed the ListView.
I was able to resolve this with the help of Felix Angelov on github.
The problem is that I'm extending Equatable but not passing the props to the super class in the TodoModel class. I had to update the constructor of the TodoModel with a super([]).
This is the way i solved the issue , even though it could not be the best solution but i'll share it , when you are on the other screen where you are supposed to show data or something , upon pressing back button call dispose as shown below
#override
void initState() {
super.initState();
print("id" + widget.teamID);
BlocProvider.of<FootBallCubit>(context).getCurrentTeamInfo(widget.teamID);
}
// what i noticed upon closing this instance of screen , it deletes old data
#override
void dispose() {
super.dispose();
Navigator.pop(context);
}