Functional Error Handling in Flutter

Implementing asynchronous operations with a Left and Right pattern in Dart, especially with Flutter and Provider, would involve using an external package like dartz. This package is not included in Dart by default, but it introduces functional programming concepts like Either, Left, and Right. Here’s how you can structure it:

1. Adding the dartz Package

First, add dartz to your pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  dartz: ^0.10.0 # Check for the latest version

2. Creating the User Model

Create a simple User model:

class User {
  final String name;
  // Add other fields as needed

  User(this.name);

  // Add fromJson, toJson if you’re fetching from an API
}

3. Creating a Service Class

This class will handle fetching data and return Either:

import 'package:dartz/dartz.dart';

class UserService {
  Future<Either<String, List<User>>> fetchUsers() async {
    try {
      // Simulating network request
      await Future.delayed(Duration(seconds: 2));
      return Right([User('Alice'), User('Bob')]);
    } catch (e) {
      return Left('Failed to fetch users');
    }
  }
}

4. Creating the UserListNotifier

This will use the ChangeNotifier from the Provider package:

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

class UserListNotifier extends ChangeNotifier {
  final UserService _userService = UserService();
  List<User> users = [];
  bool isLoading = false;
  String errorMessage = '';

  void fetchUsers() async {
    isLoading = true;
    notifyListeners();

    final result = await _userService.fetchUsers();
    result.fold(
      (left) {
        errorMessage = left;
        users = [];
      },
      (right) {
        users = right;
        errorMessage = '';
      },
    );

    isLoading = false;
    notifyListeners();
  }
}

5. Using the Notifier in Your Widget

In your main widget file:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'user_list_notifier.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => UserListNotifier(),
      child: MyApp(),
    ),
  );
}

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

class UserListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(“Users”)),
      body: Center(
        child: Consumer<UserListNotifier>(
          builder: (context, notifier, child) {
            if (notifier.isLoading) return CircularProgressIndicator();

            if (notifier.errorMessage.isNotEmpty) {
              return Text(notifier.errorMessage);
            }

            return ListView.builder(
              itemCount: notifier.users.length,
              itemBuilder: (context, index) {
                return ListTile(title: Text(notifier.users[index].name));
              },
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => Provider.of<UserListNotifier>(context, listen: false).fetchUsers(),
        child: Icon(Icons.refresh),
      ),
    );
  }
}

Pros and Cons of Using Either with Provider

Pros:

Cons:

Using Either can be beneficial for projects where clear and consistent error handling is crucial, and the team is comfortable with functional programming concepts. However, for simpler projects or teams not familiar with these concepts, traditional error handling might be more straightforward.

— Rishi Banerjee
July 2023