Flutter中的DropDownButton未显示所选值

移动开发 2026-07-08

我现在在学习Flutter,我已经用 DropDownButton 小部件加载了它的项;不过,尽管按照 这个示例 以及其他类似的回答操作,但每次在 DropDownButton 小部件上选择一个条目时,这个控件/小部件不会显示所选的值,只有在关闭当前打开的ListView时,才能看到已选择/已更改的值。

下面是示例数据:

List<Map<String, dynamic>> tipos_producto = [
  {
    "ID_TIPO_PRODUCTO": 1,
    "NOMBRE_TIPO_PRODUCTO": "Gaseosas",
    "DESCRIPCION_TIPO_PRODUCTO": "Bebidas gaseosas",
    "ID_TIPO_UNIDAD": 2,
  },
  {
    "ID_TIPO_PRODUCTO": 2,
    "NOMBRE_TIPO_PRODUCTO": "Aseo del hogar",
    "DESCRIPCION_TIPO_PRODUCTO": "Todo tipo de productos de aseo.",
    "ID_TIPO_UNIDAD": 1,
  },
  {
    "ID_TIPO_PRODUCTO": 4,
    "NOMBRE_TIPO_PRODUCTO": "Snacks",
    "DESCRIPCION_TIPO_PRODUCTO": "Paquetes de papas, entre otros.",
    "ID_TIPO_UNIDAD": 1,
  },[...]
];

这是该小部件的代码:

import 'package:flutter/material.dart';
import 'package:proyecto_minimarket_apk/tipos_producto.dart';

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

  @override
  State<opciones> createState() => _opcionesState();
}

class _opcionesState extends State<opciones> {
  String ddlVlr_tipoProducto = tipos_producto.elementAt(0)["ID_TIPO_PRODUCTO"].toString();

  @override
  Widget build(BuildContext context) {
    // Relevant part: DropDownList added with options: 
    body: ListView(
    children: [
      DropdownButton(
        hint: Text("Tipo de producto"),
        icon: Icon(Icons.arrow_drop_down),
        items: tipos_producto.map((map) {
          return DropdownMenuItem<String>(
            value: map["ID_TIPO_PRODUCTO"].toString(),
            child: Text(map["NOMBRE_TIPO_PRODUCTO"],),
          );
        }).toList(),
        value: ddlVlr_tipoProducto,
        onChanged: (String? value) {
          setState(() {
            ddlVlr_tipoProducto = value!;
          });
        },
      ),
      // [...]
  }
}

似乎缺少了什么,但我真的不明白到底缺少了什么——因为调试控制台没有报错,我也查看了 文档,但我无法重现文档中所展示的行为——也就是:如果你选择了一个条目,小部件会显示所选的项(不需要刷新 ListView)。

有人能给我指个正确的方向吗?

编辑:在DartPad上添加了可运行的演示:

复现步骤:

  1. 运行上面链接的 DartPad Gist可运行示例
  2. 选择“Productos”选项。
  3. 点击屏幕右下角的 ElevatedButton 按钮。
  4. 前两个字段是 DropdownButtons。在它们的 DropdownButtons 中任意选择一个值。
  5. 例如,在第一个控件中,默认选择第二个选项 "Aseo del Hogar",所选的元素是 "Gaseosas";但在你再选择另一个值并关闭当前的ListView后,可以再次点击屏幕右下角的 ElevatedButton 按钮,你会看到所选的元素是 "Aseo del Hogar"。

解决方案

之所以没有更新,是因为对话框/底部弹出层的状态与当前(单一)小部件的状态是分离的;所以调用setState不会刷新UI; 有几点需要注意

  • 试着对类进行适配,而不是map
  • 当变得混乱时,尽量将小部件与类分离

在代码中添加了注释,你可以阅读以了解我的意图;尽管那并非最佳实践,但在我专注于仅更新UI的阶段,尽量通过最少修改来解决问题;

这不是最佳实践,只是为了最小的改动来更新UI

import 'dart:math' as math;
import 'package:flutter/material.dart';

const Color primaryColor = Colors.orange;
const TargetPlatform platform = TargetPlatform.android;

/// NONE should be global 
List<Map<String, dynamic>> tipos_producto = [
  {
    "ID_TIPO_PRODUCTO": 1,
    "NOMBRE_TIPO_PRODUCTO": "Gaseosas",
    "DESCRIPCION_TIPO_PRODUCTO": "Bebidas gaseosas",
    "ID_TIPO_UNIDAD": 2,
  },
  {
    "ID_TIPO_PRODUCTO": 2,
    "NOMBRE_TIPO_PRODUCTO": "Aseo del hogar",
    "DESCRIPCION_TIPO_PRODUCTO": "Todo tipo de productos de aseo.",
    "ID_TIPO_UNIDAD": 1,
  },
  {
    "ID_TIPO_PRODUCTO": 3,
    "NOMBRE_TIPO_PRODUCTO": "Snacks",
    "DESCRIPCION_TIPO_PRODUCTO": "Paquetes de papas, entre otros.",
    "ID_TIPO_UNIDAD": 1,
  },
];

/// NOPE: GLobal items on setState doesnt work unless you refresh  the state of that specific widgets
List<Map<String, dynamic>> productos = [
  {
    "ID_PRODUCTO": 1,
    "ID_TIPO_PRODUCTO": 1,
    "ID_MARCA": 4,
    "NOMBRE_PRODUCTO": "Postobón gaseosa",
    "DESCRIPCION_PRODUCTO": "Postobón 2 litros - sabor manzana.",
    "VALOR": 3000,
  },
  {
    "ID_PRODUCTO": 2,
    "ID_TIPO_PRODUCTO": 2,
    "ID_MARCA": 7,
    "NOMBRE_PRODUCTO": "Esponja de brillo",
    "DESCRIPCION_PRODUCTO": "Esponja de brillo - bombril - promoción 2x1",
    "VALOR": 400,
  },
  {
    "ID_PRODUCTO": 3,
    "ID_TIPO_PRODUCTO": 1,
    "ID_MARCA": 2,
    "NOMBRE_PRODUCTO": "Pepsi",
    "DESCRIPCION_PRODUCTO": "Gaseosa Pepsi - 1 litro.",
    "VALOR": 2700,
  },
  {
    "ID_PRODUCTO": 4,
    "ID_TIPO_PRODUCTO": 2,
    "ID_MARCA": 4,
    "NOMBRE_PRODUCTO": "Shampoo Head & Shoulders - 200mg",
    "DESCRIPCION_PRODUCTO": "Shampoo Head & Shoulders - 200 mg. Promoción 2x1",
    "VALOR": 17000,
  },
];

List<Map<String, dynamic>> marcas = [
  {"ID_MARCA": 1, "NOMBRE_MARCA": "Colgate"},
  {"ID_MARCA": 2, "NOMBRE_MARCA": "Guadalupe"},
  {"ID_MARCA": 3, "NOMBRE_MARCA": "Aromatel"},
  {"ID_MARCA": 4, "NOMBRE_MARCA": "Head & Shoulders"},
  {"ID_MARCA": 5, "NOMBRE_MARCA": "Noel"},
  {"ID_MARCA": 6, "NOMBRE_MARCA": "Bombril"},
  {"ID_MARCA": 7, "NOMBRE_MARCA": "Doritos"},
  {"ID_MARCA": 8, "NOMBRE_MARCA": "Advil"},
];

void main() {
  //runApp(Sunflower());
  runApp(MaterialApp(home: Sunflower()));
}

class Sunflower extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _SunflowerState();
  }
}

class _SunflowerState extends State<Sunflower> {
  String ddlVlr_tipoProducto = tipos_producto
      .elementAt(0)["ID_TIPO_PRODUCTO"]
      .toString();
  String ddlVlr_marca = marcas.elementAt(0)["ID_MARCA"].toString();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Card(
            elevation: 6,
            child: ListTile(
              leading: Icon(Icons.add_shopping_cart),
              title: Text("Productos"),
              trailing: Icon(Icons.arrow_forward_ios),
              onTap: () {
                showModalBottomSheet(
                  isScrollControlled: true,
                  context: context,
                  builder: (context) {
                    return StatefulBuilder(
                      builder: (context, setStateSB) => Scaffold(
                        appBar: AppBar(
                          title: Text('Listado de productos'),
                          backgroundColor: Color.fromRGBO(0, 120, 215, 1),
                          foregroundColor: Colors.white,
                        ),
                        body: ListView.builder(
                          itemCount: productos.length,
                          itemBuilder: (BuildContext context, int index) {
                            return Card(
                              child: ListTile(
                                leading: Text(
                                  productos[index]['ID_PRODUCTO'].toString(),
                                ),
                                title: Text(
                                  productos[index]['NOMBRE_PRODUCTO'],
                                ),
                                subtitle: Text(
                                  tipos_producto[productos[index]['ID_TIPO_PRODUCTO']]['NOMBRE_TIPO_PRODUCTO'],
                                ),
                                trailing: Text(
                                  productos[index]['VALOR'].toString(),
                                ),
                              ),
                            );
                          },
                        ),
                        floatingActionButton: FloatingActionButton(
                          backgroundColor: Color.fromRGBO(0, 120, 215, 1),
                          child: Icon(Icons.add_box_outlined),
                          onPressed: () async {
                            await showModalBottomSheet(
                              context: context,
                              builder: (context) {
                                return NewItemCreateView(); // we moved for ....
                              },
                            );

                            /// so  we wait ;and refresh the ui
                            // notice I haved used setStateSB inside StatefulBuilder
                            setStateSB(() {});
                          },
                        ),
                      ),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

/// Too much  for callback, using class
/// Also  try using it for the map  as well if fits,else new class;;;
/// OK u are using GLobal List<Map> I am skipping using it; but you should use it
class Product {
  Product({
    required this.category,
    required this.subCategory,
    required this.name,
    required this.description,
    required this.valor,
  });

  // for dropdown
  final String category;
  final String subCategory;

  final String name;
  final String description;
  final String valor; //idk what it is

  ///yap cloning with ,,you can search online
  Product copyWith(
    String? category,
    String? subCategory,
    String? name,
    String? description,
    String? valor,
  ) {
    return Product(
      category: category ?? this.category,
      subCategory: subCategory ?? this.subCategory,
      name: name ?? this.name,
      description: description ?? this.description,
      valor: valor ?? this.valor,
    );
  }
}

/// you can always pass through  constructor
class NewItemCreateView extends StatefulWidget {
  const NewItemCreateView({
    super.key,
    this.ddlVlr_tipoProducto, // btw we prefer using snakeCase; keeping same
    this.ddlVlr_marca,
  });

  /// just for default initial value
  final String? ddlVlr_tipoProducto;
  final String? ddlVlr_marca;

  @override
  State<NewItemCreateView> createState() => _NewItemCreateViewState();
}

class _NewItemCreateViewState extends State<NewItemCreateView> {
  late String? ddlVlr_tipoProducto =
      widget.ddlVlr_tipoProducto; // get from  parent widget
  late String? ddlVlr_marca = widget
      .ddlVlr_marca; // late is good, but explore initState and didUpdateWidget methods

  /// what ever require for form put here;
  final TextEditingController nombre_prod_ctrl = TextEditingController(
    text: "ok giving default",
  );
  final TextEditingController descripcion_prod_ctrl = TextEditingController(
    text: "ok giving default",
  );
  final TextEditingController valor_prod_ctrl = TextEditingController(
    text: "ok giving default",
  );

  /// hope this is save(add item)
  void _agregarProducto() {
    final String nombre = nombre_prod_ctrl.text;
    final String descr = descripcion_prod_ctrl.text;
    final String valor = valor_prod_ctrl.text;

    if (nombre.isNotEmpty && descr.isNotEmpty && valor.isNotEmpty) {
      final nuevo_producto = {
        "ID_PRODUCTO": productos.length + 1,
        "ID_TIPO_PRODUCTO": // TOOO RISKY....try model class instead
            int.tryParse(ddlVlr_tipoProducto ?? "") ??
            0 - 1, // use tryParse instead of try
        "ID_MARCA": // bad idea using `!` but I would say validate it first
            int.parse(ddlVlr_marca ?? "0") - 1,
        "NOMBRE_PRODUCTO": nombre,
        "DESCRIPCION_PRODUCTO": descr,
        "VALOR": valor,
      };

      /// ney.... ok for now just telling parent widget to refresh as well when  it pop
      setState(() {
        productos.add(nuevo_producto);
      });

      // Reiniciar variables:
      nombre_prod_ctrl.clear();
      descripcion_prod_ctrl.clear();
      valor_prod_ctrl.clear();
      ddlVlr_tipoProducto = tipos_producto
          .elementAt(0)["ID_TIPO_PRODUCTO"]
          .toString();
      ddlVlr_marca = marcas.elementAt(0)["ID_MARCA"].toString();

      Navigator.pop(context); // ypu can pop item too
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // you can skip Scaffold and just using column(AppBar+ Expanded(ListView) ) also  do  the job
      appBar: AppBar(
        backgroundColor: Color.fromRGBO(0, 120, 215, 1),
        titleTextStyle: TextStyle(color: Colors.white, fontSize: 20),
        title: Text("Nuevo producto"),
      ),
      body: ListView(
        children: [
          DropdownButton(
            hint: Text("Tipo de producto"),
            icon: Icon(Icons.arrow_drop_down),
            items: tipos_producto.map((map) {
              return DropdownMenuItem<String>(
                value: map["ID_TIPO_PRODUCTO"].toString(),
                child: Text(map["NOMBRE_TIPO_PRODUCTO"]),
              );
            }).toList(),
            value: ddlVlr_tipoProducto,
            onChanged: (String? value) {
              setState(() {
                ddlVlr_tipoProducto = value!;
              });
            },
          ),
          DropdownButton(
            hint: Text("Marca"),
            icon: Icon(Icons.arrow_drop_down),
            value: ddlVlr_marca,
            onChanged: (String? value) {
              setState(() {
                ddlVlr_marca = value!;
              });
            },
            items: marcas.map((map) {
              return DropdownMenuItem<String>(
                value: map["ID_MARCA"].toString(),
                child: Text(map["NOMBRE_MARCA"]),
              );
            }).toList(),
          ),

          /// ... put those textFiled here
          ElevatedButton.icon(
            label: Text("Guardar"),
            icon: Icon(Icons.save),
            onPressed: _agregarProducto,
          ),
        ],
      ),
    );
  }
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章