Como enviar ou retornar dados de chamada do widget filho para o widget pai no Flutter
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)