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, 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:

Flutter Course: Adding a Loading Indicator in 15.10

Flutter is an open-source UI software development kit (SDK) developed by Google, supporting the creation of high-performance applications across various platforms, including mobile, web, and desktop apps. Flutter comes with many features that allow for rapid building and deployment of entire applications. In this tutorial, we will explain how to add a loading indicator to a Flutter application. Loading indicators are important elements that visually signal to users that a task is in progress, significantly enhancing the user experience.

1. What is a Loading Indicator?

A loading indicator is a UI element that signals to users that data is being loaded. It helps users understand the responsiveness of the app and alleviates concerns about potential delays. Flutter offers various loading indicators such as:

  • LinearProgressIndicator: Horizontal progress bar
  • CircularProgressIndicator: Circular progress bar
  • Custom loading indicators: Can be customized utilizing Flutter’s flexibility

2. Project Setup

To add a loading indicator, an existing Flutter project is required. To create a new Flutter project, run the following command to generate the basic project structure.

flutter create loading_indicator_example

Navigate to the created project directory and open the project using an IDE (e.g., Visual Studio Code).

cd loading_indicator_example

3. Adding a Loading Indicator

To add a loading indicator, we first need to define the user interface (UI). The following code is an example that uses CircularProgressIndicator and LinearProgressIndicator.


import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Loading Indicator Example',
      home: LoadingIndicatorDemo(),
    );
  }
}

class LoadingIndicatorDemo extends StatefulWidget {
  @override
  _LoadingIndicatorDemoState createState() => _LoadingIndicatorDemoState();
}

class _LoadingIndicatorDemoState extends State {
  bool _isLoading = false;

  void _fetchData() async {
    setState(() {
      _isLoading = true;
    });

    // Simulate a network request
    await Future.delayed(Duration(seconds: 3));

    setState(() {
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Loading Indicator Example'),
      ),
      body: Center(
        child: _isLoading 
            ? CircularProgressIndicator() 
            : ElevatedButton(
                onPressed: _fetchData, 
                child: Text('Fetch Data'),
              ),
      ),
    );
  }
}

The above code uses CircularProgressIndicator to show the loading state to the user. When the button is clicked, the _fetchData function is called, which waits for 3 seconds before hiding the loading indicator.

4. Adding a LinearProgressIndicator

You can implement the same way to show the loading state using LinearProgressIndicator. To change the layout, simply modify the code.


import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Loading Indicator Example',
      home: LoadingIndicatorDemo(),
    );
  }
}

class LoadingIndicatorDemo extends StatefulWidget {
  @override
  _LoadingIndicatorDemoState createState() => _LoadingIndicatorDemoState();
}

class _LoadingIndicatorDemoState extends State {
  bool _isLoading = false;

  void _fetchData() async {
    setState(() {
      _isLoading = true;
    });

    await Future.delayed(Duration(seconds: 3));

    setState(() {
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Loading Indicator Example'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          _isLoading 
                ? LinearProgressIndicator() 
                : ElevatedButton(
                    onPressed: _fetchData, 
                    child: Text('Fetch Data'),
                  ),
        ],
      )
    );
  }
}

5. Customizing Loading Indicators

You can customize the default loading indicators provided by Flutter to create a more unique and appealing UI. The following is an example of customization by adjusting color, size, and shape.


import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Customized Loading Indicator Example',
      home: LoadingIndicatorDemo(),
    );
  }
}

class LoadingIndicatorDemo extends StatefulWidget {
  @override
  _LoadingIndicatorDemoState createState() => _LoadingIndicatorDemoState();
}

class _LoadingIndicatorDemoState extends State {
  bool _isLoading = false;

  void _fetchData() async {
    setState(() {
      _isLoading = true;
    });

    await Future.delayed(Duration(seconds: 3));

    setState(() {
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Customized Loading Indicator Example'),
      ),
      body: Center(
        child: _isLoading 
          ? CircularProgressIndicator(
              valueColor: AlwaysStoppedAnimation(Colors.green),
              strokeWidth: 10.0,
            ) 
          : ElevatedButton(
              onPressed: _fetchData, 
              child: Text('Fetch Data'),
            ),
      ),
    );
  }
}

6. Conclusion

Loading indicators are important elements that improve the user experience and maintain the identity of the application. With Flutter, you can easily add loading indicators and customize them as needed. We hope this tutorial helps you successfully add loading indicators to your Flutter applications.

7. Additional Resources

For more information about loading indicators, please refer to the official Flutter documentation or community forums. They contain various examples and tips that will be useful for troubleshooting.

Thank you!

Flutter Course: Finishing the Weather App: 15.11

In this course, we will go through the final stages of completing a weather app using Flutter. In this section, we will cover several important elements such as optimizing the app’s user interface (UI), data handling, and API integration. All of these processes are aimed at providing a better experience for the user.

Project Environment Setup

First, ensure that the project is properly set up. Make sure you have installed the Flutter SDK along with Android Studio or Visual Studio Code. Also, to ensure that the Flutter environment is correctly configured, run the following command in the terminal:

flutter doctor

This command will check all configured environments and notify you if there are any additional setups required.

Integrating the API

The core of the weather application is the data source. We will use the OpenWeatherMap API to fetch real-time weather data. To get an API key, sign up on the OpenWeatherMap website and obtain your key.

We will use the HTTP library to call the API. Use the following command to add the HTTP library:

flutter pub add http

Next, let’s implement a function to send HTTP requests:

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

Future fetchWeatherData(String city) async {
    final apiKey = 'YOUR_API_KEY';
    final url = 'https://api.openweathermap.org/data/2.5/weather?q=$city&appid=$apiKey&units=metric';
    final response = await http.get(Uri.parse(url));

    if (response.statusCode == 200) {
        // Data processing logic
    } else {
        throw Exception('Failed to load weather data');
    }
}

Data Modeling

To model the weather data, we need to create a model class that can map the JSON data to the class. For example:

class Weather {
    final String cityName;
    final double temperature;
    final String description;

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

    factory Weather.fromJson(Map json) {
        return Weather(
            cityName: json['name'],
            temperature: json['main']['temp'],
            description: json['weather'][0]['description'],
        );
    }
}

State Management

In Flutter, there are various ways to manage state. In this example, we will use the Provider pattern. We will add the Provider package and implement the WeatherProvider class:

import 'package:flutter/material.dart';

class WeatherProvider with ChangeNotifier {
    Weather? _weather;

    Weather? get weather => _weather;

    Future getWeather(String city) async {
        // API call and data fetching
        final data = await fetchWeatherData(city);
        _weather = Weather.fromJson(data);
        notifyListeners();
    }
}

Building the UI

Now let’s implement the most important and interesting part, the UI. Using Flutter widgets, we will create a simple yet intuitive user interface. The basic structure is as follows:

Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text('Weather App'),
        ),
        body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                    Text('City Name: ${weatherProvider.weather?.cityName}'),
                    Text('Temperature: ${weatherProvider.weather?.temperature}°C'),
                    Text('Condition: ${weatherProvider.weather?.description}'),
                ],
            ),
        ),
    );
}

Updating State

The UI needs to be updated whenever the state changes. To do this, use the Consumer widget from Provider to detect state changes and rebuild the UI:

Consumer(
    builder: (context, weatherProvider, child) {
        if (weatherProvider.weather != null) {
            return WeatherDisplay(weather: weatherProvider.weather!);
        } else {
            return CircularProgressIndicator(); // Loading data
        }
    },
)

Final Steps and Build

Once all the code is written, it’s time to run the app and check the results. Enter the following command in the terminal to run the app:

flutter run

With that, a simple weather application using Flutter has been completed. Additionally, you can add various features to improve the user experience. For example, adding search functionality and location-based services can make it more convenient for users to check weather information.

Conclusion

Through this course, you learned the fundamental process of building a weather app using Flutter, including user interfaces, API usage, and state management. Try to continuously develop the app by adding and improving various features. Thank you.