Learn how to display data fetched from the internet in your Flutter app using FutureBuilder and ListView, handling asynchronous updates, loading states, and errors effectively.
Building a mobile application often involves fetching data from the internet and displaying it to users in a responsive and user-friendly manner. In Flutter, this can be efficiently achieved using the FutureBuilder
widget, which allows you to build widgets based on the state of a Future
. This section will guide you through the process of displaying fetched data in your Flutter app, focusing on using FutureBuilder
and ListView
to handle asynchronous data, loading states, and errors.
The FutureBuilder
widget is a powerful tool in Flutter for handling asynchronous operations. It allows you to build a widget tree based on the state of a Future
, which represents a potential value or error that will be available at some point in the future. The FutureBuilder
widget listens to the Future
and rebuilds its widget tree whenever the Future
completes, either with data or an error.
Future
that the FutureBuilder
is listening to. This is typically an asynchronous operation, such as a network request.Future
completes. It receives a BuildContext
and an AsyncSnapshot
, which contains the state of the Future
.none
: No connection has been made yet.waiting
: The Future
is still running.active
: The Future
is active and may produce data at any time.done
: The Future
has completed, either with data or an error.Let’s explore how to use FutureBuilder
to display a list of posts fetched from the internet. We’ll assume you have a Future<List<Post>>
that fetches posts from an API.
class PostsPage extends StatelessWidget {
final Future<List<Post>> posts;
PostsPage({Key? key, required this.posts}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Posts')),
body: FutureBuilder<List<Post>>(
future: posts,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('An error occurred: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No posts found.'));
} else {
return ListView(
children: snapshot.data!.map((post) => ListTile(
title: Text(post.title),
subtitle: Text(post.body),
)).toList(),
);
}
},
),
);
}
}
The ListView
widget in Flutter is a scrollable list of widgets. It is perfect for displaying a list of items fetched from the internet. In the example above, we use ListView
to display each post as a ListTile
.
When the Future
completes successfully, and we have data to display, we use the ListView
widget to display each item. The ListView
is populated by mapping over the list of posts and creating a ListTile
for each one.
ListView(
children: snapshot.data!.map((post) => ListTile(
title: Text(post.title),
subtitle: Text(post.body),
)).toList(),
)
Handling loading and error states is crucial for providing a good user experience. Users should be informed when data is being loaded and when an error occurs.
While the Future
is running, we display a CircularProgressIndicator
to indicate that data is being loaded. This is done by checking if the connectionState
is ConnectionState.waiting
.
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
If the Future
completes with an error, we display an error message. This is done by checking if snapshot.hasError
is true.
else if (snapshot.hasError) {
return Center(child: Text('An error occurred: ${snapshot.error}'));
}
Let’s continue with our example of displaying a list of posts. Assume you have a Post
class and a function fetchPosts()
that returns a Future<List<Post>>
.
class Post {
final String title;
final String body;
Post({required this.title, required this.body});
}
Future<List<Post>> fetchPosts() async {
final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));
if (response.statusCode == 200) {
List jsonResponse = json.decode(response.body);
return jsonResponse.map((post) => Post(title: post['title'], body: post['body'])).toList();
} else {
throw Exception('Failed to load posts');
}
}
In your PostsPage
, you can use the fetchPosts()
function to get the posts and display them using FutureBuilder
.
class PostsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Posts')),
body: FutureBuilder<List<Post>>(
future: fetchPosts(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('An error occurred: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No posts found.'));
} else {
return ListView(
children: snapshot.data!.map((post) => ListTile(
title: Text(post.title),
subtitle: Text(post.body),
)).toList(),
);
}
},
),
);
}
}
To better understand how the app looks, here are some screenshots of the app displaying the list of posts:
ListView.builder
for large lists to improve performance by lazily building list items.Future
, including loading, error, and empty data states.Displaying fetched data in a Flutter app involves understanding how to work with asynchronous operations and updating the UI accordingly. By using FutureBuilder
and ListView
, you can create responsive and user-friendly interfaces that handle loading and error states effectively. Remember to always consider the user’s experience and handle all possible states of the Future
.