mn_loc 1.0.1
mn_loc: ^1.0.1 copied to clipboard
A Flutter package for 3-level dependent dropdowns (District -> Subdivision -> Circle) loaded directly from JSON data.
๐ mn_loc โ Dependent Dropdowns from JSON #
A modern, highly customizable Flutter package for 3-level dependent dropdown selections (District โ Subdivision โ Circle) loaded directly from JSON files (district.json, subdivision.json, circle.json).
๐ฏ Key Design Philosophy: Individual Standalone Widgets #
Important
No Monolithic Single-Widget Wrappers!
Unlike traditional packages that force all dropdowns into one fixed container, mn_loc provides unwrapped, standalone individual widgets:
<DistrictDropdown /><SubdivisionDropdown /><CircleDropdown />
You can place each widget anywhere in your UI tree โ across different cards, grid columns, form tabs, or stepper screens. They reactively sync state through LocationDropdownProvider.
โจ Features #
- ๐งฉ 100% Modular & Unwrapped: Place District, Subdivision, and Circle dropdowns anywhere in your layout.
- โก Reactive Auto-Dependencies: Selecting a District automatically updates Subdivisions and resets Circle selections.
- ๐ JSON Asset & Raw String Support: Built-in JSON loader (
LocationJsonService) for asset paths, raw JSON strings, or direct model lists. - ๐จ Material 3 Design: Supports theme customization, custom input decorations, custom item builders, icons, and disabled states.
- ๐งน Clean State Management: Built with
provider(ChangeNotifier).
๐ Getting Started #
Add mn_loc to your pubspec.yaml:
dependencies:
mn_loc: ^1.0.0
provider: ^6.1.2
Ensure your pubspec.yaml includes the location JSON assets:
flutter:
assets:
- assets/data/district.json
- assets/data/subdivision.json
- assets/data/circle.json
๐ป Usage Guide #
1. Register LocationDropdownProvider #
Wrap your root widget or screen with MultiProvider or ChangeNotifierProvider:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:mn_loc/mn_loc.dart';
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => LocationDropdownProvider()..initialize(),
),
],
child: const MyApp(),
),
);
}
2. Place Dropdown Widgets Independently #
Each widget connects automatically to LocationDropdownProvider:
import 'package:flutter/material.dart';
import 'package:mn_loc/mn_loc.dart';
class LocationFormPage extends StatelessWidget {
const LocationFormPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Location Form')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// 1. District Dropdown Widget
const DistrictDropdown(
label: 'District',
hint: 'Choose a district...',
),
const SizedBox(height: 16),
// 2. Subdivision Dropdown Widget (Auto-disabled until District is chosen)
const SubdivisionDropdown(
label: 'Subdivision',
hint: 'Choose a subdivision...',
disabledHint: 'Select District first',
),
const SizedBox(height: 16),
// 3. Circle Dropdown Widget (Auto-disabled until Subdivision is chosen)
const CircleDropdown(
label: 'Circle',
hint: 'Choose a circle...',
disabledHint: 'Select Subdivision first',
),
const SizedBox(height: 24),
// Display current selection summary card
const LocationSummaryCard(),
],
),
),
);
}
}
๐ JSON Schema Specification #
district.json #
[
{ "id": "d_01", "name": "Imphal West", "code": "IW" },
{ "id": "d_02", "name": "Imphal East", "code": "IE" }
]
subdivision.json #
[
{ "id": "s_01", "district_id": "d_01", "name": "Lamphelpat" },
{ "id": "s_02", "district_id": "d_01", "name": "Patsoi" }
]
circle.json #
[
{ "id": "c_01", "subdivision_id": "s_01", "name": "Lamphel Circle I" },
{ "id": "c_02", "subdivision_id": "s_01", "name": "Lamphel Circle II" }
]
๐ Accessing Selected Data #
Access selections reactively anywhere in your code using Provider:
final provider = context.watch<LocationDropdownProvider>();
final DistrictModel? district = provider.selectedDistrict;
final SubdivisionModel? subdivision = provider.selectedSubdivision;
final CircleModel? circle = provider.selectedCircle;
print('District ID: ${district?.id}, Name: ${district?.name}');
print('Subdivision ID: ${subdivision?.id}, Name: ${subdivision?.name}');
print('Circle ID: ${circle?.id}, Name: ${circle?.name}');
To reset selections programmatically:
context.read<LocationDropdownProvider>().resetSelections();
๐ ๏ธ API Reference #
DistrictDropdown #
| Parameter | Type | Default | Description |
|---|---|---|---|
label |
String? |
'District' |
Input decoration field label. |
hint |
String |
'Select District' |
Placeholder text. |
decoration |
InputDecoration? |
null |
Custom input decoration. |
onChanged |
ValueChanged<DistrictModel?>? |
null |
Optional selection change listener callback. |
itemBuilder |
Widget Function(...) |
null |
Custom dropdown item builder widget. |
SubdivisionDropdown #
| Parameter | Type | Default | Description |
|---|---|---|---|
label |
String? |
'Subdivision' |
Input decoration field label. |
hint |
String |
'Select Subdivision' |
Placeholder text when enabled. |
disabledHint |
String |
'Select District First' |
Placeholder text when parent district is not selected. |
onChanged |
ValueChanged<SubdivisionModel?>? |
null |
Selection change listener. |
CircleDropdown #
| Parameter | Type | Default | Description |
|---|---|---|---|
label |
String? |
'Circle' |
Input decoration field label. |
hint |
String |
'Select Circle' |
Placeholder text when enabled. |
disabledHint |
String |
'Select Subdivision First' |
Placeholder text when parent subdivision is not selected. |
onChanged |
ValueChanged<CircleModel?>? |
null |
Selection change listener. |
๐ License #
This project is licensed under the MIT License - see the LICENSE file for details.