Question #124EasyFlutter Basics

What is use of completer in flutter?

#flutter

Answer

Overview

Completer is a Dart class that allows you to manually complete a Future at any time. It's useful when you need to create a Future that doesn't follow the standard async/await pattern.


Basic Concept

A Completer creates a Future that you control.

dart
import 'dart:async';

void main() async {
  final completer = Completer<String>();

  // Get the Future
  final future = completer.future;

  // Complete it later
  Future.delayed(Duration(seconds: 2), () {
    completer.complete('Hello from Completer!');
  });

  // Await the Future
  final result = await future;
  print(result); // Hello from Completer!
}

Creating a Completer

dart
// Generic Completer
final completer = Completer<int>();

// Complete with a value
completer.complete(42);

// Complete with an error
completer.completeError(Exception('Something went wrong'));

// Check if completed
if (completer.isCompleted) {
  print('Already completed');
}

Use Case 1: Bridging Callbacks to Futures

Convert callback-based APIs to Futures.

dart
Future<String> fetchDataWithCallback() {
  final completer = Completer<String>();

  // Simulate callback-based API
  someAsyncOperation((result, error) {
    if (error != null) {
      completer.completeError(error);
    } else {
      completer.complete(result);
    }
  });

  return completer.future;
}

// Usage
final data = await fetchDataWithCallback();
print(data);

Real-world example (Platform Channel):

dart
Future<String> getBatteryLevel() {
  final completer = Completer<String>();

  const platform = MethodChannel('com.example/battery');

  platform.invokeMethod('getBatteryLevel').then((result) {
    completer.complete(result);
  }).catchError((error) {
    completer.completeError(error);
  });

  return completer.future;
}

Use Case 2: Waiting for User Action

Wait for user to complete an action (button tap, dialog dismiss).

dart
Future<bool> showConfirmationDialog(BuildContext context) {
  final completer = Completer<bool>();

  showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: Text('Confirm'),
        content: Text('Are you sure?'),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.pop(context);
              completer.complete(false); // User cancelled
            },
            child: Text('Cancel'),
          ),
          TextButton(
            onPressed: () {
              Navigator.pop(context);
              completer.complete(true); // User confirmed
            },
            child: Text('OK'),
          ),
        ],
      );
    },
  );

  return completer.future;
}

// Usage
final confirmed = await showConfirmationDialog(context);
if (confirmed) {
  print('User confirmed');
} else {
  print('User cancelled');
}

Use Case 3: Custom Stream Listener

Convert stream events to a single Future.

dart
Future<String> waitForFirstEvent(Stream<String> stream) {
  final completer = Completer<String>();

  final subscription = stream.listen((data) {
    if (!completer.isCompleted) {
      completer.complete(data); // Complete on first event
    }
  }, onError: (error) {
    if (!completer.isCompleted) {
      completer.completeError(error);
    }
  });

  // Cleanup subscription after completion
  completer.future.whenComplete(() => subscription.cancel());

  return completer.future;
}

// Usage
final firstEvent = await waitForFirstEvent(myStream);
print('First event: $firstEvent');

Use Case 4: Timeout Handling

Add custom timeout logic.

dart
Future<String> fetchDataWithTimeout(Duration timeout) {
  final completer = Completer<String>();

  // Start async operation
  fetchData().then((result) {
    if (!completer.isCompleted) {
      completer.complete(result);
    }
  }).catchError((error) {
    if (!completer.isCompleted) {
      completer.completeError(error);
    }
  });

  // Timeout timer
  Timer(timeout, () {
    if (!completer.isCompleted) {
      completer.completeError(TimeoutException('Request timed out'));
    }
  });

  return completer.future;
}

// Usage
try {
  final data = await fetchDataWithTimeout(Duration(seconds: 5));
  print(data);
} catch (e) {
  print('Error: $e');
}

Use Case 5: Coordinating Multiple Async Tasks

Wait for first successful result from multiple sources.

dart
Future<String> fetchFromMultipleSources() {
  final completer = Completer<String>();

  // Try API 1
  fetchFromAPI1().then((result) {
    if (!completer.isCompleted) {
      completer.complete(result);
    }
  }).catchError((_) {});

  // Try API 2
  fetchFromAPI2().then((result) {
    if (!completer.isCompleted) {
      completer.complete(result);
    }
  }).catchError((_) {});

  // Try API 3
  fetchFromAPI3().then((result) {
    if (!completer.isCompleted) {
      completer.complete(result);
    }
  }).catchError((_) {});

  return completer.future; // Returns first successful result
}

Complete Example: Custom Loading Manager

dart
class LoadingManager {
  Completer<void>? _completer;

  Future<void> showLoading() async {
    _completer = Completer<void>();
    return _completer!.future;
  }

  void hideLoading() {
    if (_completer != null && !_completer!.isCompleted) {
      _completer!.complete();
    }
  }
}

// Usage
final loadingManager = LoadingManager();

void loadData() async {
  // Show loading and wait until it's dismissed
  final loadingFuture = loadingManager.showLoading();

  // Simulate data fetch
  await Future.delayed(Duration(seconds: 3));

  // Hide loading
  loadingManager.hideLoading();

  // Wait for loading to be dismissed
  await loadingFuture;

  print('Data loaded');
}

Completer vs Future

FeatureCompleterFuture
CreationManual (you control when it completes)Automatic (completes on its own)
Use caseCallbacks, custom async logicStandard async operations
ControlFull control over completionNo control
dart
// Future — completes automatically
Future<String> fetchData() async {
  return 'Data'; // Completes when function returns
}

// Completer — completes manually
Future<String> fetchDataWithCompleter() {
  final completer = Completer<String>();

  // Complete whenever you want
  someCallback(() {
    completer.complete('Data');
  });

  return completer.future;
}

Common Pitfalls

1. Completing Twice

dart
final completer = Completer<String>();

completer.complete('First');
completer.complete('Second'); // ❌ StateError: Already completed

// ✅ Check before completing
if (!completer.isCompleted) {
  completer.complete('Value');
}

2. Not Handling Errors

dart
final completer = Completer<String>();

// ❌ Unhandled error (if never completed)
// completer.future.then(...);

// ✅ Handle errors
completer.future.then((value) {
  print(value);
}).catchError((error) {
  print('Error: $error');
});

3. Memory Leaks

dart
// ❌ Completer never completed → Future never resolves
final completer = Completer<String>();
return completer.future; // ❌ Hangs forever

// ✅ Always complete or add timeout
Timer(Duration(seconds: 5), () {
  if (!completer.isCompleted) {
    completer.completeError(TimeoutException('Timeout'));
  }
});

Best Practices

dart
// ✅ Check if already completed
if (!completer.isCompleted) {
  completer.complete(value);
}

// ✅ Use completeError for errors
if (hasError) {
  completer.completeError(Exception('Error'));
}

// ✅ Add timeout to prevent hanging Futures
Timer(timeout, () {
  if (!completer.isCompleted) {
    completer.completeError(TimeoutException('Timeout'));
  }
});

// ✅ Cleanup resources when Future completes
completer.future.whenComplete(() {
  // Cleanup
  subscription.cancel();
});

Summary

Use CaseExample
Callbacks → FuturesPlatform channels, legacy APIs
User actionsWait for dialog dismiss, button tap
Stream → FutureGet first event from stream
Custom timeoutAdd timeout logic
Multiple sourcesReturn first successful result

When to use:

  • ✅ Converting callbacks to Futures
  • ✅ Waiting for user actions
  • ✅ Custom async control flow
  • ❌ Standard async operations (use
    text
    async/await
    )

Learn more: Completer Documentation