Flutter Course: Using the HTTP Package

Author: Your Name | Date: October 2023

Introduction

Flutter is an open-source UI software development kit (SDK) created by Google,
which allows rapid development of applications that run on multiple platforms using a single codebase.
In this tutorial, we will explore the http package, commonly used to communicate with external APIs in Flutter.
This package is a tool that simplifies communication with RESTful APIs.

Installing the http Package

To use the http package, you first need to add it to your pubspec.yaml file.
Add the following code to the dependencies: section:

dependencies:
  http: ^0.13.4

After adding the package, use the following command to install it:

flutter pub get

Basic Usage

To use the http package, you first need to import the relevant classes.
Write your code like this to use the http package:

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

Now, let’s look at how to send a GET request to an external API.
For example, checking user information from the JSONPlaceholder API:

Future fetchData() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
  
  if (response.statusCode == 200) {
    // When the request was successful
    print('Data: ${response.body}');
  } else {
    // When the request failed
    throw Exception('Failed to fetch data.');
  }
}

The function above operates asynchronously,
it requests user information from the API, printing the data if the response is successful.

Sending a POST Request

Now, let’s look at how to send a POST request.
For example, when sending data to an API that creates a new user:

Future createUser() async {
  final response = await http.post(
    Uri.parse('https://jsonplaceholder.typicode.com/users'),
    headers: {
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode({
      'name': 'John Doe',
      'username': 'johndoe',
      'email': 'johndoe@example.com',
    }),
  );

  if (response.statusCode == 201) {
    // User was successfully created.
    print('User created: ${response.body}');
  } else {
    // When the request failed
    throw Exception('Failed to create user');
  }
}

In the above code, the `jsonEncode` function is a built-in JSON encoding function in Dart,
used to convert Dart objects into JSON-formatted strings.

Query Parameters and Headers

Let’s learn how to add query parameters in HTTP GET requests
and how to set request headers.
For example, you can filter data based on certain conditions:

Future fetchFilteredData() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users?filter=active'), 
    headers: {
      'Authorization': 'Bearer some_api_key',
    }
  );

  if (response.statusCode == 200) {
    print('Filtered data: ${response.body}');
  } else {
    throw Exception('Failed to fetch filtered data.');
  }
}

Here, `filter=active` is a query parameter provided by the API,
and the `Authorization` header is a way to provide authentication information to the server by including the API key.

Error Handling

When making API requests, you should always handle errors.
By checking the HTTP request’s status code and handling exceptions,
you can provide a better experience for users:

Future fetchDataWithErrorHandling() async {
  try {
    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));

    if (response.statusCode == 200) {
      print('Data: ${response.body}');
    } else {
      throw Exception('Server Error: ${response.statusCode}');
    }
  } catch (e) {
    print('Error occurred during request: $e');
  }
}

The above code uses a try-catch statement to
handle potential exceptions that may occur during asynchronous requests.

Reusing the HTTP Client

Reusing the HTTP client can optimize performance and
allow for common use across multiple requests.
You can create an HTTP client like this:

class ApiService {
  final http.Client client;

  ApiService(this.client);
  
  Future fetchData() async {
    final response = await client.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
    // Same data processing logic as above...
  }
}

// Example usage:
final apiService = ApiService(http.Client());
await apiService.fetchData();

By injecting the client into a class like this,
you can improve reusability and ease of testing.

Parsing JSON Data

You can parse JSON data received from an API for use.
It’s common practice to create model classes to consume the data internally:

class User {
  final int id;
  final String name;
  final String username;
  final String email;

  User({required this.id, required this.name, required this.username, required this.email});

  factory User.fromJson(Map json) {
    return User(
      id: json['id'],
      name: json['name'],
      username: json['username'],
      email: json['email'],
    );
  }
}

The above model class shows how
to convert JSON data into objects.

Handling List Data

Let’s learn how to handle multiple JSON objects as a list.
To do this, you’ll need to properly transform the data received from the API:

Future> fetchUsers() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
  
  if (response.statusCode == 200) {
    List jsonData = jsonDecode(response.body);
    return jsonData.map((data) => User.fromJson(data)).toList();
  } else {
    throw Exception('Failed to fetch user list.');
  }
}

This code parses the JSON data received from the server and
returns a list of user information.

Handling HTTP Redirection

Certain API requests may require handling redirection.
In this case, it is automatically handled when using http.Client,
but let’s also look at how to manually handle redirection:

Future handleRedirect() async {
  final response = await http.get(Uri.parse('https://httpbin.org/redirect/1'));

  if (response.statusCode == 200) {
    print('Final URL: ${response.request!.url}');
  } else {
    print('Request failed: ${response.statusCode}');
  }
}

In the example above, it will automatically follow redirection for the HTTP request and output the final URL.

Comprehensive Example: Creating a CRUD Application

Based on what we have learned so far, let’s discuss how to implement a simple CRUD (Create, Read, Update, Delete) application.
For example, you can implement functionalities to add, retrieve, update, and delete users using the JSONPlaceholder API.

class UserApiService {
  final http.Client client;
  
  UserApiService(this.client);
  
  Future> fetchUsers() async {
    // Code to fetch users...
  }

  Future createUser(User user) async {
    // Code to create a user...
  }

  Future updateUser(User user) async {
    // Code to update a user...
  }

  Future deleteUser(int id) async {
    final response = await client.delete(Uri.parse('https://jsonplaceholder.typicode.com/users/$id'));
    // Logic for deletion...
  }
}

The above example defines a `UserApiService` class that includes methods for CRUD operations.
You can add functionalities by implementing actual HTTP requests.

Through this tutorial, you have gained an understanding of how to use the http package in Flutter and learned how to expand the functionality of Flutter applications through communication with RESTful APIs.
Expect more examples and advanced content in the next tutorial!

Flutter Course, 15.4 initState() Method and Exception Handling

Flutter is a powerful tool for cross-platform application development, providing developers with very useful features. In this course, we will deeply explore the initState() method, one of Flutter’s important lifecycle methods, and how to handle exceptions. This topic is a very important aspect of designing and developing Flutter applications.

1. What is the initState() Method?

In Flutter, the initState() method is the first method called in the StatefulWidget lifecycle. This method is called when the widget is first created and is used to initialize the user interface and load necessary data.

1.1 Characteristics of initState

  • Called only once when the widget is created.
  • Suitable for starting asynchronous tasks.
  • Can update surrounding state.

1.2 Example of the initState() Method

class MyHomePage extends StatefulWidget {
        @override
        _MyHomePageState createState() => _MyHomePageState();
    }

    class _MyHomePageState extends State {
        String text = "";

        @override
        void initState() {
            super.initState();
            text = 'Initialization Complete';
            print(text);
        }

        @override
        Widget build(BuildContext context) {
            return Scaffold(
                appBar: AppBar(
                    title: Text('initState Example'),
                ),
                body: Center(
                    child: Text(text),
                ),
            );
        }
    }

In the example above, the initState() method is called when the state of the StatefulWidget is initialized. Inside this method, the value of the text variable is set, and a message indicating that initialization is complete is printed.

2. Roles of the initState() Method

The initState() method serves several roles:

  • Initial Variable Setup: Sets initial values needed for the widget.
  • Data Loading: Loads data through API calls and initializes state.
  • Timer and Stream Setup: Starts timer or streams to detect changes in data.

2.1 Example: Data Loading

Here is an example of loading data using initState():

class DataFetcher extends StatefulWidget {
        @override
        _DataFetcherState createState() => _DataFetcherState();
    }

    class _DataFetcherState extends State {
        String data = '';

        @override
        void initState() {
            super.initState();
            fetchData();
        }

        void fetchData() async {
            try {
                final response = await http.get(Uri.parse('https://api.example.com/data'));
                if (response.statusCode == 200) {
                    setState(() {
                        data = response.body;
                    });
                } else {
                    throw Exception('Failed to load data');
                }
            } catch (e) {
                print('Error occurred: $e');
            }
        }

        @override
        Widget build(BuildContext context) {
            return Scaffold(
                appBar: AppBar(
                    title: Text('Data Loading Example'),
                ),
                body: Center(
                    child: Text(data),
                ),
            );
        }
    }

3. The Importance of Exception Handling

When developing Flutter applications, exception handling is very important. It contributes to enhancing user experience and increasing the application’s stability. Through exception handling, developers can take appropriate actions when errors occur and clearly communicate these errors to users.

3.1 Basic Concept of Exception Handling

Exception handling defines how an application recognizes errors and deals with situations where data is incorrect. This process involves the following steps:

  1. Error Detection: Checks if an exception has occurred at a specific point in the program.
  2. Error Handling: Performs appropriate actions regarding the occurred error.
  3. Error Propagation: Passes the error to the upper caller if necessary.

3.2 Exception Handling Syntax

In Flutter, you can perform exception handling using the try-catch syntax. Here is an example:

void fetchData() async {
        try {
            // Code for requesting data
        } catch (e) {
            print('Exception occurred: $e');
        }
    }

4. Integration of initState() and Exception Handling

When performing asynchronous tasks within initState(), it is important to use appropriate exception handling methods. This allows for the proper handling of errors that may occur during the initialization process. Here is an integrated example:

class MyApp extends StatefulWidget {
        @override
        _MyAppState createState() => _MyAppState();
    }

    class _MyAppState extends State {
        String data = '';
        String errorMessage = '';

        @override
        void initState() {
            super.initState();
            loadData();
        }

        Future loadData() async {
            try {
                // Request data from the specified URL
            } catch (e) {
                setState(() {
                    errorMessage = 'An error occurred while loading data';
                });
            }
        }

        @override
        Widget build(BuildContext context) {
            return Scaffold(
                appBar: AppBar(
                    title: Text('Integrated Exception Handling Example'),
                ),
                body: Center(
                    child: errorMessage.isNotEmpty
                        ? Text(errorMessage)
                        : Text(data),
                ),
            );
        }
    }

5. Hands-On Practice

Now it’s time for you to use the initState() method and apply exception handling to create a real Flutter application. Follow the steps below for practice:

  1. Create a StatefulWidget: Create a new StatefulWidget.
  2. Implement initState(): Implement initState() to load data during widget initialization. Please refer to the previous examples.
  3. Add Exception Handling: Before making an API call, add logic to detect errors through exception handling and show an error message to the user.

Conclusion

The initState() method and exception handling are two important elements in Flutter development. They play a key role in managing widgets and states, contributing to improving user experience. Through this course, we aim to help you understand the role of the initState() method and the methods of exception handling, equipping you with the ability to apply them to real projects. We hope you continue to explore the various features and technologies of Flutter and discover endless possibilities.

Flutter Course: Installing the Geolocator Package

In this course, we will learn how to install the Geolocator package in a Flutter application. The Geolocator package is a useful Flutter package that tracks the user’s location and provides various GPS-based features. With this package, the application can easily utilize the user’s location information and implement various features such as maps and location-based services.

1. What is the Geolocator package?

Geolocator is a package that helps easily navigate and utilize location in Flutter applications. By using this package, you can obtain the user’s location in real time via GPS, network, or other sensors. Geolocator works on both Android and iOS, supporting features such as:

  • Getting the current location
  • Receiving location updates
  • Managing location permissions
  • Calculating distances and verifying location distances

2. How to install the Geolocator package

To install the Geolocator package, you need to follow these steps.

2.1 Creating a Flutter project

If you haven’t created a Flutter project yet, create a new Flutter project with the following command:

flutter create my_location_app

Navigate to the project directory:

cd my_location_app

2.2 Adding the Geolocator package

Open the project’s pubspec.yaml file and add geolocator to the dependencies list. You should check for the latest version and add the appropriate version. Here is an example:

dependencies:
  flutter:
    sdk: flutter
  geolocator: ^9.0.0

After adding it, run the following command to install the dependencies:

flutter pub get

3. Setting up the Geolocator package

After installing the Geolocator package, you need to add some settings to use it in your application.

3.1 Android settings

To request location permissions on Android, open the AndroidManifest.xml file and add the following permissions:

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

3.2 iOS settings

For iOS, modify the Info.plist file to add a message for requesting location permissions. Please add the following:

<key>NSLocationWhenInUseUsageDescription</key>
<string>Permission is required to use the user's location.</string>

4. Implementing basic location code

Now let’s write the basic code to get the user’s location using the Geolocator package.

import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: LocationScreen(),
    );
  }
}

class LocationScreen extends StatefulWidget {
  @override
  _LocationScreenState createState() => _LocationScreenState();
}

class _LocationScreenState extends State {
  String locationMessage = "";

  @override
  void initState() {
    super.initState();
    getLocation();
  }

  Future getLocation() async {
    Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
    setState(() {
      locationMessage = "Latitude: ${position.latitude}, Longitude: ${position.longitude}";
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Location Information'),
      ),
      body: Center(
        child: Text(locationMessage),
      ),
    );
  }
}

Using the code above, you can create a Flutter application that retrieves the user’s current location (latitude and longitude).

5. Conclusion

We learned how to install and set up the Geolocator package. This allows us to efficiently utilize the user’s location information in a Flutter application. Additionally, you can use various methods of the Geolocator package to implement location tracking and other features.

Note: When using the Geolocator, be careful to request location permissions and add exception handling for cases where the user does not grant permission.

Flutter Course, 15.2 Using OpenWeatherMap

In this section, we will learn how to integrate the OpenWeatherMap API into a Flutter application. OpenWeatherMap provides various useful weather information, including real-time weather data, temperature, humidity, and wind speed. This API offers both free and paid plans, and today we will explain based on the free plan.

1. Sign up for OpenWeatherMap API and get the API key

The first step is to sign up on the OpenWeatherMap website to obtain an API key. Please follow the steps below:

  1. Go to the OpenWeatherMap website.
  2. Click “Sign Up” in the top menu to create an account.
  3. Enter your email address and password, then complete the registration process.
  4. After logging in, navigate to the “API keys” menu to check your automatically generated default API key.
  5. You can create a new API key if needed.

2. Setting up the Flutter project

Now let’s set up the Flutter project. You can either create a new Flutter application or use an existing project.

flutter create weather_app

Navigate to the project directory:

cd weather_app

Next, add the required package to the pubspec.yaml file to send HTTP requests:

dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.3

Run the following command to install all dependencies:

flutter pub get

3. Creating the weather data model

Create a Dart model class to learn the JSON data received from the OpenWeatherMap API. For example, let’s create a class for weather data.

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 json) {
    return Weather(
      city: json['name'],
      temperature: json['main']['temp'] - 273.15, // Convert Kelvin to Celsius
      description: json['weather'][0]['description'],
    );
  }
}

4. Fetching weather data with HTTP requests

Now it’s time to write the HTTP request to fetch weather information. We will send a GET request to the OpenWeatherMap API using the http package.

Here’s an example of writing a function to fetch weather information:

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

Future fetchWeather(String city) async {
  final apiKey = 'YOUR_API_KEY_HERE'; // Enter your API key here
  final response = await http.get(Uri.parse('https://api.openweathermap.org/data/2.5/weather?q=$city&appid=$apiKey'));

  if (response.statusCode == 200) {
    return Weather.fromJson(json.decode(response.body));
  } else {
    throw Exception('Failed to load weather data');
  }
}

5. Creating the user interface

Now let’s create a simple user interface to display the weather information. We will use Flutter components to show weather information based on the city name entered by the user.

Here’s a basic UI code example:

import 'package:flutter/material.dart';

class WeatherApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Weather App',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Weather Information'),
        ),
        body: WeatherInfo(),
      ),
    );
  }
}

class WeatherInfo extends StatefulWidget {
  @override
  _WeatherInfoState createState() => _WeatherInfoState();
}

class _WeatherInfoState extends State {
  String city = '';
  Weather? weather;

  final TextEditingController controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(16.0),
          child: TextField(
            controller: controller,
            decoration: InputDecoration(labelText: 'Enter City Name'),
          ),
        ),
        ElevatedButton(
          onPressed: () {
            setState(() {
              city = controller.text.trim();
            });
            fetchWeather(city).then((value) {
              setState(() {
                weather = value;
              });
            }).catchError((error) {
              showDialog(
                context: context,
                builder: (_) => AlertDialog(
                  title: Text('Error'),
                  content: Text(error.toString()),
                  actions: [
                    TextButton(
                      onPressed: () {
                        Navigator.of(context).pop();
                      },
                      child: Text('OK'),
                    ),
                  ],
                ),
              );
            });
          },
          child: Text('Get Weather'),
        ),
        if (weather != null)
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Text(
              'City: ${weather!.city}\nTemperature: ${weather!.temperature.toStringAsFixed(1)}°C\nCondition: ${weather!.description}',
              style: TextStyle(fontSize: 20),
            ),
          ),
      ],
    );
  }
}

void main() {
  runApp(WeatherApp());
}

6. Run the app and check the results

You can run the code written above on a device or emulator to check the results. Now, when you enter a city name in the input field and press the “Get Weather” button, it will display the weather information fetched through the API request on the screen.

7. Error handling and improvements

The currently implemented example provides only basic functionality, so there are various improvements that can be added. For example:

  • Location-based weather information provision: Add a feature to automatically fetch weather information based on the user’s current location.
  • Caching weather information: Add a caching mechanism to reduce response time for the same requests.
  • Improving colors and design: Refine the UI design to enhance user experience.
  • Providing various weather information: Display additional information such as humidity, wind speed, etc., besides temperature.

8. Conclusion

In this post, we have detailed how to fetch real-time weather information using Flutter and the OpenWeatherMap API. I hope this helps you learn how to use the API and add various features through practice. In the future, I encourage you to create many innovative applications using Flutter!

© 2023 Flutter Course. All rights reserved.

Flutter Course: 15.1 Concept of API

1. What is an API?

API (Application Programming Interface) is a set of agreements or rules that enable interaction between software. It defines the methods and procedures needed for different software systems to exchange information and interact with each other. APIs typically operate through communication with servers that provide specific data or functionality.

2. Types of APIs

There are various types of APIs, and they can be categorized in different ways. Commonly, they can be divided into the following types:

  • Web API: An API that provides web-based services, using protocols such as RESTful, SOAP, etc.
  • Operating System API: APIs provided to utilize the functions of an operating system, such as Windows API, POSIX API, etc.
  • Library API: Interfaces of libraries provided in specific programming languages, such as the Pandas library API in Python.
  • Database API: An API used to interact with databases, which is used to execute SQL queries and communicate with databases.

3. Importance of APIs

APIs have become essential elements in modern software development. Here are the key points of API importance:

  • Reusability: Allows reusing existing code, reducing development time.
  • Scalability: Provides a structure that allows new features to be integrated without affecting existing systems.
  • Interoperability: Enables communication between different platforms or languages.
  • Distributed Systems: Supports interactions between multiple services, such as in a microservices architecture.

4. Using APIs in Flutter

Flutter is a framework for mobile app development that allows the creation of applications that work across different platforms. Through APIs, you can communicate with back-end systems, allowing you to fetch dynamic data and present it to users.

4.1 HTTP Package

To use APIs in Flutter, you can utilize the http package. This package helps you easily handle HTTP requests with servers. Here’s how to send a GET request and receive data using the http package:

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

Future fetchData() async {
  final response = await http.get(Uri.parse('https://api.example.com/data'));

  if (response.statusCode == 200) {
    var data = json.decode(response.body);
    // Process data
  } else {
    throw Exception('Failed to load data');
  }
}

4.2 Handling JSON Data

The data received from APIs is often in JSON format. In Flutter, you can easily convert JSON data using the dart:convert library. For example, you can map JSON data to model classes:

class User {
  final String name;
  final String email;

  User({required this.name, required this.email});

  factory User.fromJson(Map json) {
    return User(
      name: json['name'],
      email: json['email'],
    );
  }
}

// Example of JSON conversion
User user = User.fromJson(json.decode(response.body));

5. Example of API Call

Let’s implement an API call with a simple example. For instance, we will write code to fetch a list of users using the free REST API called JSONPlaceholder.

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: UserListScreen(),
    );
  }
}

class UserListScreen extends StatefulWidget {
  @override
  _UserListScreenState createState() => _UserListScreenState();
}

class _UserListScreenState extends State {
  List _users = [];

  @override
  void initState() {
    super.initState();
    fetchUsers();
  }

  Future fetchUsers() async {
    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));

    if (response.statusCode == 200) {
      var jsonResponse = json.decode(response.body);
      List users = (jsonResponse as List).map((user) => User.fromJson(user)).toList();
      setState(() {
        _users = users;
      });
    } else {
      throw Exception('Failed to load users');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Users')),
      body: ListView.builder(
        itemCount: _users.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(_users[index].name),
            subtitle: Text(_users[index].email),
          );
        },
      ),
    );
  }
}

class User {
  final String name;
  final String email;

  User({required this.name, required this.email});

  factory User.fromJson(Map json) {
    return User(
      name: json['name'],
      email: json['email'],
    );
  }
}

6. Precautions When Making API Calls

There are several precautions to keep in mind when calling APIs. Here are points to be aware of when using APIs:

  • Error Handling: API calls can fail, so you must implement error handling.
  • Asynchronous Calls: Since API calls are made asynchronously, be careful not to start rendering the UI before the data is ready.
  • Security: You must manage authentication tokens and sensitive information securely during API calls.
  • Performance: Frequent API calls can impact performance; consider caching strategies if necessary.

7. Conclusion

Utilizing APIs in Flutter is a fundamental and essential skill in modern application development. By communicating with databases or external services through APIs, you can build dynamic applications that provide a richer experience for users. We hope this course has helped you understand the basic concepts of APIs and how to utilize them in Flutter.

8. Additional Learning Resources

If you want a deeper understanding, please refer to the following resources: