Como enviar ou retornar dados de chamada do widget filho para o widget pai no Flutter

Tempo de leitura: 5 minutes

Observação: a transmissão de dados do widget filho para o widget pai é diferente de retornos de chamada ou do envio de dados do widget filho para o widget pai por meio de uma função de evento. No entanto, para passar dados do widget filho para o widget pai da mesma forma que passamos dados do widget pai para o widget filho por meio da injeção de dependência, podemos usar o campo parentData doRenderBox, que é como podemos ter coisas como o widget Positioned dentro da Stack.

 

Há dois padrões possíveis para conseguir isso:

Padrão Function Callback
Padrão de método estático

Antes de implementar os dois padrões, enfatizarei o possível caso de uso.

Os retornos de chamada podem ser usados quando há dados em um widget filho que precisam ser enviados ao componente para uma alteração de estado quando um evento é acionado no widget filho. Exemplo: Vamos supor que temos uma interface de usuário que exibe e atualiza nomes na tela com base na lista de contatos. Quando um usuário clica em um contato da lista, o nome do contato é atualizado na tela.

 

1. Usando a função Callback Pattern:

Primeira etapa: Defina uma variável privada do tipo de dados de sua escolha no widget pai. No exemplo abaixo, usamos o tipo de dados “Map<String, dynamic>”.

import 'package:flutter/material.dart';

class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<StatefulWidget> createState() => ParentWidgetState();

}

class ParentWidgetState extends State<ParentWidget> {
  
  // Primeiro Passo
  Map<String, dynamic> _contact = {};
  
  }

Segunda etapa: Defina uma função de nível superior no arquivo do widget secundário, que aceite o tipo de dados que você escolher para enviar ou fazer callback e retorne void. Além disso, injete a função no construtor do widget filho.

import 'package:flutter/material.dart';

// Segundo Passo
// Implementação da Function callback
typedef MapCallback = void Function(Map<String, dynamic> val);

class ChildWidget extends StatelessWidget {

  // Segunda Passo
  final MapCallback callback;

  ChildWidget({super.key, required this.callback});
  
  }

Terceira etapa: Chame ou faça o layout do widget filho no widget pai e passe uma função anônima e use setState para definir o novo valor recebido na função anônima para a variável privada que deve receber o valor de callback ou os dados enviados.

import 'package:flutter/material.dart';
import 'package:flutter_callbacks/child_widget.dart';

class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<StatefulWidget> createState() => ParentWidgetState();

}

class ParentWidgetState extends State<ParentWidget> {
  Map<String, dynamic> _contact = {};


  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('Flutter Callbacks'),
        ),
        body: Column(
          children: [
            Container(
              height: 300.0,
              margin: const EdgeInsets.all(10.0),
              decoration: const BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(10.0)),
                  gradient: LinearGradient(
                      colors: [Colors.yellow, Colors.green],
                      begin: Alignment.centerLeft,
                      end: Alignment.centerRight,
                      tileMode: TileMode.clamp)),
              child: _contact.isEmpty
                  ? const Center(
                      child: Text(
                      'Select a Contact',
                      style: TextStyle(color: Colors.white, fontSize: 24),
                    ))
                  : Row(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: <Widget>[
                        Padding(
                            padding:
                                const EdgeInsets.only(left: 10.0, right: 10.0),
                            child: CircleAvatar(
                              radius: 35.0,
                              backgroundImage: AssetImage(_contact['image']),
                            )),
                        Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          crossAxisAlignment: CrossAxisAlignment.end,
                          children: [
                            Text(
                              _contact['name'],
                              style: const TextStyle(
                                  fontSize: 24.0,
                                  color: Colors.white70,
                                  fontWeight: FontWeight.bold),
                            ),
                            const SizedBox(
                              height: 2.0,
                            ),
                            Text(
                              _contact['number'].toString(),
                              style: const TextStyle(
                                  fontSize: 20.0, color: Colors.white70),
                            ),
                            const SizedBox(
                              height: 2.0,
                            ),
                            Text(
                              _contact['address'],
                              style: const TextStyle(
                                  fontSize: 20.0, color: Colors.white70),
                            ),
                          ],
                        ),
                      ],
                    ),
            ),
            const SizedBox(
              height: 40,
            ),
            // Third Step
            ChildWidget(callback: (val) => setState(() => _contact = val)),
          ],
        ));
  }
}

Etapa final: Chame ou faça referência à função callback em uma função de evento e passe o valor que deseja enviar. No exemplo abaixo, estamos chamando-a na função de evento onTap do widget InkWell.

import 'package:flutter/material.dart';


// Function callback Implementation
typedef MapCallback = void Function(Map<String, dynamic> val);

class ChildWidget extends StatelessWidget {
  // Function callback Implementation
  final MapCallback callback;

  ChildWidget({super.key, required this.callback});

  final List<Map<String, dynamic>> contacts = [
    {
      'name': 'Stanley',
      'number': 111111,
      'address': 'Dubai',
      'image': 'assets/stan.png'
    },
    {
      'name': 'Maryan',
      'number': 22222,
      'address': 'London',
      'image': 'assets/maryan.png'
    },
    {
      'name': 'Kingsley',
      'number': 333333,
      'address': 'New York',
      'image': 'assets/kins.png'
    },
    {
      'name': 'Mary',
      'number': 444444,
      'address': 'Toronto',
      'image': 'assets/mary.png'
    },
    {
      'name': 'Ezekiel',
      'number': 555555,
      'address': 'Texas',
      'image': 'assets/ezeki.png'
    }
  ];

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          ...contacts
              .map((e) => InkWell(
                    onTap: () {
                    
                      // Final Step
                      // Function callback call
                      callback({
                        'name': e['name'],
                        'number': e['number'],
                        'address': e['address'],
                        'image': e['image'],
                      });

                    },
                    child: CircleAvatar(
                      child: Image.asset(e['image']),
                    ),
                  ))
              .toList()
        ],
      ),
    );
  }
}

 

2. Uso do padrão de método estático:

Primeira etapa: Defina uma variável privada do tipo de dados de sua escolha no widget pai. No exemplo abaixo, usamos o tipo de dados “Map<String, dynamic>”. Além disso, crie um setter para a variável privada.

import 'package:flutter/material.dart';

class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<StatefulWidget> createState() => ParentWidgetState();

}

class ParentWidgetState extends State<ParentWidget> {

  // Primeira etapa
  Map<String, dynamic> _contact = {};
  
  // Primeira etapa
  // setter
  set contact(Map<String, dynamic> value) => setState(() => _contact = value);
  
  }

Segunda etapa: Defina um método estático chamado “of” que aceita um argumento BuildContext e retorna ParentWidgetState.

import 'package:flutter/material.dart';


class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<StatefulWidget> createState() => ParentWidgetState();

  // Segunda etapa
  static ParentWidgetState? of(BuildContext context) =>
      context.findAncestorStateOfType<ParentWidgetState>();
}

Terceira etapa: Chamar ou fazer o layout do widget filho no widget pai.

import 'package:flutter/material.dart';
import 'package:flutter_callbacks/child_widget.dart';

class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<StatefulWidget> createState() => ParentWidgetState();

  // de implementação do método
  static ParentWidgetState? of(BuildContext context) =>
      context.findAncestorStateOfType<ParentWidgetState>();
}

class ParentWidgetState extends State<ParentWidget> {

  Map<String, dynamic> _contact = {};

  // setter
  set contact(Map<String, dynamic> value) => setState(() => _contact = value);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('Flutter Callbacks'),
        ),
        body: Column(
          children: [
            Container(
              height: 300.0,
              margin: const EdgeInsets.all(10.0),
              decoration: const BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(10.0)),
                  gradient: LinearGradient(
                      colors: [Colors.yellow, Colors.green],
                      begin: Alignment.centerLeft,
                      end: Alignment.centerRight,
                      tileMode: TileMode.clamp)),
              child: _contact.isEmpty
                  ? const Center(
                      child: Text(
                      'Select a Contact',
                      style: TextStyle(color: Colors.white, fontSize: 24),
                    ))
                  : Row(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: <Widget>[
                        Padding(
                            padding:
                                const EdgeInsets.only(left: 10.0, right: 10.0),
                            child: CircleAvatar(
                              radius: 35.0,
                              backgroundImage: AssetImage(_contact['image']),
                            )),
                        Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          crossAxisAlignment: CrossAxisAlignment.end,
                          children: [
                            Text(
                              _contact['name'],
                              style: const TextStyle(
                                  fontSize: 24.0,
                                  color: Colors.white70,
                                  fontWeight: FontWeight.bold),
                            ),
                            const SizedBox(
                              height: 2.0,
                            ),
                            Text(
                              _contact['number'].toString(),
                              style: const TextStyle(
                                  fontSize: 20.0, color: Colors.white70),
                            ),
                            const SizedBox(
                              height: 2.0,
                            ),
                            Text(
                              _contact['address'],
                              style: const TextStyle(
                                  fontSize: 20.0, color: Colors.white70),
                            ),
                          ],
                        ),
                      ],
                    ),
            ),
            const SizedBox(
              height: 40,
            ),
            ChildWidget(),
          ],
        ));
  }
}

Etapa final: Chame ou faça referência ao setter da nossa variável privada por meio do método estático “of” no widget pai e defina o novo valor que você deseja enviar.

import 'package:flutter/material.dart';
import 'package:flutter_callbacks/parent_widget.dart';

class ChildWidget extends StatelessWidget {

  ChildWidget({super.key});

  final List<Map<String, dynamic>> contacts = [
    {
      'name': 'Stanley',
      'number': 111111,
      'address': 'Dubai',
      'image': 'assets/stan.png'
    },
    {
      'name': 'Maryan',
      'number': 22222,
      'address': 'London',
      'image': 'assets/maryan.png'
    },
    {
      'name': 'Kingsley',
      'number': 333333,
      'address': 'New York',
      'image': 'assets/kins.png'
    },
    {
      'name': 'Mary',
      'number': 444444,
      'address': 'Toronto',
      'image': 'assets/mary.png'
    },
    {
      'name': 'Ezekiel',
      'number': 555555,
      'address': 'Texas',
      'image': 'assets/ezeki.png'
    }
  ];

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          ...contacts
              .map((e) => InkWell(
                    onTap: () {
                    
                    
                      // Final Step
                      // of Pattern call
                      ParentWidget.of(context)?.contact = {
                        'name': e['name'],
                        'number': e['number'],
                        'address': e['address'],
                        'image': e['image'],
                      };
                    },
                    child: CircleAvatar(
                      child: Image.asset(e['image']),
                    ),
                  ))
              .toList()
        ],
      ),
    );
  }
}

Código-fonte do projeto no Github (link)