Home > Software design >  How to get Value to update in Flutter DropdownButton from stream?
How to get Value to update in Flutter DropdownButton from stream?

Time:12-29

I'm trying to get a list from my firebase firestore and provide it as a dropdown button, but when the user selects the option it does not update on GUI.

I think the problems is where I instantiate the dropdownValue variable but I don't where else to place it.

class _LocationNameListState extends State<LocationNameList> {
  @override
  Widget build(BuildContext context) {
    List dropdownOptions = <String>[];
    String? dropdownValue;
    return StreamBuilder(
      stream: LocationController().getAllLocations(),
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return const Text("This is something wrong");
        }
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const CircularProgressIndicator();
        }

        for (var i = 0; i < snapshot.data!.docs.length; i  ) {
          dropdownOptions.add("${snapshot.data!.docs[i]['name']}");
        }
        print(dropdownOptions);
         String dropdownValue = dropdownOptions[0];
        return DropdownButton(
          items: dropdownOptions
              .map((e) => DropdownMenuItem(
                    value: e,
                    child: Text(e),
                  ))
              .toList(),
          onChanged: (value) {
            setState(() {
              dropdownValue = value.toString();
              print(dropdownValue);
            });
          },
          value: dropdownValue,
        );
      },
    );
  }
}

CodePudding user response:

The problem is that your dropDown value is set within your Build method:

Widget build(BuildContext context) {
    List dropdownOptions = <String>[];
    String? dropdown value;
    return StreamBuilder(
    ...

So every setState it gets reset, since the build rebuilds.

To fix the error, move your value outside of the build method:

class _LocationNameListState extends State<LocationNameList> {
  // --> Add this variable over here
  List dropdownOptions = <String>[];
  String? dropdownValue;
  @override
  Widget build(BuildContext context) {
  ...
}

I've managed to reproduce your problem with a simplified example. As you see dropdownValue will be reset, since it's within the build method:

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyDropdown(),
        ),
      ),
    );
  }
}


class MyDropdown extends StatefulWidget {
  const MyDropdown({Key? key}) : super(key: key);

  @override
  State<MyDropdown> createState() => _MyDropdownState();
}

class _MyDropdownState extends State<MyDropdown> {
  @override
  Widget build(BuildContext context) {
    String dropdownValue = 'One';
    return DropdownButton<String>(
      value: dropdownValue,
      icon: const Icon(Icons.arrow_downward),
      iconSize: 24,
      elevation: 16,
      style: const TextStyle(color: Colors.deepPurple),
      underline: Container(
        height: 2,
        color: Colors.deepPurpleAccent,
      ),
      onChanged: (String? newValue) {
        setState(() {
          dropdownValue = newValue!;
        });
      },
      items: <String>['One', 'Two', 'Free', 'Four']
          .map<DropdownMenuItem<String>>((String value) {
        return DropdownMenuItem<String>(
          value: value,
          child: Text(value),
        );
      }).toList(),
    );
  }
}

And to solve the issue:

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyDropdown(),
        ),
      ),
    );
  }
}

class MyDropdown extends StatefulWidget {
  const MyDropdown({Key? key}) : super(key: key);

  @override
  State<MyDropdown> createState() => _MyDropdownState();
}

class _MyDropdownState extends State<MyDropdown> {
  // -->Simply set the value here
  String dropdownValue = 'One';
  @override
  Widget build(BuildContext context) {
    
    return DropdownButton<String>(
      value: dropdownValue,
      icon: const Icon(Icons.arrow_downward),
      iconSize: 24,
      elevation: 16,
      style: const TextStyle(color: Colors.deepPurple),
      underline: Container(
        height: 2,
        color: Colors.deepPurpleAccent,
      ),
      onChanged: (String? newValue) {
        setState(() {
          dropdownValue = newValue!;
        });
      },
      items: <String>['One', 'Two', 'Free', 'Four']
          .map<DropdownMenuItem<String>>((String value) {
        return DropdownMenuItem<String>(
          value: value,
          child: Text(value),
        );
      }).toList(),
    );
  }
}

CodePudding user response:

Every time setState is called, build method gets called. It means String dropdownValue = dropdownOptions[0]; is called as well setting the value of variable to first item of the list.

You need to move dropdownValue to class level variable of your state class. (String? dropdownValue = null)

Then replace above mentioned line with

if(dropdownValue == null) {
    dropdownValue = dropdownOptions[0] 
}
  • Related