2

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(),
    );
  }
}

3
  • 1
    State Management. I suggest Riverpod. 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. Commented Feb 27 at 6:03
  • I've added another answer using provider package for state management. Please check out the answer. Commented Feb 28 at 2:26

4 Answers 4

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]);
      },
    );
  }
}

Sign up to request clarification or add additional context in comments.

2 Comments

In this instance, both of the classes are stateful widgets.
@AnishPokharel If the widget A should be a stateful widget for some reason, it can still take status using a constructor. status should not be a state of widget A.
1

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}");
              },
            ),
          ),
        ],
      ),
    );
  }
}

1 Comment

I have edited the original post and added a little bit of code for context.
0

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

Comments

0

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();
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.