Convert Map to String to fill Dropdown in Flutter

Asked

Viewed 1,131 times

1

I’m having a hard time filling one dropdown. Using data from a query sqflite I have already made the following codes:

A Future map that returns my database categories in the following format:

[ 
   { 
      "_categoriaid":1,
      "categorianome":"Investimentos"
   },
   { 
      "_categoriaid":2,
      "categorianome":"Salário"
   }
]

Code:

  Future<Map> _categorias() async {
    var db = await DatabaseHelper.instance.database;
    List respostaCategoria = await db
        .rawQuery('SELECT (_categoriaid),(categorianome) FROM categoria');
    print(respostaCategoria);
    return respostaCategoria[0];
  }

For display I did this FutureBuiler but the difficulty is time to fill the data in the dropdown.

From what I understand I need to convert this Map returned from Future to a string and fill in the dropdown but I cannot do this conversion

FutureBuilder<Map>(
                    future: _categorias(),
                    builder: (context, snapshot) {
                      final state = snapshot.connectionState;
                      var resultado;

                      if(state == ConnectionState.done) {
                        if(snapshot.hasError) {
                          resultado = "Error";
                        } else {
                          resultado = snapshot.data;

                          if (resultado == null){
                            resultado = "Algo deu errado.";
                          }
                        }
                      }
                      return DropdownButton<String>(
                        items: resultado.map((String dropDownStringItem) {
                          return DropdownMenuItem<String>(
                            value: dropDownStringItem,
                            child: Text(dropDownStringItem),
                          );
                        }).toList(),

                        onChanged: (String newValueSelected){
                          setState(() {
                            this._currentItemSelected = newValueSelected;
                          });
                        },
                        value: _currentItemSelected,
                        isExpanded: true,
                      );
                    },
                  ),

In the current form I get the following error:

The method 'map' was called on null.
Receiver: null
Tried calling: map(Closure: (String) => DropdownMenuItem<String>)

type '(String) => DropdownMenuItem<String>' is not a subtype of type '(String, dynamic) => MapEntry<dynamic, dynamic>' of 'transform'

What am I doing wrong?

2 answers

0

The Futurebuilder is a widget that is built based on the last interaction with a Future.

When your Future returned by the function _categorias() has not yet completed, snapshot.data will be null, preventing you from continuing.

It is not really a "mistake", it is only a state that, while waiting, needs to have a coherent return.

You have two ways to fix this:

  • What you tried to do,

resultado = "Algo deu errado.";

That would be a thought equivalent to: while I still don’t have snapshot.data, I create my own fake "date" to maintain a single build function

There is nothing wrong, if you are sure that your "date" prepared is consistent with the date that comes from the snapshot. I believe this was the mistake in your case, since your snapshot.data is a map that links "String" to "Dynamic". If you did something like this instead:

if (resultado == null){
                            resultado = {"campo1":0};
                          }

(Or the equivalent for your map structure) Your code should work.

Or the other way,

  • Check first of all if the snapshot has data, and if you have returned a Widget, if you have not returned another. This is the conventional way of doing, including the way the example does, in the Futurebuilder page pointed out above.

To do this, one can for example do:

[...]
builder: (context, snapshot) {
    if (snapshot.hasData) {
        return Dropdown[...]//Seu código e tratamento aqui
    }else{
        return Text('Carregando!'); //Ou uma outra mensagem equivalente.
    }

},
[...]

0


You need to work better upon your returned data...

class Category{
  Category({this.id, this.nome});
  int id;
  String nome;
  
  factory Category.fromJson(Map<String, dynamic> json) => Category(
    id: json["_categoriaid"],
    nome: json["categorianome"]
  );  
}

class Something extends StatefulWidget {
  @override
  _SomethingState createState() => _SomethingState();
}

class _SomethingState extends State<Something> {
  
  @override
  void initState() {
    super.initState();

  }
  
  Future<dynamic> getData() async{
    
    await Future.delayed(const Duration(seconds: 1));
      /* Exemplo de dados retornados */
    final json = jsonDecode('[{"_categoriaid": 1, "categorianome": "Investimentos"}, {"_categoriaid": 2, "categorianome": "Salário"}]');
    return json;
  }

  List<Category> toList(dynamic json){
    List<Category> list = List<Category>();
    
    list = (json as List).map((item) => Category.fromJson(item)).toList();
    return list;
  }
  
  int _currentItemSelected = 1;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        height: 80,
        width: 200,
        child: FutureBuilder(
        future: getData(),
        builder: (context, snapshot) {
          /* Apenas para desenhar algo enquanto não existir informações pra montar o DropDown */
          print(snapshot.hasData);
          if (!snapshot.hasData)
            return Container();
          
          List<Category> categorias = toList(snapshot.data);
          
          return DropdownButton<int>(
            value: _currentItemSelected,
            isExpanded: true,
            items: categorias.map((item) {
              return DropdownMenuItem(
                value: item.id,
                child: Text(item.nome),
              );
            }).toList(),

            onChanged: (value){
              setState((){
                _currentItemSelected = value;
              });
            },
          );
        },
      ),
      )
    );
   }
}

Explanation

  1. We created the class Category to save the data of each object in your JSON.
  2. We created the method toList() that will receive your JSON and then return a list of categories List<Category>.
  3. Then we go through the list and add category by category in your Combobox.

In Combobox we use the field ID as key(int) and the field NOME as display(string)

Observing

Try not to use the same variable for several things, there you were using the var resultado wrong way because she could receive both a Map<String, dynamic> how much a String, opening the way for a future mistake. Structure your data to prevent problems.

  • I managed to make it work, thank you, I still do not understand some parts of the code I will study to understand.

  • If you need to answer your questions here, I will answer... Just one observation, the method getData() I did it just to simulate his comic book, you can ignore it.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.