Stateless widgets don’t remember anything, but Stateful Widgets can keep track of things like variables and properties. To update the screen, you use the setState() method. State is the information that can change while the widget is being used.
There are four important steps in the life of a StatefulWidget:
initState()
: This is called only once when the widget is first created. It’s where you set up things and give starting values to your objects. Try to do as much as you can here instead of in thebuild()
method.build()
: This is called whenever something changes. It destroys the old UI and makes a new one.deactivate() and dispose()
: These are called when a widget is removed. You use these to close things like database connections or save data before switching screens.
Now, let’s learn how to work with Futures while considering these steps in a widget’s life.
We’ll use the geolocator library, available at https://pub.dev/packages/geolocator. Add it to your pubspec.yaml
file by typing in your Terminal:
dependencies:
flutter:
sdk: flutter
geolocator: ^12.0.0
The geolocator library in Dart is used for accessing geolocation information. It allows developers to:
- Get the Current Location: Retrieve the current position of the device (latitude and longitude).
- Track Location Changes: Monitor and get updates when the device’s location changes over time.
- Check Location Permissions: Determine if the app has permission to access the device’s location and request permission if needed.
- Calculate Distances: Compute the distance between two geographic points.
- Geocoding and Reverse Geocoding: Convert addresses into geographic coordinates and vice versa.
This library is commonly used in Flutter apps to create location-based features like maps, navigation, and location-aware services.
Step 1: Create a StatefulWidget
we created a new file called geolocation.dart
, and then create a stateful widget called LocationScreen
;
import 'package:flutter/material.dart';
class LocationScreen extends StatefulWidget {
const LocationScreen({super.key});
@override
State<LocationScreen> createState() => _LocationScreenState();
}
class _LocationScreenState extends State<LocationScreen> {
@override
Widget build(BuildContext context) {
return Container();
}
}
Step 2: Initialize the Future
and initState
Start the asynchronous operation in the initState
method and manage the state manually and update the UI.
class _LocationScreenState extends State<LocationScreen> {
String myPosition = '';
@override
void initState() {
// TODO: implement initState
super.initState();
getPosition().then((Position myPos) {
myPosition =
'latitude: ${myPos.latitude.toString()} - longitude: ${myPos.longitude.toString()}';
setState(() {
myPosition = myPosition;
});
});
}
Future<Position> getPosition() async {
await Geolocator.requestPermission();
await Geolocator.isLocationServiceEnabled();
Position? position = await Geolocator.getCurrentPosition();
return position;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Current Location')),
body: Center(child: Text(myPosition)),
);
}
}
Full Code
Here is the complete code for your reference:
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
class LocationScreen extends StatefulWidget {
const LocationScreen({super.key});
@override
State<LocationScreen> createState() => _LocationScreenState();
}
class _LocationScreenState extends State<LocationScreen> {
String myPosition = '';
@override
void initState() {
// TODO: implement initState
super.initState();
getPosition().then((Position myPos) {
myPosition =
'latitude: ${myPos.latitude.toString()} - longitude: ${myPos.longitude.toString()}';
setState(() {
myPosition = myPosition;
});
});
}
Future<Position> getPosition() async {
await Geolocator.requestPermission();
await Geolocator.isLocationServiceEnabled();
Position? position = await Geolocator.getCurrentPosition();
return position;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Current Location')),
body: Center(child: Text(myPosition)),
);
}
}
Conclusion
Using Futures with StatefulWidget
in Flutter is an effective way to handle asynchronous operations such as fetching data from an API, especially when you want more control over the state management and UI updates. By manually managing the state within the State
class, you can provide a responsive and user-friendly experience, handling loading indicators, error messages, and displaying results seamlessly.
This approach involves four main steps:
- Creating a
StatefulWidget
and its associatedState
class. - Declaring state variables to manage loading status, error messages, and results.
- Initializing the Future in the
initState
method and updating the state based on the outcome of the asynchronous operation. - Conditionally updating the UI based on the current state.
Following these steps allows you to bypass the FutureBuilder
widget and directly manage the asynchronous operation’s lifecycle, providing a clear and flexible way to handle complex scenarios in your Flutter applications. This method ensures that your application remains responsive and provides feedback to users during data fetches, resulting in a smooth and polished user experience.
[…] this example, we will build the same UI that we built in the previous article: how to use Futures with StatefulWidgets. We will find the user location coordinates and show them on the screen, leveraging the Geolocator […]