admin管理员组

文章数量:1279016

I have a widget 'A' that displays Text(currentPlayerStatus). This widget is called 10 times from another widget 'B', one for each player in the list to display each player's status. Initially, all the players have same status.

In widget 'B', there is a dropdown or a button or a GuestureDecetor that the user can interact with. When user selects one of the player, the corresponding widget, or taps the GuestureDetector, the 'A' for that player should update it's status from "not-set" to "set".

I tried passing globalKey and key.currentState?.setStatus(status) to call the setStatus function in widget A, but the method is never invoked as the key.currentState? always returns null in this case.

How do I set state in a paticular Child widget of same type from a parent widget?

I hope I am clear with the question.

EDIT: Here is dartpad link: /6f016fe76a5ba392ac560cff43475bf3

import 'package:flutter/material.dart';

void main() {
 runApp(const MyApp());
}

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

 @override
 State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     debugShowCheckedModeBanner: false,
     home: Scaffold(body: Center(child: B())),
   );
 }
}

Here is A

// Class A
class A extends StatefulWidget {
  A({super.key, required this.status, required this.player});
  String status;
  Player player;

  @override
  State<A> createState() => _AState(status);
}

class _AState extends State<A> {
  _AState(this.currentStatus);
  String currentStatus;

  void setStatus(String status) {
    currentStatus = status;
  }

  @override
  Widget build(BuildContext context) {
    return Text(currentStatus);
  }
}


Here is B and Player Class

// Player model
class Player {
  String name;
  String id;

  Player({required this.name, required this.id});
}

//Class B
class B extends StatefulWidget {
  @override
  State<B> createState() => _BState();
}

class _BState extends State<B> {
  List<Player> players = [];
  List<String> statuses = [];
  String placeholder = "This One";

  @override
  void initState() {
    players = List.generate(
      10,
      (index) => Player(name: "Player $index", id: index.toString()),
    );
    statuses = List.generate(players.length, (index) => "not-set");
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children:
          players.map((player) {
            return Padding(
              padding: EdgeInsets.all(8),
              child: Row(
                children: [
                  GestureDetector(
                    child: Text(player.name),
                    onTapDown: (onTap) {
                      // Set only this player status to 'set'
                      // The placeholder is only for attention.
                      setState(() {
                        placeholder = "only selected player's status should change";
                      });
                    },
                  ),

                  SizedBox(width: 20),
                  A(player: player, status: statuses[players.indexOf(player)]),
                  SizedBox(width: 20),
                  Text(placeholder),
                ],
              ),
            );
          }).toList(),
    );
  }
}

I have a widget 'A' that displays Text(currentPlayerStatus). This widget is called 10 times from another widget 'B', one for each player in the list to display each player's status. Initially, all the players have same status.

In widget 'B', there is a dropdown or a button or a GuestureDecetor that the user can interact with. When user selects one of the player, the corresponding widget, or taps the GuestureDetector, the 'A' for that player should update it's status from "not-set" to "set".

I tried passing globalKey and key.currentState?.setStatus(status) to call the setStatus function in widget A, but the method is never invoked as the key.currentState? always returns null in this case.

How do I set state in a paticular Child widget of same type from a parent widget?

I hope I am clear with the question.

EDIT: Here is dartpad link: https://dartpad.dev/6f016fe76a5ba392ac560cff43475bf3

import 'package:flutter/material.dart';

void main() {
 runApp(const MyApp());
}

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

 @override
 State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     debugShowCheckedModeBanner: false,
     home: Scaffold(body: Center(child: B())),
   );
 }
}

Here is A

// Class A
class A extends StatefulWidget {
  A({super.key, required this.status, required this.player});
  String status;
  Player player;

  @override
  State<A> createState() => _AState(status);
}

class _AState extends State<A> {
  _AState(this.currentStatus);
  String currentStatus;

  void setStatus(String status) {
    currentStatus = status;
  }

  @override
  Widget build(BuildContext context) {
    return Text(currentStatus);
  }
}


Here is B and Player Class

// Player model
class Player {
  String name;
  String id;

  Player({required this.name, required this.id});
}

//Class B
class B extends StatefulWidget {
  @override
  State<B> createState() => _BState();
}

class _BState extends State<B> {
  List<Player> players = [];
  List<String> statuses = [];
  String placeholder = "This One";

  @override
  void initState() {
    players = List.generate(
      10,
      (index) => Player(name: "Player $index", id: index.toString()),
    );
    statuses = List.generate(players.length, (index) => "not-set");
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children:
          players.map((player) {
            return Padding(
              padding: EdgeInsets.all(8),
              child: Row(
                children: [
                  GestureDetector(
                    child: Text(player.name),
                    onTapDown: (onTap) {
                      // Set only this player status to 'set'
                      // The placeholder is only for attention.
                      setState(() {
                        placeholder = "only selected player's status should change";
                      });
                    },
                  ),

                  SizedBox(width: 20),
                  A(player: player, status: statuses[players.indexOf(player)]),
                  SizedBox(width: 20),
                  Text(placeholder),
                ],
              ),
            );
          }).toList(),
    );
  }
}

Share Improve this question edited Feb 24 at 17:44 Anish Pokharel asked Feb 24 at 3:43 Anish PokharelAnish Pokharel 215 bronze badges 3
  • 1 State Management. I suggest Riverpod. – Randal Schwartz Commented Feb 24 at 3:52
  • With the approach you have, the A(player: player, status: statuses[players.indexOf(player)]), resets every changes you make in the onTap and setState as setState will cause the build method to build again. You may need to search a different approach. – rusty Commented Feb 27 at 6:03
  • I've added another answer using provider package for state management. Please check out the answer. – rusty Commented Feb 28 at 2:26
Add a comment  | 

4 Answers 4

Reset to default 1

The widget 'A' should be StatelessWidget that takes status using a constructor. And then, widget 'B' should be StatefulWidget which holds the status list, and calls setState when user selects a player.

Here's an example:

class A extends StatelessWidget {
  const A({
    super.key,
    required this.status,
  });

  final String status;

  @override
  Widget build(BuildContext context) {
    return Text(status);
  }
}

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

  @override
  State<B> createState() => _BState();
}

class _BState extends State<B> {
  final List<String> statuses = [
    'unselected',
    'unselected',
    'unselected',
    'unselected',
  ];

  void _onSelected(int index) {
    setState(() {
      statuses[index] = 'selected';
    });
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: statuses.length,
      itemBuilder: (context, index) {
        return A(status: statuses[index]);
      },
    );
  }
}

I am not exactly sure of your requirements. Here, is my attempt to solving your problem. Hope it helps.

Full code:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: const B());
  }
}

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

  @override
  State<B> createState() => _BState();
}

class _BState extends State<B> {
  Map<String, String> playerSelectedStatus = {
    "Player01": "notSelected",
    "Player02": "notSelected",
    "Player03": "notSelected",
    "Player04": "notSelected",
    "Player05": "notSelected",
  };
  late String? myDropdownValue = playerSelectedStatus.keys.first;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Example"),
        backgroundColor: Colors.blue,
      ),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(20),
        child: Column(
          children: [
            DropdownMenu<String>(
              initialSelection: myDropdownValue,
              onSelected: (String? value) {
                setState(() {
                  playerSelectedStatus[value!] = "selected";
                });
              },
              dropdownMenuEntries:
                  playerSelectedStatus.keys
                      .toList()
                      .map<DropdownMenuEntry<String>>((String tmpStr) {
                        return DropdownMenuEntry(value: tmpStr, label: tmpStr);
                      })
                      .toList(),
            ),
            A(myMap: playerSelectedStatus),
          ],
        ),
      ),
    );
  }
}

class A extends StatelessWidget {
  final Map<String, String> myMap;
  const A({super.key, required this.myMap});

  @override
  Widget build(BuildContext context) {
    final entries = myMap.entries.toList();

    return Container(
      height: 150,
      decoration: BoxDecoration(color: Colors.blue.shade100),
      child: Column(
        children: [
          Expanded(
            child: ListView.builder(
              shrinkWrap: true,
              itemCount: entries.length,
              itemBuilder: (context, index) {
                final entry = entries[index];
                return Text("${entry.key} is ${entry.value}");
              },
            ),
          ),
        ],
      ),
    );
  }
}

globalKey.currentState?.setStatus() was coming out null and I was unable to use it to update the child because I was doing it wrong. The key that I initially used returned currentState to be null because I was passing it a wrong way. Here is how I fixed it and got the desired outcome.

https://dartpad.dev/26b9afdbddac1cd18992412bacf77a6e

I was also trying to solve your problem, and I've come up with the following using provider package to do the state management. Please see if it helps.

lib/main.dart:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:set_state_of_a_child_widget_from_parent/class_B.dart';
import 'package:set_state_of_a_child_widget_from_parent/my_custom_provider.dart';

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider<MyCustomProvider>(
          create: (context) => MyCustomProvider(),
        ),
      ],
      child: const MyApp(),
    ),
  );
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(body: Center(child: B())),
    );
  }
}

lib/class_player.dart:

// Player model
class Player {
  String name;
  String id;

  Player({required this.name, required this.id});
}

lib/class_B.dart:

//Class B
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:set_state_of_a_child_widget_from_parent/class_A.dart';
import 'package:set_state_of_a_child_widget_from_parent/class_player.dart';
import 'package:set_state_of_a_child_widget_from_parent/my_custom_provider.dart';

class B extends StatefulWidget {
  @override
  State<B> createState() => _BState();
}

class _BState extends State<B> {
  List<Player> players = [];
  List<String> statuses = [];
  String placeholder = "This One";
  MyCustomProvider? myCustomProviderObj;

  @override
  void initState() {
    super.initState();
    players = List.generate(
      10,
      (index) => Player(name: "Player $index", id: index.toString()),
    );
    statuses = List.generate(players.length, (index) => "not-set");

    myCustomProviderObj = Provider.of<MyCustomProvider>(context, listen: false);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children:
          players.map((player) {
            return Padding(
              padding: EdgeInsets.all(8),
              child: Row(
                children: [
                  GestureDetector(
                    child: Text(player.name),
                    onTapDown: (onTap) {
                      myCustomProviderObj!.updatePlayerStatus(player);
                    },
                  ),

                  SizedBox(width: 20),
                  Consumer<MyCustomProvider>(
                    builder: (context, myProvider, child) {
                      if (myProvider.updatedPlayers.contains(player)) {
                        return A(player: player, status: "selected");
                      } else {
                        return A(player: player, status: "not-set");
                      }
                    },
                  ),
                  SizedBox(width: 20),
                  Text(placeholder),
                ],
              ),
            );
          }).toList(),
    );
  }
}

lib/class_A.dart:

// Class A
import 'package:flutter/material.dart';
import 'package:set_state_of_a_child_widget_from_parent/class_player.dart';

class A extends StatefulWidget {
  const A({super.key, required this.status, required this.player});

  final String status;
  final Player player;

  @override
  State<A> createState() => _AState();
}

class _AState extends State<A> {
  @override
  Widget build(BuildContext context) {
    return Text(widget.status);
  }
}

lib/my_custom_provider.dart:

import 'package:flutter/widgets.dart';
import 'package:set_state_of_a_child_widget_from_parent/class_player.dart';

class MyCustomProvider with ChangeNotifier {
  List<Player> updatedPlayers = [];

  void updatePlayerStatus(Player player) {
    updatedPlayers.add(player);
    notifyListeners();
  }
}

本文标签: flutterHow to set state of a child widget from the parent widgetStack Overflow