When working with asynchronous API calls in Flutter using Dart, it’s crucial to provide a good user experience by showing a loading indicator. Without it, users may think the app is stuck or unresponsive.
In this tutorial, you’ll learn how to display a loading spinner (CircularProgressIndicator) while an API call is being processed. We’ll use FutureBuilder
and setState
methods, and include a simple UI to demonstrate how to show and hide the loading indicator dynamically.
This approach is especially useful for beginners building apps using Flutter. For a better understanding of asynchronous programming in Dart, check out Dart Asynchronous Programming Guide.
Dependencies
No additional packages needed, just use http
for API calls:
dependencies:
flutter:
sdk: flutter
http: ^0.13.5
dependencies:
flutter:
sdk: flutter
http: ^0.13.5
Step-by-step Code Implementation
1. Main UI (main.dart)
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'api_service.dart';
void main() => runApp(MyApp());
class MyApp extends <a href="https://aliendro.id/create-ui-with-statelesswidget-in-dart/">StatelessWidget</a> {
Widget build(BuildContext context) {
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'api_service.dart';
void main() => runApp(MyApp());
class MyApp extends <a href="https://aliendro.id/create-ui-with-statelesswidget-in-dart/">StatelessWidget</a> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Loading Demo',
home: ApiCallPage(),
);
}
}
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'api_service.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Loading Demo',
home: ApiCallPage(),
);
}
}
2. API Call Page (api_call_page.dart)
import 'package:flutter/material.dart';
import 'api_service.dart';
class ApiCallPage extends <a href="https://aliendro.id/statefulwidget-vs-statelesswidget-in-dart-guide/">StatefulWidget</a> {
_ApiCallPageState createState() => _ApiCallPageState();
class _ApiCallPageState extends State<ApiCallPage> {
final data = await ApiService.getData();
result = data ?? 'Failed to load data';
Widget build(BuildContext context) {
appBar: AppBar(title: Text("API Loading Example")),
? CircularProgressIndicator()
mainAxisAlignment: MainAxisAlignment.center,
child: Text("Fetch Data"),
import 'package:flutter/material.dart';
import 'api_service.dart';
class ApiCallPage extends <a href="https://aliendro.id/statefulwidget-vs-statelesswidget-in-dart-guide/">StatefulWidget</a> {
@override
_ApiCallPageState createState() => _ApiCallPageState();
}
class _ApiCallPageState extends State<ApiCallPage> {
bool isLoading = false;
String result = '';
void fetchData() async {
setState(() {
isLoading = true;
});
final data = await ApiService.getData();
setState(() {
isLoading = false;
result = data ?? 'Failed to load data';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("API Loading Example")),
body: Center(
child: isLoading
? CircularProgressIndicator()
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(result),
SizedBox(height: 20),
ElevatedButton(
onPressed: fetchData,
child: Text("Fetch Data"),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'api_service.dart';
class ApiCallPage extends StatefulWidget {
@override
_ApiCallPageState createState() => _ApiCallPageState();
}
class _ApiCallPageState extends State<ApiCallPage> {
bool isLoading = false;
String result = '';
void fetchData() async {
setState(() {
isLoading = true;
});
final data = await ApiService.getData();
setState(() {
isLoading = false;
result = data ?? 'Failed to load data';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("API Loading Example")),
body: Center(
child: isLoading
? CircularProgressIndicator()
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(result),
SizedBox(height: 20),
ElevatedButton(
onPressed: fetchData,
child: Text("Fetch Data"),
),
],
),
),
);
}
}
3. API Service (api_service.dart)
import 'package:http/http.dart' as http;
static Future<String?> getData() async {
final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
if (response.statusCode == 200) {
final data = json.decode(response.body);
return "Error: ${response.statusCode}";
import 'package:http/http.dart' as http;
import 'dart:convert';
class ApiService {
static Future<String?> getData() async {
try {
final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
if (response.statusCode == 200) {
final data = json.decode(response.body);
return data['title'];
} else {
return "Error: ${response.statusCode}";
}
} catch (e) {
return "Exception: $e";
}
}
}
import 'package:http/http.dart' as http;
import 'dart:convert';
class ApiService {
static Future<String?> getData() async {
try {
final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
if (response.statusCode == 200) {
final data = json.decode(response.body);
return data['title'];
} else {
return "Error: ${response.statusCode}";
}
} catch (e) {
return "Exception: $e";
}
}
}
This simple architecture separates UI from API logic and handles loading state efficiently. You can reuse this pattern across your app for various network operations.
Post Views: 29