Future in Flutter

In Flutter, as in Dart, a Future represents a value or an error that will be available at some point in the future. It is a core part of Dart’s asynchronous programming model and is used extensively for tasks that take time to complete, such as fetching data from the internet, reading files, or interacting with a database.

Key Characteristics of Futures

  1. Asynchronous Operation: A Future is used to handle operations that are asynchronous, meaning they might take some time to complete and the result isn’t available immediately.
  2. Single Completion: A Future completes with either a value (successful result) or an error (failure). Once a future is completed, it cannot change its state.
  3. Await and Async: The await keyword is used to pause the execution of an async function until the future completes. This allows writing asynchronous code in a more synchronous, readable manner.

Creating and Using Futures

Creating a Future

There are several ways to create a Future in Dart:

1. Using Future.value and Future.error:

    Future.value creates a Future that completes with the provided value. This is useful when you want to simulate an asynchronous operation that successfully returns a result.

    Example:

    Suppose you have a function that fetches user data. For testing purposes, you can mock this function to return a Future that immediately completes with a mock user data.

    import 'dart:async';
    
    void main() {
      // Simulate fetching user data
      Future<Map<String, String>> fetchUserData() {
        // Mock user data
        Map<String, String> userData = {'name': 'John Doe', 'email': 'john.doe@example.com'};
        return Future.value(userData);
      }
    
      // Usage example
      fetchUserData().then((data) {
        print('User Name: ${data['name']}');
        print('User Email: ${data['email']}');
      });
    }
    Output:
    User Name: John Doe
    User Email: john.doe@example.com

    2. Using Future.error

    Future.error creates a Future that completes with an error. This is useful for simulating error conditions in asynchronous operations.

    Example:

    Continuing with the user data fetching example, you can mock the function to return a Future that immediately completes with an error.

    import 'dart:async';
    
    void main() {
      // Simulate fetching user data with an error
      Future<Map<String, String>> fetchUserDataWithError() {
        return Future.error('Failed to fetch user data');
      }
    
      // Usage example
      fetchUserDataWithError().catchError((error) {
        print('Error: $error');
      });
    }
    Output:
    Error: Failed to fetch user data

    Combining Future.value and Future.error in Tests

    In a real-world application, you often need to test both success and failure scenarios. Here’s how you can use Future.value and Future.error to mock different outcomes.

    Example:

    Consider a repository class with a method to fetch user data. In tests, you can mock this method to return either a successful result or an error.

    class UserRepository {
      Future<Map<String, String>> fetchUserData() {
        // This would normally make a network call
        return Future.value({'name': 'John Doe', 'email': 'john.doe@example.com'});
      }
    
      Future<Map<String, String>> fetchUserDataWithError() {
        // Simulate a network error
        return Future.error('Network error occurred');
      }
    }
    
    void main() {
      final repository = UserRepository();
    
      // Test successful data fetch
      repository.fetchUserData().then((data) {
        print('Success: ${data['name']}');
      }).catchError((error) {
        print('Unexpected Error: $error');
      });
    
      // Test data fetch with error
      repository.fetchUserDataWithError().then((data) {
        print('Unexpected Success: ${data['name']}');
      }).catchError((error) {
        print('Expected Error: $error');
      });
    }
    Output:
    Success: John Doe
    Expected Error: Network error occurred

    2. Using Future.delayed:

    Future.delayed is a Dart utility that allows you to create a Future that completes after a specified duration. This can be useful for simulating delays in asynchronous operations, such as network requests.

    Useful in testing scenarios where you need to simulate network latency or other delays.

    Example
    Simulating a Network Request with Delay
    import 'dart:async';
    
    void main() {
      // Function to simulate fetching user data with a delay
      Future<Map<String, String>> fetchUserData() {
        return Future.delayed(Duration(seconds: 2), () {
          return {'name': 'John Doe', 'email': 'john.doe@example.com'};
        });
      }
    
      // Usage example
      print('Fetching user data...');
      fetchUserData().then((data) {
        print('User Name: ${data['name']}');
        print('User Email: ${data['email']}');
      }).catchError((error) {
        print('Error: $error');
      });
    }

    .here, fetchUserData is defined to return a Future that completes after a 2-second delay with a mock user data map. Future.delayed Creates a Future that completes after the specified duration (Duration(seconds: 2)).

    Then and CatchError:

    • .then((data) { ... }): Executes when the Future completes successfully, printing the user data.
    • .catchError((error) { ... }): Handles any errors that occur (though in this example, no errors are thrown).
    Output
    Fetching user data...
    (after 2-second delay)
    User Name: John Doe
    User Email: john.doe@example.com

      3. Using the async and await keywords:

      The async keyword is used to mark a function as asynchronous. When you define a function with async, it returns a Future and allows you to use await within it.

      The await keyword is used to pause the execution of an async function until the awaited Future completes. This means the function will wait for the result of the Future before proceeding to the next line of code.

      Example: Fetching a Value Asynchronously
      import 'dart:async';
      
      // Function to simulate fetching a value with a delay
      Future<int> fetchValue() async {
        await Future.delayed(Duration(seconds: 2));  // Simulate a delay of 2 seconds
        return 42;  // Return a value after the delay
      }
      
      void main() async {
        print('Fetching value...');
        try {
          int value = await fetchValue();  // Await the completion of fetchValue
          print('Fetched value: $value');
        } catch (e) {
          print('Error: $e');
        }
      }
      1. async Keyword: Marks a function as asynchronous, allowing the use of await inside it.
      2. await Keyword: Pauses the function execution until the awaited Future completes.
      3. Future.delayed: Simulates a delay of 2 seconds.
      4. fetchValue Function: Asynchronously waits for 2 seconds and then returns 42.
      5. main Function:
        • Prints “Fetching value…”
        • Waits for fetchValue to complete and prints the fetched value.
      Output
      Fetching value...
      (after a 2-second delay)
      Fetched value: 42

      Handling Future Results

      There are two primary ways to handle the results of a Future: using then and catchError or using async and await.

      1. Handling Future Results: Using then and catchError:

      import 'dart:async';
      
      // Function to simulate fetching user data
      Future<Map<String, String>> fetchUserData() {
        return Future.delayed(Duration(seconds: 2), () {
          // Simulated user data
          return {'name': 'John Doe', 'email': 'john.doe@example.com'};
        });
      }
      
      void main() {
        print('Fetching user data...');
        
        // Using then to handle successful result
        fetchUserData().then((data) {
          print('User Name: ${data['name']}');
          print('User Email: ${data['email']}');
        }).catchError((error) {
          print('Error: $error');
        });
      }
        Output
        Fetching user data...
        User Name: John Doe
        User Email: john.doe@example.com

        fetchUserData Function: Returns a Future that completes after a 2-second delay with simulated user data.

        Main Function:

        • Prints “Fetching user data…” to indicate the start of the asynchronous operation.
        • Uses then to handle a successful result from fetchUserData. The provided callback prints the user’s name and email.
        • Uses catchError to handle any errors that occur during the asynchronous operation. The error message is printed in this case.

        2. Handling Future Results: Using async and await

        import 'dart:async';
        
        // Function to simulate fetching user data
        Future<Map<String, String>> fetchUserData() {
          return Future.delayed(Duration(seconds: 2), () {
            // Simulated user data
            return {'name': 'John Doe', 'email': 'john.doe@example.com'};
          });
        }
        
        void main() async {
          print('Fetching user data...');
        
          try {
            // Await the result of fetchUserData
            Map<String, String> data = await fetchUserData();
            print('User Name: ${data['name']}');
            print('User Email: ${data['email']}');
          } catch (error) {
            print('Error: $error');
          }
        }
          Output
          Fetching user data...
          User Name: John Doe
          User Email: john.doe@example.com
          1. fetchUserData Function: Same as in the previous example, returns a Future with simulated user data after a 2-second delay.
          2. Main Function:
            • Prints “Fetching user data…” to indicate the start of the asynchronous operation.
            • Uses try-catch to handle both successful results and errors.
            • await fetchUserData() waits for fetchUserData to complete and returns the result.
            • If an error occurs during the asynchronous operation, it is caught and printed in the catch block.

          Leave a Reply