how to initize late variable in flutter? LateInitializationError - flutter

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

Related

How to initialize a Future and then replace it in Flutter/Dart?

How do I do the initialize the Future correctly for a FutureBuilder in Flutter?
As you can see, there is an exception being thrown because the late Future is not yet initialized. (please ignore that parameter v is not used in the method, removed method details for simplicity, but v is important).
late Future<int> myIntvalue;
Future<int> getStackInit(int v) async {
await Future.delay(Duration(seconds: 5));
return 5;
}
#override
void initState()
{
SharedPreferences.getInstance().then((sp) {
int value = sp.getInt("StartValue") ?? 0;
myIntvalue = getStackInit(value);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: myIntvalue, <-- Exception, not initialized
builder: (context, AsyncSnapshot snapshot) {
....
The problem ist, that myIntvalue is not yet initialized when the FutureBuilder is reached. What is the best way to initialized the future before it is "really" initialized? Or is it possible to define a future during initialization and "replace" the result later on?
Is there any more elegant way?
Thanks!
In general, if you need to be able to check if something's been initialized, you need to make it nullable.
Or you could set your Future to an initial sentinel value (e.g. Future.value(0)), replace it later, and call setState to rebuild your widget tree, just as you would if you had to update any other member variable.
However, in your case you should just initialize myIntvalue to the desired Future as soon as possible. For example:
#override
void initState()
{
Future<int> initializeIntValue() async {
var sp = SharedPreferences.getInstance();
int value = sp.getInt("StartValue") ?? 0;
return await getStackInit(value);
}
myIntvalue = initializeIntValue();
super.initState();
}

Flutter - FutureBuilder fires twice on hot reload

In my flutter project when I start the project in the simulator everything works fine and the future builder only fires once, but when I do hot reload the FutureBuilder fires twice which causes an error any idea how to fix this?
Future frameFuture() async {
var future1 = await AuthService.getUserDataFromFirestore();
var future2 = await GeoService.getPosition();
return [future1, future2];
}
#override
void initState() {
user = FirebaseAuth.instance.currentUser!;
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: frameFuture(),
builder: (context, snap) {
if (snap.connectionState == ConnectionState.done && snap.hasData) return HomePage();
else return Container(
color: Colors.black,
child: Center(
child: spinKit,
),
);
}
);
}
I solved the issue. I put the Future function in the initState and then used the variable in the FutureBuilder. I'm not sure why it works this way, but here's the code:
var futures;
Future frameFuture() async {
var future1 = await AuthService.getUserDataFromFirestore();
var future2 = await GeoService.getPosition();
return [future1, future2];
}
#override
void initState() {
user = FirebaseAuth.instance.currentUser!;
super.initState();
futures = frameFuture();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futures,
builder: (context, snap) {
if (snap.connectionState == ConnectionState.done && snap.hasData) return HomePage();
else return Container(
color: Colors.black,
child: Center(
child: spinKit,
),
);
}
);
}
The solution as you already figured out is to move the future loading process to the initState of a StatefulWidget, but I'll explain the why it happens:
You were calling your future inside your build method like this:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: frameFuture(),
The issue is that Flutter calls the build method each time it renders the Widget, whenever a dependency changes(InheritedWidget, setState) or Flutter decides to rebuild it. So each time you redraw your UI frameFuture() gets called, this makes your build method to have side effects (this async call) which it should not, and is encouraged for widgets not to have side effects.
By moving the async computation to the initState you're only calling it once and then accessing the cached variable futures from your state.
As a plus here is an excerpt of the docs of the FutureBuilder class
"The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted."
Hope this makes clear the Why of the solution.
This can happen even when the Future is called from initState. The prior solution I was using felt ugly.
The cleanest solution is to use AsyncMemoizer which effectively just checks if a function is run before
import 'package:async/async.dart';
class SampleWid extends StatefulWidget {
const SampleWid({Key? key}) : super(key: key);
final AsyncMemoizer asyncResults = AsyncMemoizer();
#override
_SampleWidState createState() => _SampleWidState();
}
class _SampleWidState extends State<SampleWid> {
#override
void initState() {
super.initState();
_getData();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: widget.asyncResults.future,
builder: (context, snapshot) {
if (!snapshot.hasData) return yourLoadingAnimation();
// ... Do things with the data!
});
}
// The async and await here aren't necessary.
_getData() async () {
await widget.asyncResults.runOnce(() => yourApiCall());
}
}
Surprisingly, there's no .reset() method. It seems like the best way to forcibly rerun it is to override it with a new AsyncMemoizer(). You could do that easily like this
_getData() async ({bool reload = false}) {
if (reload) widget.asyncResults = AsyncMemoizer();
await widget.asyncResults.runOnce(() => yourApiCall());
}

Flutter set state not updating my UI with new data

I have a ListView.builder widget wrapped inside a RefreshIndicator and then a FutureBuilder. Refreshing does not update my list, I have to close the app and open it again but the refresh code does the same as my FutureBuilder.
Please see my code below, when I read it I expect the widget tree to definitely update.
#override
void initState() {
super.initState();
taskListFuture= TaskService().getTasks();
}
#override
Widget build(BuildContext context) {
return Consumer<TaskData>(builder: (context, taskData, child) {
return FutureBuilder(
future: taskListFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
taskData.tasks = (snapshot.data as ApiResponseModel).responseBody;
return RefreshIndicator(
onRefresh: () async {
var responseModel = await TaskService().getTasks();
setState(() {
taskData.tasks = responseModel.responseBody;
});
},
child: ListView.builder(
...
...
Let me know if more code is required, thanks in advance!
Points
I am using a StatefulWidget
Task data is a class that extends ChangeNotifier
When I debug the refresh I can see the new data in the list, but the UI does not update
getTasks()
Future<ApiResponseModel> getTasks() async {
try {
var _sharedPreferences = await SharedPreferences.getInstance();
var userId = _sharedPreferences.getString(PreferencesModel.userId);
var response = await http.get(
Uri.parse("$apiBaseUrl/$_controllerRoute?userId=$userId"),
headers: await authorizeHttpRequest(),
);
var jsonTaskDtos = jsonDecode(response.body);
var taskDtos= List<TaskDto>.from(
jsonTaskDtos.map((jsonTaskDto) => TaskDto.fromJson(jsonTaskDto)));
return ApiResponseModel(
responseBody: taskDtos,
isSuccessStatusCode: isSuccessStatusCode(response.statusCode));
} catch (e) {
return null;
}
}
The issue here seems to be that you are updating a property that is not part of your StatefulWidget state.
setState(() {
taskData.tasks = responseModel.responseBody;
});
That sets a property part of TaskData.
My suggestion is to only use the Consumer and refactor TaskService so it controls a list of TaskData or similar. Something like:
Provider
class TaskService extends ChangeNotifier {
List<TaskData> _data;
load() async {
this.data = await _fetchData();
}
List<TaskData> get data => _data;
set data(List<TaskData> data) {
_data = data;
notifyListeners();
}
}
Widget
class MyTaskList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<TaskService>(builder: (context, service, child) {
return RefreshIndicator(
onRefresh: () {
service.getTasks();
},
child: ListView.builder(
itemCount: service.data.length,
itemBuilder: (BuildContext context, int index) {
return MyTaskItem(data:service.data[index]);
},
),
);
});
}
}
and make sure to call notifyListeners() in the service.getTasks() method to make the Consumer rebuild
I think (someone will correct me if I'm wrong) the problem is that you are using the FutureBuilder, once it's built, you need to refresh to whole widget for the FutureBuilder to listen to changes. I can suggest a StreamBuilder that listens to any changes provided from the data model/api/any kind of stream of data. Or better yet, you can use some sort of state management like Provider and use Consumer from the Provider package that notifies the widget of any changes that may occurred.

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

State.initState() must be a void method without an `async` keyword

![State.initState() must be a void method without an async keyword.
how can i solve this probelms]1
#override
Future<void> initState() async {
// TODO: implement initState
super.initState();
_current_location();
BitmapDescriptor.fromAssetImage(
ImageConfiguration(devicePixelRatio: 2.5),
'assets/fff.png').then((onValue) {
pinLocationIcon = onValue;
});
//createCustomMarker(context);
// final Marker marker = Marker(icon: BitmapDescriptor.fromBytes(markerIcon));
DatabaseReference ref = FirebaseDatabase.instance.reference();
ref.child('users').once().then((DataSnapshot snapshot) {
Map<dynamic, dynamic> values = snapshot.value;
print(values.toString());
values.forEach((k, v) {
allMarkers.add(Marker(
markerId: MarkerId(k),
draggable: false,
icon: pinLocationIcon,
position: LatLng(v["latitude"], v["longitude"]),
infoWindow: InfoWindow(title: v["name"]),
onTap: () {
_onMarkerTapped(v["name"]);
},
),);
});
});
}
initState must be a method which takes no parameters and returns void. This is because it overrides the method of the same name in the superclass (either StatelessWidget or State<StatefulWidgetType>. As such, this limitation is a contract that is fixed and binding; you cannot change it.
Of course, this also means that initState cannot be marked as async. This is because any method marked as async will implicitly return a Future, but if the method returns anything, it cannot have a return type of void which breaks the override contract.
If you need to call an async method from within initState, you can do so simply by not awaiting it:
#override
void initState() {
super.initState();
doSomeAsyncStuff();
}
Future<void> doSomeAsyncStuff() async {
...
}
If, however, you need the data from the async method for your widget, you cannot simply wait for the Future to return before you build the widget. Flutter does not allow this, because there is no telling how long it will take for the Future to return, and stalling the widget building until then could potentially block your entire app.
Instead, you need to have your widget build normally and then have a way to notify your widget to update when the Future has returned. This is most easily done with a FutureBuilder:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: doSomeAsyncStuff(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
// Future hasn't finished yet, return a placeholder
return Text('Loading');
}
return Text('Loading Complete: ${snapshot.data}');
}
);
}
(Notice how instead of calling the async method from initState, I am calling it from the FutureBuilder during the build process.)
EDIT: As pointed out, this approach only works in OP's situation where the awaited future will always eventually return a value. This is not always the case - sometimes the future doesn't return a value at all and is just a long-running process. Sometimes the future might return null instead of concrete data. And sometimes the future may result in an error instead of completing successfully. In any of these cases, snapshot.data will be null after the future completes, in which case snapshot.hasData will always be false.
In these situations, instead of depending on snapshot.hasData to wait for data to appear, you can use snapshot.connectionState to monitor the state of the future itself:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: doSomeAsyncStuff(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
// Future hasn't finished yet, return a placeholder
return Text('Loading');
}
return Text('Loading Complete');
}
);
}