Md Mominul Islam | Software and Data Enginnering | SQL Server, .NET, Power BI, Azure Blog

while(!(succeed=try()));

LinkedIn Portfolio Banner

Latest

Home Top Ad

Responsive Ads Here

Tuesday, August 26, 2025

Master Flutter & Dart: Complete Mobile App Development Course - Chapter 6: Working with APIs and Networking

 



Table of Contents

  1. Introduction to REST APIs and Networking in Flutter

    • 1.1 What Are REST APIs?

    • 1.2 Importance of APIs in Mobile Apps

    • 1.3 Why Use Flutter for API Integration?

  2. Setting Up Your Flutter Environment for Networking

    • 2.1 Installing the http Package

    • 2.2 Configuring Permissions for Internet Access

    • 2.3 Project Setup for API Integration

  3. Making HTTP Requests with the http Package

    • 3.1 Understanding HTTP Methods (GET, POST, PUT, DELETE)

    • 3.2 Making a Simple GET Request

    • 3.3 Handling POST Requests

  4. Parsing JSON Data in Dart

    • 4.1 What Is JSON?

    • 4.2 Converting JSON to Dart Objects

    • 4.3 Using json_serializable for Complex JSON

  5. Error Handling and Loading States

    • 5.1 Common Network Errors

    • 5.2 Implementing Try-Catch Blocks

    • 5.3 Displaying Loading Indicators

    • 5.4 Showing User-Friendly Error Messages

  6. Caching API Responses for Offline Support

    • 6.1 Why Cache Data?

    • 6.2 Using shared_preferences for Simple Caching

    • 6.3 Implementing sqflite for Advanced Caching

  7. Practical Exercise: Building a Weather App

    • 7.1 Choosing a Public Weather API

    • 7.2 Setting Up the Weather App Project

    • 7.3 Fetching Weather Data

    • 7.4 Parsing and Displaying Weather Data

    • 7.5 Adding Error Handling

    • 7.6 Implementing Offline Support

    • 7.7 Enhancing the UI with Weather Icons

  8. Best Practices for API Integration in Flutter

    • 8.1 Structuring API Calls

    • 8.2 Using Clean Architecture

    • 8.3 Optimizing Network Performance

  9. Pros and Cons of Using the http Package

    • 9.1 Advantages of the http Package

    • 9.2 Limitations and Challenges

    • 9.3 Alternatives to the http Package

  10. Real-Life Use Cases and Examples

    • 10.1 E-Commerce App: Fetching Product Listings

    • 10.2 Social Media App: Loading User Feeds

    • 10.3 News App: Displaying Articles

  11. Conclusion and Next Steps

    • 11.1 Summary of Key Concepts

    • 11.2 Further Learning Resources


1. Introduction to REST APIs and Networking in Flutter

1.1 What Are REST APIs?

REST (Representational State Transfer) APIs are a standard way for applications to communicate with servers over the internet. They use HTTP methods like GET, POST, PUT, and DELETE to perform operations on resources, typically returning data in JSON format. In mobile apps, REST APIs fetch dynamic data, such as weather updates or user profiles, enabling real-time interactivity.

1.2 Importance of APIs in Mobile Apps

APIs are the backbone of modern mobile apps. They allow apps to:

  • Fetch real-time data (e.g., weather forecasts, stock prices).

  • Sync user data across devices (e.g., social media posts).

  • Integrate third-party services (e.g., payment gateways, maps). Without APIs, apps would be static and limited in functionality.

1.3 Why Use Flutter for API Integration?

Flutter, paired with Dart, offers robust tools for networking:

  • Cross-Platform: Write one codebase for iOS, Android, and web.

  • Hot Reload: Instantly see API integration results.

  • Rich Ecosystem: Libraries like http simplify network requests.

  • Asynchronous Programming: Dart’s async/await ensures smooth UI performance during network calls.


2. Setting Up Your Flutter Environment for Networking

2.1 Installing the http Package

The http package is Flutter’s go-to library for making HTTP requests. To add it:

  1. Open pubspec.yaml.

  2. Add under dependencies:

    http: ^1.2.1
  3. Run:

    flutter pub get

2.2 Configuring Permissions for Internet Access

For Android, add to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>

For iOS, add to ios/Runner/Info.plist:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

2.3 Project Setup for API Integration

Create a new Flutter project:

flutter create weather_app
cd weather_app

Organize your project:

  • lib/models/: For data models.

  • lib/services/: For API calls.

  • lib/screens/: For UI screens.


3. Making HTTP Requests with the http Package

3.1 Understanding HTTP Methods

  • GET: Retrieve data (e.g., fetch weather data).

  • POST: Send data to the server (e.g., submit a form).

  • PUT: Update existing data.

  • DELETE: Remove data.

3.2 Making a Simple GET Request

Here’s an example using the JSONPlaceholder API:

import 'package:http/http.dart' as http;
import 'dart:convert';

Future<void> fetchData() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
  if (response.statusCode == 200) {
    print(jsonDecode(response.body));
  } else {
    print('Failed to load data: ${response.statusCode}');
  }
}

3.3 Handling POST Requests

To send data:

Future<void> postData() async {
  final response = await http.post(
    Uri.parse('https://jsonplaceholder.typicode.com/posts'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({'title': 'New Post', 'body': 'Content', 'userId': 1}),
  );
  if (response.statusCode == 201) {
    print('Post created: ${jsonDecode(response.body)}');
  } else {
    print('Failed to create post: ${response.statusCode}');
  }
}

4. Parsing JSON Data in Dart

4.1 What Is JSON?

JSON (JavaScript Object Notation) is a lightweight format for data exchange. Example:

{
  "id": 1,
  "title": "Sample Post",
  "body": "This is a sample post."
}

4.2 Converting JSON to Dart Objects

Create a Post model:

class Post {
  final int id;
  final String title;
  final String body;

  Post({required this.id, required this.title, required this.body});

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'],
      title: json['title'],
      body: json['body'],
    );
  }
}

Fetch and parse:

Future<Post> fetchPost() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
  if (response.statusCode == 200) {
    return Post.fromJson(jsonDecode(response.body));
  } else {
    throw Exception('Failed to load post');
  }
}

4.3 Using json_serializable for Complex JSON

For complex JSON, use json_serializable:

  1. Add to pubspec.yaml:

    dependencies:
      json_annotation: ^4.8.1
    dev_dependencies:
      build_runner: ^2.4.6
      json_serializable: ^6.7.1
  2. Create a model:

    import 'package:json_annotation/json_annotation.dart';
    
    part 'post.g.dart';
    
    @JsonSerializable()
    class Post {
      final int id;
      final String title;
      final String body;
    
      Post({required this.id, required this.title, required this.body});
    
      factory Post.fromJson(Map<String, dynamic> json) => _$PostFromJson(json);
      Map<String, dynamic> toJson() => _$PostToJson(this);
    }
  3. Run:

    flutter pub run build_runner build

5. Error Handling and Loading States

5.1 Common Network Errors

  • 404 Not Found: Resource doesn’t exist.

  • 500 Server Error: Server-side issue.

  • Timeout: Request takes too long.

  • No Internet: Device is offline.

5.2 Implementing Try-Catch Blocks

Future<Post> fetchPost() async {
  try {
    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
    if (response.statusCode == 200) {
      return Post.fromJson(jsonDecode(response.body));
    } else {
      throw Exception('Failed to load post: ${response.statusCode}');
    }
  } catch (e) {
    throw Exception('Network error: $e');
  }
}

5.3 Displaying Loading Indicators

Use a FutureBuilder:

FutureBuilder<Post>(
  future: fetchPost(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return Center(child: CircularProgressIndicator());
    } else if (snapshot.hasError) {
      return Center(child: Text('Error: ${snapshot.error}'));
    } else if (snapshot.hasData) {
      return Text(snapshot.data!.title);
    }
    return Container();
  },
)

5.4 Showing User-Friendly Error Messages

Use a SnackBar:

void showError(BuildContext context, String message) {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text(message)),
  );
}

6. Caching API Responses for Offline Support

6.1 Why Cache Data?

Caching improves performance and supports offline use. For example, a weather app can show cached data when offline.

6.2 Using shared_preferences for Simple Caching

  1. Add to pubspec.yaml:

    shared_preferences: ^2.2.2
  2. Cache data:

    import 'package:shared_preferences/shared_preferences.dart';
    
    Future<void> cacheData(String key, String data) async {
      final prefs = await SharedPreferences.getInstance();
      await prefs.setString(key, data);
    }
    
    Future<String?> getCachedData(String key) async {
      final prefs = await SharedPreferences.getInstance();
      return prefs.getString(key);
    }

6.3 Implementing sqflite for Advanced Caching

For larger datasets, use sqflite:

  1. Add to pubspec.yaml:

    sqflite: ^2.3.0
  2. Create a database:

    import 'package:sqflite/sqflite.dart';
    import 'package:path/path.dart';
    
    class DatabaseHelper {
      static final DatabaseHelper instance = DatabaseHelper._init();
      static Database? _database;
    
      DatabaseHelper._init();
    
      Future<Database> get database async {
        if (_database != null) return _database!;
        _database = await _initDB('posts.db');
        return _database!;
      }
    
      Future<Database> _initDB(String filePath) async {
        final dbPath = await getDatabasesPath();
        final path = join(dbPath, filePath);
        return await openDatabase(path, version: 1, onCreate: _createDB);
      }
    
      Future _createDB(Database db, int version) async {
        await db.execute('''
          CREATE TABLE posts (
            id INTEGER PRIMARY KEY,
            title TEXT,
            body TEXT
          )
        ''');
      }
    
      Future<void> insertPost(Post post) async {
        final db = await database;
        await db.insert('posts', {'id': post.id, 'title': post.title, 'body': post.body});
      }
    
      Future<List<Post>> getPosts() async {
        final db = await database;
        final maps = await db.query('posts');
        return List.generate(maps.length, (i) => Post.fromJson(maps[i]));
      }
    }

7. Practical Exercise: Building a Weather App

7.1 Choosing a Public Weather API

We’ll use the OpenWeatherMap API. Sign up at openweathermap.org to get an API key.

7.2 Setting Up the Weather App Project

Create a new Flutter project and add dependencies:

dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.1
  json_annotation: ^4.8.1
  shared_preferences: ^2.2.2
dev_dependencies:
  build_runner: ^2.4.6
  json_serializable: ^6.7.1

7.3 Fetching Weather Data

Create a Weather model:

import 'package:json_annotation/json_annotation.dart';

part 'weather.g.dart';

@JsonSerializable()
class Weather {
  final String city;
  final double temperature;
  final String description;

  Weather({required this.city, required this.temperature, required this.description});

  factory Weather.fromJson(Map<String, dynamic> json) => _$WeatherFromJson(json);
  Map<String, dynamic> toJson() => _$WeatherToJson(this);
}

Create an API service:

import 'package:http/http.dart' as http;
import 'dart:convert';
import 'weather.dart';

class WeatherService {
  final String apiKey = 'YOUR_API_KEY';
  final String baseUrl = 'https://api.openweathermap.org/data/2.5/weather';

  Future<Weather> fetchWeather(String city) async {
    final response = await http.get(Uri.parse('$baseUrl?q=$city&appid=$apiKey&units=metric'));
    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      return Weather.fromJson({
        'city': data['name'],
        'temperature': data['main']['temp'],
        'description': data['weather'][0]['description'],
      });
    } else {
      throw Exception('Failed to load weather');
    }
  }
}

7.4 Parsing and Displaying Weather Data

Create the main UI:

import 'package:flutter/material.dart';
import 'weather_service.dart';
import 'weather.dart';

void main() => runApp(WeatherApp());

class WeatherApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(primarySwatch: Colors.blue),
      home: WeatherScreen(),
    );
  }
}

class WeatherScreen extends StatefulWidget {
  @override
  _WeatherScreenState createState() => _WeatherScreenState();
}

class _WeatherScreenState extends State<WeatherScreen> {
  final WeatherService _weatherService = WeatherService();
  final TextEditingController _controller = TextEditingController();
  Weather? _weather;
  bool _isLoading = false;
  String? _error;

  Future<void> _fetchWeather() async {
    setState(() {
      _isLoading = true;
      _error = null;
    });
    try {
      final weather = await _weatherService.fetchWeather(_controller.text);
      setState(() {
        _weather = weather;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _error = e.toString();
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Weather App')),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _controller,
              decoration: InputDecoration(labelText: 'Enter city'),
            ),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: _fetchWeather,
              child: Text('Get Weather'),
            ),
            SizedBox(height: 16),
            if (_isLoading) CircularProgressIndicator(),
            if (_error != null) Text('Error: $_error', style: TextStyle(color: Colors.red)),
            if (_weather != null)
              Column(
                children: [
                  Text('City: ${_weather!.city}', style: TextStyle(fontSize: 24)),
                  Text('Temperature: ${_weather!.temperature}°C', style: TextStyle(fontSize: 20)),
                  Text('Description: ${_weather!.description}', style: TextStyle(fontSize: 20)),
                ],
              ),
          ],
        ),
      ),
    );
  }
}

7.5 Adding Error Handling

Enhance error handling:

Future<Weather> fetchWeather(String city) async {
  try {
    final response = await http.get(Uri.parse('$baseUrl?q=$city&appid=$apiKey&units=metric')).timeout(Duration(seconds: 10));
    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      return Weather.fromJson({
        'city': data['name'],
        'temperature': data['main']['temp'],
        'description': data['weather'][0]['description'],
      });
    } else if (response.statusCode == 404) {
      throw Exception('City not found');
    } else {
      throw Exception('Failed to load weather: ${response.statusCode}');
    }
  } on TimeoutException {
    throw Exception('Request timed out. Please try again.');
  } catch (e) {
    throw Exception('Network error: $e');
  }
}

7.6 Implementing Offline Support

Cache weather data:

Future<void> cacheWeather(Weather weather) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('weather_${weather.city}', jsonEncode(weather.toJson()));
}

Future<Weather?> getCachedWeather(String city) async {
  final prefs = await SharedPreferences.getInstance();
  final data = prefs.getString('weather_$city');
  if (data != null) {
    return Weather.fromJson(jsonDecode(data));
  }
  return null;
}

Update _fetchWeather:

Future<void> _fetchWeather() async {
  setState(() {
    _isLoading = true;
    _error = null;
  });
  try {
    final weather = await _weatherService.fetchWeather(_controller.text);
    await cacheWeather(weather);
    setState(() {
      _weather = weather;
      _isLoading = false;
    });
  } catch (e) {
    final cachedWeather = await getCachedWeather(_controller.text);
    setState(() {
      _weather = cachedWeather;
      _error = cachedWeather == null ? e.toString() : 'Showing cached data';
      _isLoading = false;
    });
  }
}

7.7 Enhancing the UI with Weather Icons

Add the weather_icons package:

dependencies:
  weather_icons: ^3.0.0

Update the UI:

import 'package:weather_icons/weather_icons.dart';

if (_weather != null)
  Column(
    children: [
      Text('City: ${_weather!.city}', style: TextStyle(fontSize: 24)),
      Text('Temperature: ${_weather!.temperature}°C', style: TextStyle(fontSize: 20)),
      Text('Description: ${_weather!.description}', style: TextStyle(fontSize: 20)),
      Icon(
        _weather!.description.contains('cloud') ? WeatherIcons.cloud : WeatherIcons.sun,
        size: 50,
      ),
    ],
  ),

8. Best Practices for API Integration in Flutter

8.1 Structuring API Calls

Use a service class to separate API logic:

class ApiService {
  final String baseUrl;
  final http.Client client;

  ApiService(this.baseUrl, {http.Client? client}) : client = client ?? http.Client();

  Future<dynamic> get(String endpoint) async {
    final response = await client.get(Uri.parse('$baseUrl$endpoint'));
    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      throw Exception('Failed to load data');
    }
  }
}

8.2 Using Clean Architecture

Adopt clean architecture:

  • Data Layer: API calls, caching.

  • Domain Layer: Business logic, models.

  • Presentation Layer: UI, state management.

8.3 Optimizing Network Performance

  • Use pagination for large datasets.

  • Compress request/response data.

  • Implement retry logic for failed requests.


9. Pros and Cons of Using the http Package

9.1 Advantages

  • Lightweight and simple.

  • Supports all HTTP methods.

  • Easy to integrate with Flutter.

9.2 Limitations

  • No built-in caching.

  • Manual error handling required.

  • Limited advanced features (e.g., interceptors).

9.3 Alternatives

  • Dio: Supports interceptors, caching, and retries.

  • Chopper: Generates API clients with annotations.

  • GraphQL: For complex queries and real-time data.


10. Real-Life Use Cases and Examples

10.1 E-Commerce App: Fetching Product Listings

Fetch products from an API:

class Product {
  final int id;
  final String name;
  final double price;

  Product({required this.id, required this.name, required this.price});

  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(
      id: json['id'],
      name: json['name'],
      price: json['price'].toDouble(),
    );
  }
}

Future<List<Product>> fetchProducts() async {
  final response = await http.get(Uri.parse('https://api.example.com/products'));
  if (response.statusCode == 200) {
    List data = jsonDecode(response.body);
    return data.map((json) => Product.fromJson(json)).toList();
  } else {
    throw Exception('Failed to load products');
  }
}

Display products:

FutureBuilder<List<Product>>(
  future: fetchProducts(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    } else if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    } else {
      return ListView.builder(
        itemCount: snapshot.data!.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(snapshot.data![index].name),
            subtitle: Text('\$${snapshot.data![index].price}'),
          );
        },
      );
    }
  },
)

10.2 Social Media App: Loading User Feeds

Fetch user posts:

class Post {
  final String username;
  final String content;

  Post({required this.username, required this.content});

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      username: json['username'],
      content: json['content'],
    );
  }
}

Future<List<Post>> fetchPosts() async {
  final response = await http.get(Uri.parse('https://api.example.com/feed'));
  if (response.statusCode == 200) {
    List data = jsonDecode(response.body);
    return data.map((json) => Post.fromJson(json)).toList();
  } else {
    throw Exception('Failed to load feed');
  }
}

Display feed:

FutureBuilder<List<Post>>(
  future: fetchPosts(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    } else if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    } else {
      return ListView.builder(
        itemCount: snapshot.data!.length,
        itemBuilder: (context, index) {
          return Card(
            child: ListTile(
              title: Text(snapshot.data![index].username),
              subtitle: Text(snapshot.data![index].content),
            ),
          );
        },
      );
    }
  },
)

10.3 News App: Displaying Articles

Fetch news articles:

class Article {
  final String title;
  final String summary;

  Article({required this.title, required this.summary});

  factory Article.fromJson(Map<String, dynamic> json) {
    return Article(
      title: json['title'],
      summary: json['summary'],
    );
  }
}

Future<List<Article>> fetchArticles() async {
  final response = await http.get(Uri.parse('https://api.example.com/news'));
  if (response.statusCode == 200) {
    List data = jsonDecode(response.body);
    return data.map((json) => Article.fromJson(json)).toList();
  } else {
    throw Exception('Failed to load articles');
  }
}

Display articles:

FutureBuilder<List<Article>>(
  future: fetchArticles(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    } else if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    } else {
      return ListView.builder(
        itemCount: snapshot.data!.length,
        itemBuilder: (context, index) {
          return Card(
            child: ListTile(
              title: Text(snapshot.data![index].title),
              subtitle: Text(snapshot.data![index].summary),
            ),
          );
        },
      );
    }
  },
)

11. Conclusion and Next Steps

11.1 Summary of Key Concepts

This chapter covered:

  • Making HTTP requests with the http package.

  • Parsing JSON data into Dart objects.

  • Handling errors and loading states.

  • Caching data for offline support.

  • Building a weather app with real-world API integration.

11.2 Further Learning Resources

  • Official Flutter Documentation: docs.flutter.dev

  • OpenWeatherMap API: openweathermap.org

  • Dart API Docs: api.dart.dev

  • Explore advanced topics like GraphQL or WebSocket for real-time apps.

No comments:

Post a Comment

Thanks for your valuable comment...........
Md. Mominul Islam