Sitemap

Functional Programming in Dart

5 min readOct 22, 2024

--

A Deep Dive into Network Responses ๐ŸŒ to Handle API Responses with Dart.

Press enter or click to view image in full size

Functional programming has transformed how we write software, enabling developers to produce more predictable, maintainable, and error-resistant code. Dart, a versatile language, supports functional programming patterns, which are especially useful for handling asynchronous operations such as API calls.

In this article, weโ€™ll explore functional programming in Dart by comparing a custom class for managing API responses with the popular dartz package. Weโ€™ll also cover additional functional programming techniques like map, fold, and Future combinators, giving you a deeper understanding of how to handle data flow effectively.

Why Functional Programming? ๐Ÿค”

Functional programming promotes writing pure functions, handling data through immutability, and eliminating side effects. This approach makes your code easier to reason about and debug, especially when managing state or asynchronous data such as API responses.

Key Concepts in Functional Programming:

  • Immutability: Data structures donโ€™t change; instead, you create new ones with modified values.
  • Pure Functions: Functions that donโ€™t modify any outside state and always return the same output for the same input.
  • Higher-Order Functions: Functions that take other functions as parameters or return them as results.

These principles are crucial for managing complex flows like handling API responses.

Custom Class for Network Responses: A Hands-On Example ๐Ÿ’ป

When dealing with API responses, the data returned can either be a success or an error. Instead of writing if-else blocks or using try-catch, functional programming offers a cleaner, more declarative way to handle these outcomes.

Letโ€™s implement a custom class using sealed classes in Dart to manage API responses functionally.

Custom Implementation

sealed class NetworkResponse {
const NetworkResponse();

T when<T>({
required T Function(dynamic data) success,
required T Function(String message) error,
});
}

class OK extends NetworkResponse {
final dynamic data;

const OK(this.data);

@override
T when<T>({
required T Function(dynamic data) success,
required T Function(String message) error,
}) {
return success(data);
}
}

class ERROR extends NetworkResponse {
final String message;

const ERROR(this.message);

@override
T when<T>({
required T Function(dynamic data) success,
required T Function(String message) error,
}) {
return error(message);
}
}

In this implementation, the when function provides an elegant, functional approach to handling both success and error states. Instead of imperative control flow, we define what happens with each outcome through function parameters, making the code more declarative.

Comparing Custom Implementation with Dartz ๐Ÿ†š

Now, letโ€™s compare this custom class with the popular dartz package, which also provides a functional programming approach using constructs like Either to handle multiple outcomes.

Dartz: Handling API Responses with Either ๐Ÿ“ฆ

Dartz provides a powerful type called Either, which represents a value that can be one of two types: Left (usually an error) or Right (a success). Hereโ€™s an example of handling API responses with Dartz:

import 'package:dartz/dartz.dart';

Future<Either<String, dynamic>> fetchApiData() async {
try {
var data = await someApiCall();
return Right(data);
} catch (e) {
return Left("Error fetching data");
}
}

Pros and Cons

Pros of Dartz:

  1. Established Pattern: Dartz provides functional programming utilities like Either and Option that are widely recognized and understood in the FP community.
  2. Chaining and Composability: Functions like map, fold, and flatMap allow easy chaining and data transformation, reducing the need for complex logic.
  3. Functional Purity: Dartz encourages avoiding exceptions by handling errors through the Left value, aligning with functional programming principles.

Cons of Dartz:

  1. External Dependency: Adding Dartz to your project increases the size of your package and requires external package management.
  2. Learning Curve: Developers unfamiliar with functional programming concepts like Either and Option may find it harder to understand at first.

Why Choose Custom Classes?

While dartz is powerful, creating a custom class offers control, simplicity, and flexibility:

Press enter or click to view image in full size

Advanced Functional Programming Techniques in Dart ๐Ÿ’ก

In addition to handling API responses, Dart supports functional programming constructs like map, fold, and Future combinators, which are invaluable for working with collections and asynchronous data.

Using map for Data Transformation

The map function is commonly used to transform a collection of data. Hereโ€™s an example:

List<int> numbers = [1, 2, 3, 4];
List<int> doubled = numbers.map((n) => n * 2).toList();
print(doubled); // [2, 4, 6, 8]

This is a simple but powerful tool that fits perfectly with functional programming, allowing data transformation without modifying the original collection.

fold: Reducing Collections

fold is another higher-order function used to reduce a collection into a single value by applying a function repeatedly:

List<int> numbers = [1, 2, 3, 4];
int sum = numbers.fold(0, (prev, curr) => prev + curr);
print(sum); // 10

This approach eliminates the need for loops, making your code more declarative and concise.

Handling Futures with Future Combinators

In Dart, Future combinators like then, catchError, and whenComplete allow you to handle asynchronous data in a functional style:

Future<int> fetchData() async {
return Future.delayed(Duration(seconds: 2), () => 42);
}

fetchData().then((value) {
print("Data received: $value");
}).catchError((error) {
print("Error occurred: $error");
});

These combinators promote chaining and clean error handling, which aligns well with functional programmingโ€™s declarative style.

Real-World Use Cases ๐ŸŒ

Functional programming in Dart shines in various scenarios:

  • State Management: Libraries like Riverpod and Flutter Bloc leverage functional programming concepts to manage app state in a predictable manner.
  • Form Validation: You can use functional patterns like map and fold to process and validate forms in Flutter apps, reducing the need for imperative if-else chains.
  • Error Handling: Handling API responses using functional programming minimizes the need for try-catch, providing cleaner error propagation.

Final Thoughts โœจ

Functional programming in Dart allows you to write more maintainable, scalable, and predictable code. Whether you choose to use dartz or a custom implementation like NetworkResponse, functional programming principles like immutability, declarative style, and pure functions will elevate the quality of your code.

By combining techniques like map, fold, and Future combinators, you can build robust and clean data flows in your Dart applications. Regardless of the approach, embracing functional programming will help you write cleaner, more expressive code, leading to fewer bugs and more maintainable projects.

Did you know you can clap for an article up to 50 times? Give it a try!

I welcome your feedback in the comments. I would also appreciate the opportunity to connect with you on LinkedIn!

--

--

Syed Abdul Basit
Syed Abdul Basit

Written by Syed Abdul Basit

๐Ÿ“ Moved to a new account โ†’ medium.com/@umairsyedahmed282 Flutter + AI content ยท Follow me there for 2026 updates