Linguagens

30 set, 2025

Desvendando os Completers no Dart: Simplificando o Desenvolvimento Assíncrono

Publicidade

A programação assíncrona é algo que certamente está presente no dia-a-dia de todo desenvolvedor e necessitamos frequentemente gerenciar Futures.

E é bem simples, na verdade, controlar o fluxo de uma Future para garantir que não executemos a Future antes de um código que dependa de seu resultado quando executamos a partir de uma interação do usuário, entretanto as vezes essa execução foge um pouco de nosso controle. Mas calma, no Dart existe com Completers e serve justamente para esses casos.

O Completer é uma forma de executar Futures, que podem ser completadas ou disparar um error, e gerencia-las manualmente. O Completer te dará uma maior flexibilidade no controle das Futures.

No Completer teremos 5 propriedades importantes0

/// INICIALIZAÇÃO	
final Completer<T> completer = Completer<T>();

/// RETORNA O FUTURO EM EXECÃO
final Future<T> future = completer.future;

/// RETORNA SE O FUTURO FOI CONCLUÍDO
bool completer.isCompleted

/// COMPLETA O FUTURO COM UM VALOR
completer.complete(...);

// COMPLETA O FUTURO COM UM ERRO
completer.completeError(...);

Então para usarmos o Completer podemos executar o complete(T) para quando queremos completar com sucesso e o .completeError(Object) quando queremos completar com algum error.

Future<String> fetchData() async {
  final completer = Completer<String>();
  try {
    await Future.delayed(
        const Duration(seconds: 2)); // Simulating network latency

    completer.complete('Data fetched successfully');
  } on Exception {
    completer.completeError('Data fetched successfully');
  }

  return completer.future;
}

A partir dai conseguimos utilizar a Future normalmente

Press enter or click to view image in full size

Um exemplo pratico que podemos fazer é a de criar um Timeout para chamada de uma API, no exemplo a seguir estou simulando a chamada em uma API que demora 5 segundos e configurei um Timeout de 3 segundos.

Press enter or click to view image in full size

E o Resultado será

Se eu mudar o timeout para 10 segundos obterei

Agora imagine que necessitemos executar alguma função assíncrona no construtor de uma classe, como por exemplo, iremos utilizar o Hive e necessitamos abrir o box no construtor para que utilizemos nos outros métodos.

class HiveRepository {
  late final Box<Map<String, dynamic>> box;
  HiveRepository(String boxName) {
    _init(boxName);
  }

  _init(String boxName) async {
    if (completer.isCompleted) return;

    try {
      final directory = await getApplicationDocumentsDirectory();

      Hive.init('${directory.path}/$boxName.db.hive');

      box = await Hive.openBox<Map<String, dynamic>>(boxName);
    } catch (e) {
      throw Exception(e);
    }
  }

  add(Map<String, dynamic> item) async {
    await box.add(item);
  }

  Future<List<Map<String, dynamic>>> getAll() async {
    return box.values.toList();
  }
}

O problema dessa abordagem é que não conseguimos controlar o fluxo de execução das Futures e não conseguimos garantir que o _init() seja executado antes do add() por exemplo.

Dai que entra os completers, utilizando o Completer conseguimos facilmente garantir que o box do Hive está aberto antes de chamarmos o add() ou até mesmo o values.

class HiveRepository {
  late final Box<Map<String, dynamic>> box;
  final completer = Completer<Box<Map<String, dynamic>>>();

  HiveRepository(String boxName) {
    _init(boxName);
  }

  _init(String boxName) async {
    if (completer.isCompleted) return;

    try {
      final directory = await getApplicationDocumentsDirectory();

      Hive.init('${directory.path}/$boxName.db.hive');

      var box = await Hive.openBox<Map<String, dynamic>>(boxName);
      box.clear();
      completer.complete(box);
    } catch (e) {
      completer.completeError(e);
    }
  }

  add(Map<String, dynamic> item) async {
    final box = await completer.future;
    await box.add(item);
  }

  Future<List<Map<String, dynamic>>> getAll() async {
    final box = await completer.future;
    return box.values.toList();
  }
}

E para utilizarmos basta criar uma instancia do repositório e chamar os métodos add() e getAl()

Press enter or click to view image in full size

Press enter or click to view image in full size

Legal, ne? Essa abordagem eu utilizo bastante quando crio uma adaptação do Hive por exemplo e preciso registrar no meu gerenciador de injeção de dependência como o getIt e o modular, é uma forma muito eficaz de trabalhar