Flutter: setState (() {} не может перерисовать значение DropdownButton

Я новичок в флаттере и пытаюсь написать код для мобильной игры. До сих пор мне удавалось ответить на предыдущие вопросы на форумах, в руководствах по YouTube и в примерах приложений. Однако я наткнулся на стену, которую не могу решить.

Многие функции в моем пользовательском интерфейсе должны изменяться в зависимости от поведения пользователя, но не обновляются. Я использую этот DropdownButton в качестве примера, но это проблема, с которой я столкнулся и в другом месте кода. Когда пользователь делает выбор из DropdownButton, он должен обновить значение кнопки, но вместо этого отображается только подсказка. Я смог определить с помощью операторов печати, что выбор зарегистрирован.

На основании того, что я узнал до сих пор, я подозреваю, что проблема связана с сохранением состояния моего виджета или его отсутствием. Я нашел несколько похожих вопросов, в которых предлагалось использовать StatefulBuilder, однако, когда я попытался реализовать его, в коде были ошибки или дублировалась вся моя группа ListTile. Я также видел предложения о создании виджета с отслеживанием состояния, но инструкции были слишком расплывчаты, чтобы я мог следовать им и реализовывать без ошибок. Я включаю фрагменты кода ниже, я не решаюсь вставить все это целиком, потому что на данный момент приложение содержит почти 2000 строк. Пожалуйста, дайте мне знать, если потребуется дополнительная информация или код. это проблемный код, см. ниже код в контексте.

        DropdownButton(
          value: skillChoice,
          items: listDrop,
          hint: Text("Choose Skill"),
          onChanged: (value) {
            setState(() {
              skillChoice = value;
            });
          },
        ),
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hunters Guild',
      theme: ThemeData(
        primaryColor: Colors.grey,
      ),
      home: Main(),
    );
  }
}
final List<Combatant> _combatants = List<Combatant>(); 
//combatant is a class that holds stats like HP which may change during a battle
//they also have a Fighter and a Monster and one of those will be null and the other useful
//depending if faction = "good" or "evil
//I am aware that this may not be the most elegant implementation but that is a problem for another day.

class MainState extends State<Main> {
  final List<Fighter> _squad = List<Fighter>(); 
//Fighter is a class which stores info like HP, name, skills, etc for game character,
//this List is populated in the page previous to _fight 
//where the user picks which characters they are using in a given battle
void _fight(Quest theQuest){
    for(Fighter guy in _squad){
      _combatants.add(Combatant("good", guy, null));
    }
    _combatants.add(Combatant("evil", null, theQuest.boss)); 
//quests are a class which store a boss Monster, later on I will add other things like rewards and prerequisites
final tiles = _combatants.map((Combatant comba){ //this structure is from the build your first app codelab
      List<DropdownMenuItem<String>> listDrop = [];
      String skillChoice = null;
      if (comba.faction == "good"){
        for (Skill skill in comba.guy.skills){
          listDrop.add((DropdownMenuItem(child: Text(skill.name), value: skill.name)));
        }
      }
      else if (comba.faction == "evil"){
        for (Skill skill in comba.boss.skills){
          listDrop.add((DropdownMenuItem(child: Text(skill.name), value: skill.name)));
        }
      }
//^this code populates each ListTile (one for each combatant) with a drop down button of all their combat skills
      return ListTile(

        leading: comba.port,
        title: Text(comba.info),
        onTap: () {
          if(comba.faction == "good"){
            _fighterFightInfo(comba.guy, theQuest.boss); //separate page with more detailed info, not finished
          }
          else{
            _monsterFightInfo(theQuest.boss); //same but for monsters
          }
        },
        trailing: Row(
          mainAxisSize: MainAxisSize.min,
          children: [

            DropdownButton( //HERE IS WHERE THE ERROR LIES
              value: skillChoice,
              items: listDrop,
              hint: Text("Choose Skill"),
              onChanged: (value) {
                setState(() {
                  skillChoice = value;
                });
              },
            ),
            IconButton(icon: Icon(Icons.check), onPressed: (){
//eventually this button will be used to execute the user's skill choice
            })
          ],
        ),
        subtitle: Text(comba.hp.toString()),
      );
    },
    );
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (BuildContext context) {
//I have tried moving most of the above code into here, 
//and also implementing StatelessBuilders here and other places in the below code to no effect
          final divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();
          return Scaffold(
            appBar: AppBar(
              title: Text("Slay "+theQuest.boss.name+"  "+theQuest.boss.level.toString()+"  "+theQuest.boss.type.name, style: TextStyle(fontSize: 14),),
              leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: () {
                _squadSelect(theQuest);
              }),
            ),
            body: ListView(children: divided),
          );
        },
      ),
    );
  }
//there are a lot more methods here which I haven't included
 @override

  Widget build(BuildContext context) {
//I can show this if you need it but there's a lot of UI building on the main page
//Also might not be the most efficiently implemented
//what you need to know is that there are buttons which call _fight and go to that page
}
class Main extends StatefulWidget {
  @override
  MainState createState() => MainState();
}

Спасибо за любую помощь или совет, которые вы можете предложить.


person Ryan Bowman    schedule 29.05.2020    source источник
comment
Код немного сложен для понимания, поэтому я предлагаю вам извлечь фрагменты кода, вызывающие проблему, и показать их в новом виджете, чтобы облегчить чтение.   -  person Mohammad Assad Arshad    schedule 29.05.2020
comment
Отвечает ли это на ваш вопрос? удалить элемент из списка с помощью дрожания   -  person desertnaut    schedule 30.05.2020


Ответы (2)


Немного сложно понять, что здесь происходит, поэтому, если возможно, добавьте проблемные области в новый виджет и разместите его здесь.

В любом случае, сейчас кажется, что ваша переменная skillShare является локальной, поэтому каждый раз, когда вы устанавливаете состояние, она не обновляется. Попробуйте что-то вроде этого:

class MainState extends State<Main> {
String skillShare = ""; //i.e make skillShare a global variable
final List<Fighter> _squad = List<Fighter>(); 
person Mohammad Assad Arshad    schedule 29.05.2020
comment
У меня нет переменной skillShare, вы имеете в виду skillChoice? прямо сейчас он определен в цикле final tiles = _combatants.map((Combatant comba){. каждый ListTile имеет свой собственный список навыков, составленный бойцом, к которому он относится. for (Skill skill in comba.guy.skills){ listDrop.add((DropdownMenuItem(child: Text(skill.name), value: comba.guy.skills.indexOf(skill)))); } - person Ryan Bowman; 29.05.2020

В следующем вопросе была информация, которая помогла мне найти ответ. Другой вопрос, но решение применимо. Спасибо пользователю: 4576996 Sven. В основном мне нужно переформатировать мои построения в void _fight на новую страницу с отслеживанием состояния. Это может быть полезно для всех, кто использует начальное руководство в качестве основы для создания своего приложения.

удалить элемент из списка с помощью дрожания

person Ryan Bowman    schedule 31.05.2020