You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Alert内DropdownButton点击选项后状态不更新问题排查

问题描述

我在开发Flutter应用时,需要在Alert对话框中使用DropdownButton。当前组件是StatefulWidget,点击DropdownItem后已经调用了setState,但按钮显示始终没有变化,无法同步更新选中的选项。

问题原因

Dialog通过showDialog创建后,会生成独立的Widget树,拥有自己的上下文。外层MealItem的setState只能触发自身组件的重建,无法影响Dialog内部的Widget状态,因此DropdownButton的显示不会跟随选中值更新。

解决方案

使用StatefulBuilder包裹Dialog的内容部分,它可以在Dialog内部提供局部的状态更新能力,同时能感知外层状态的变化,确保DropdownButton的显示与选中值同步。

修正后的代码

核心修改是用StatefulBuilder包裹Dialog主体,确保状态变化能触发Dialog内部重建:

// ignore_for_file: use_key_in_widget_constructors, avoid_print, unrelated_type_equality_checks

import 'dart:convert';
import 'package:http/http.dart' as http;

import 'package:apetit_project/models/app_user.dart';
import 'package:apetit_project/models/reservation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';

import '../models/meal.dart';
import '../utils/app_routes.dart';

class MealItem extends StatefulWidget {
  final Meal meal;

  MealItem(
    this.meal,
  );

  @override
  State<MealItem> createState() => _MealItemState();
}

class _MealItemState extends State<MealItem> {
  final List<Reservation> _items = [];
  String dropDownValue = '11:30';

  List<Reservation> get items => [..._items];

  final AppUser? appUser = const AppUser(
    id: 'PedroNovoTeste',
    email: 'email',
    company: 'GTFoods',
  );

  void addReservation(Reservation reservation) {
    final future = http.post(
      Uri.parse(
          'http://172.16.30.120:8080/ords/apiteste/integrafoods/cadastra-empresa'),
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode<String, String>({
        "cnpj": reservation.user,
        "razao": reservation.company,
        "fantasia": reservation.opcao,
      }),
    );

    future.then((response) {
      _items.add(reservation);
    });
  }

  void _newReservation() {
    final newReservation = Reservation(
        user: appUser!.id,
        date: DateTime.now().add(const Duration(days: 2)),
        opcao: widget.meal.id,
        company: appUser!.company,
        resTime: dropDownValue);

    addReservation(newReservation);

    print(newReservation.user);
    print(newReservation.date);
    print(newReservation.opcao);
    print(newReservation.company);
    print(newReservation.isReserved);
    print(newReservation.resTime);
  }

  void _selectMeal(BuildContext context) {
    Navigator.of(context)
        .pushNamed(
      AppRoutes.MEAL_DETAIL,
      arguments: widget.meal,
    )
        .then((result) {
      if (result == null) {
        print('Sem resultado!');
      } else {
        print('O nome da refeição é $result.');
      }
    });
  }

  List<DropdownMenuItem<String>> get dropdownItems {
    List<DropdownMenuItem<String>> menuItems = [
      const DropdownMenuItem(value: "11:30", child: Text("11:30")),
      const DropdownMenuItem(value: "12:00", child: Text("12:00")),
      const DropdownMenuItem(value: "12:30", child: Text("12:30")),
      const DropdownMenuItem(value: "13:00", child: Text("13:00")),
      const DropdownMenuItem(value: "13:30", child: Text("13:30")),
    ];
    return menuItems;
  }

  @override
  Widget build(BuildContext context) {
    final reservation = Provider.of<Reservation>(context);
    final date = DateTime.now().add(const Duration(days: 2));
    String formattedDate = DateFormat('dd/MM/yyyy').format(date);

    return InkWell(
      onTap: (widget.meal.descricao == 'Reservar')
          ? () {
              showDialog(
                context: context,
                builder: (context) {
                  // 用StatefulBuilder包裹Dialog内容
                  return StatefulBuilder(
                    builder: (dialogContext, setStateDialog) {
                      return Dialog(
                        shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(30)),
                        child: DecoratedBox(
                          decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(30),
                            gradient: LinearGradient(
                              colors: [
                                Colors.white.withOpacity(0.2),
                                Colors.grey.withOpacity(0.2),
                                Colors.black.withOpacity(0.2),
                              ],
                              begin: Alignment.topCenter,
                              end: Alignment.bottomCenter,
                            ),
                          ),
                          child: Column(
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              SingleChildScrollView(
                                child: ListBody(
                                  children: <Widget>[
                                    ListTile(
                                      title: Padding(
                                        padding: const EdgeInsets.only(top: 15),
                                        child: Image.asset(
                                          'assets/images/logo.png',
                                          fit: BoxFit.contain,
                                          height: 80,
                                        ),
                                      ),
                                    ),
                                    Column(
                                      children: [
                                        Text(
                                          (reservation.isReserved == false)
                                              ? 'Confirmar reserva para o dia $formattedDate no restaurante administrativo da empresa GTFoods(Matriz) para o horário'
                                              : 'Você já possui uma reserva para o dia $formattedDate no restaurante administrativo da empresa GTFoods(Matriz), deseja cancelar sua reserva?',
                                          textAlign: TextAlign.center,
                                          style: const TextStyle(
                                              fontSize: 15,
                                              fontFamily: 'Raleway',
                                              fontWeight: FontWeight.bold),
                                        ),
                                        Row(
                                          mainAxisAlignment:
                                              MainAxisAlignment.center,
                                          children: [
                                            DropdownButton<String>(
                                              value: dropDownValue,
                                              items: <String>[
                                                '11:30',
                                                '12:00',
                                                '12:30',
                                                '13:00',
                                                '13:30',
                                              ].map<DropdownMenuItem<String>>(
                                                  (String value) {
                                                return DropdownMenuItem<String>(
                                                  value: value,
                                                  child: Text(value),
                                                );
                                              }).toList(),
                                              onChanged: (String? newValue) {
                                                // 更新外层状态,同时触发Dialog内部重建
                                                setState(() {
                                                  dropDownValue = newValue!;
                                                });
                                              },
                                            ),
                                            const Text('?')
                                          ],
                                        ),
                                      ],
                                    ),
                                    const SizedBox(
                                      height: 10,
                                    ),
                                    Row(
                                      mainAxisAlignment:
                                          MainAxisAlignment.spaceEvenly,
                                      children: [
                                        if (reservation.isReserved == false)
                                          ElevatedButton(
                                            child: const Text('Sim'),
                                            onPressed: () {
                                              _newReservation();
                                              reservation.toggleReserved();
                                              Navigator.pop(context);
                                            },
                                          ),
                                        if (reservation.isReserved == true)
                                          ElevatedButton(
                                            child: const Text('Sim'),
                                            onPressed: () {
                                              print(reservation.isReserved);
                                              reservation.toggleReserved();
                                              Navigator.pop(context);
                                            },
                                          ),
                                        ElevatedButton(
                                          child: const Text('Não'),
                                          onPressed: () => Navigator.pop(context),
                                        ),
                                      ],
                                    ),
                                    ListTile(
                                      title: Image.asset(
                                        'assets/images/logo_gttech.png',
                                        fit: BoxFit.contain,
                                        height: 40,
                                      ),
                                    ),
                                  ],
                                ),
                              ),
                            ],
                          ),
                        ),
                      );
                    },
                  );
                },
              );
            }
          : () => _selectMeal(context),
      child: Card(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(15),
        ),
        elevation: 0,
        margin: const EdgeInsets.only(right: 0, left: 0),
        child: Column(
          children: [
            Stack(
              children: [
                ClipRRect(
                  child: Image.asset(
                    widget.meal.imageUrl,
                    height: (widget.meal.descricao == 'Reservar') ? 50 : 73,
                    width: double.infinity,
                    fit: BoxFit.cover,
                  ),
                ),
                Container(
                  height: (widget.meal.descricao == 'Reservar') ? 0 : 73,
                  decoration: BoxDecoration(
                    gradient: LinearGradient(
                      begin: FractionalOffset.centerRight,
                      end: FractionalOffset.centerLeft,
                      colors: [
                        Colors.grey.withOpacity(0.0),
                        Colors.black.withOpacity(0.9),
                      ],
                      stops: const [0.0, 1.0],
                    ),
                  ),
                ),
                Positioned(
                  bottom: (widget.meal.descricao == 'Reservar')
                      ? 7
                      : (widget.meal.descricao == 'Feijoada')
                          ? 15
                          : (widget.meal.descricao == 'X Salada')
                              ? 15
                              : 0,
                  left: (widget.meal.descricao == 'Reservar') ? 95 : 10,
                  child: Container(
                    width: (widget.meal.descricao.length > 22)
                        ? 300
                        : (widget.meal.descricao.length > 20)
                            ? 210
                            : (widget.meal.descricao == 'Reservar')
                                ? 210
                                : (widget.meal.descricao == 'Bife Americano')
                                    ? 150
                                    : (widget.meal.descricao ==
                                            'Feijão Tropeiro')
                                        ? 150
                                        : (widget.meal.descricao.length > 14)
                                            ? 200
                                            : 150,
                    padding: const EdgeInsets.symmetric(
                      vertical: 5,
                      horizontal: 15,
                    ),
                    child: Text(
                      widget.meal.descricao,
                      style: const TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 24,
                        color: Colors.white,
                      ),
                      softWrap: true,
                      overflow: TextOverflow.fade,
                    ),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
额外说明
  • 因为dropDownValue需要在Dialog外的_newReservation方法中使用,所以状态仍保留在_MealItemState中,通过外层setState更新后,StatefulBuilder会感知到值的变化并重建Dialog内部组件。
  • 如果Dialog需要管理独立的内部状态,可以将变量放在StatefulBuilder的builder函数内部,但此场景下无需这么做。

内容的提问来源于stack exchange,提问作者pedro.curti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 18:36:41