Implementing Dependent Dropdowns with BLoC in Flutter


Introduction
Handling dependent dropdowns in Flutter using the BLoC pattern is a common scenario when implementing forms that require cascading selections. A typical example is selecting a country, then a state, and finally a city. In this guide, we will cover how to properly manage this scenario using BLoC events, states, and efficient state management.
Requirements
flutter_bloc package
API or mock data for countries, states, and cities
Step 1: Define BLoC Events
To handle user interactions and API calls, we define different events for loading data and selecting values.
abstract class DropdownEvent {}class LoadCountries extends DropdownEvent {}
class CountrySelected extends DropdownEvent {
final String selectedCountry;
CountrySelected(this.selectedCountry);
}class StateSelected extends DropdownEvent {
final String selectedState;
StateSelected(this.selectedState);
}class CitySelected extends DropdownEvent {
final String selectedCity;
CitySelected(this.selectedCity);
}class EditSelection extends DropdownEvent {
final String selectedCountry;
final String selectedState;
final String selectedCity;
EditSelection(this.selectedCountry, this.selectedState, this.selectedCity);
}
Step 2: Define BLoC States
We define states to hold the list of available options and selected values.
abstract class DropdownState {}
class DropdownInitial extends DropdownState {}
class DropdownLoaded extends DropdownState {
final List<String> countries;
final List<String> states;
final List<String> cities;
final String? selectedCountry;
final String? selectedState;
final String? selectedCity;
DropdownLoaded({
required this.countries,
required this.states,
required this.cities,
this.selectedCountry,
this.selectedState,
this.selectedCity,
});
}
Step 3: Implement the BLoC
The BLoC will handle fetching data, user selections, and emitting new states.
class DropdownBloc extends Bloc<DropdownEvent, DropdownState> {
DropdownBloc() : super(DropdownInitial()) {
on<LoadCountries>(_loadCountries);
on<CountrySelected>(_onCountrySelected);
on<StateSelected>(_onStateSelected);
on<CitySelected>(_onCitySelected);
on<EditSelection>(_onEditSelection);
}
Future<void> _loadCountries(LoadCountries event, Emitter<DropdownState> emit) async {
final countries = await fetchCountries();
emit(DropdownLoaded(countries: countries, states: [], cities: []));
}
Future<void> _onCountrySelected(CountrySelected event, Emitter<DropdownState> emit) async {
final states = await fetchStates(event.selectedCountry);
emit(DropdownLoaded(
countries: (state as DropdownLoaded).countries,
states: states,
cities: [],
selectedCountry: event.selectedCountry,
));
}
Future<void> _onStateSelected(StateSelected event, Emitter<DropdownState> emit) async {
final cities = await fetchCities(event.selectedState);
emit(DropdownLoaded(
countries: (state as DropdownLoaded).countries,
states: (state as DropdownLoaded).states,
cities: cities,
selectedCountry: (state as DropdownLoaded).selectedCountry,
selectedState: event.selectedState,
));
}
void _onCitySelected(CitySelected event, Emitter<DropdownState> emit) {
emit(DropdownLoaded(
countries: (state as DropdownLoaded).countries,
states: (state as DropdownLoaded).states,
cities: (state as DropdownLoaded).cities,
selectedCountry: (state as DropdownLoaded).selectedCountry,
selectedState: (state as DropdownLoaded).selectedState,
selectedCity: event.selectedCity,
));
}
Future<void> _onEditSelection(EditSelection event, Emitter<DropdownState> emit) async {
final countries = await fetchCountries();
final states = await fetchStates(event.selectedCountry);
final cities = await fetchCities(event.selectedState);
emit(DropdownLoaded(
countries: countries,
states: states,
cities: cities,
selectedCountry: event.selectedCountry,
selectedState: event.selectedState,
selectedCity: event.selectedCity,
));
}
}
Step 4: Implement the UI
Now, we create the UI that interacts with the BLoC.
class DropdownPage extends StatelessWidget {
final String? editCountry;
final String? editState;
final String? editCity;
DropdownPage({this.editCountry, this.editState, this.editCity});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) {
final bloc = DropdownBloc();
if (editCountry != null) {
bloc.add(EditSelection(editCountry!, editState!, editCity!));
} else {
bloc.add(LoadCountries());
}
return bloc;
},
child: Column(
children: [
BlocBuilder<DropdownBloc, DropdownState>(
builder: (context, state) {
if (state is DropdownLoaded) {
return DropdownButton<String>(
value: state.selectedCountry,
hint: Text("Select Country"),
items: state.countries.map((country) => DropdownMenuItem(value: country, child: Text(country))).toList(),
onChanged: (value) => context.read<DropdownBloc>().add(CountrySelected(value!)),
);
}
return CircularProgressIndicator();
},
),
BlocBuilder<DropdownBloc, DropdownState>(
builder: (context, state) {
if (state is DropdownLoaded && state.selectedCountry != null) {
return DropdownButton<String>(
value: state.selectedState,
hint: Text("Select State"),
items: state.states.map((state) => DropdownMenuItem(value: state, child: Text(state))).toList(),
onChanged: (value) => context.read<DropdownBloc>().add(StateSelected(value!)),
);
}
return SizedBox.shrink();
},
),
],
),
);
}
}
Conclusion
By implementing dependent dropdowns using BLoC, we ensure a clean architecture, efficient state management, and easy handling of prepopulated selections. This approach is scalable for larger applications and keeps the UI responsive.
Hope you enjoyed this article!
Subscribe to my newsletter
Read articles from NonStop io Technologies directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

NonStop io Technologies
NonStop io Technologies
Product Development as an Expertise Since 2015 Founded in August 2015, we are a USA-based Bespoke Engineering Studio providing Product Development as an Expertise. With 80+ satisfied clients worldwide, we serve startups and enterprises across San Francisco, Seattle, New York, London, Pune, Bangalore, Tokyo and other prominent technology hubs.