nusantara_data 1.0.0
nusantara_data: ^1.0.0 copied to clipboard
A comprehensive Flutter library for Indonesia location data (Province → City → District → Village → Postal Code) with BPS (Badan Pusat Statistik) codes. Complete data for all 38 provinces, 514 cities, [...]
import 'package:flutter/material.dart';
import 'package:nusantara_data/nusantara_data.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize the library
await NusantaraData.initialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nusantara Data Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String? _selectedProvinceId;
String? _selectedCityId;
String? _selectedDistrictId;
String? _selectedVillageId;
List<ProvinceSummary> _provinces = [];
List<CitySummary> _cities = [];
List<DistrictSummary> _districts = [];
List<VillageSummary> _villages = [];
List<String> _postalCodes = [];
DataStatistics? _statistics;
@override
void initState() {
super.initState();
_loadData();
}
void _loadData() {
setState(() {
_provinces = NusantaraData.getAllProvinces();
_statistics = NusantaraData.getStatistics();
});
}
void _onProvinceSelected(String? provinceId) {
setState(() {
_selectedProvinceId = provinceId;
_selectedCityId = null;
_selectedDistrictId = null;
_selectedVillageId = null;
_cities = provinceId != null
? NusantaraData.getCitiesByProvinceId(provinceId)
: [];
_districts = [];
_villages = [];
_postalCodes = [];
});
}
void _onCitySelected(String? cityId) {
setState(() {
_selectedCityId = cityId;
_selectedDistrictId = null;
_selectedVillageId = null;
_districts =
cityId != null ? NusantaraData.getDistrictsByCityId(cityId) : [];
_villages = [];
_postalCodes = [];
});
}
void _onDistrictSelected(String? districtId) {
setState(() {
_selectedDistrictId = districtId;
_selectedVillageId = null;
_villages = districtId != null
? NusantaraData.getVillagesByDistrictId(districtId)
: [];
_postalCodes = [];
});
}
void _onVillageSelected(String? villageId) {
setState(() {
_selectedVillageId = villageId;
_postalCodes = villageId != null
? NusantaraData.getPostalCodesByVillageId(villageId)
: [];
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Nusantara Data Example'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Statistics Card
if (_statistics != null) _buildStatisticsCard(),
const SizedBox(height: 24),
// Location Picker
const Text(
'Pilih Lokasi',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
// Province Dropdown
_buildDropdown<ProvinceSummary>(
label: 'Provinsi',
hint: 'Pilih Provinsi',
value: _selectedProvinceId,
items: _provinces,
getId: (p) => p.id,
getLabel: (p) => p.name,
onChanged: _onProvinceSelected,
),
// City Dropdown
_buildDropdown<CitySummary>(
label: 'Kota/Kabupaten',
hint: 'Pilih Kota/Kabupaten',
value: _selectedCityId,
items: _cities,
getId: (c) => c.id,
getLabel: (c) => '${c.type.displayName} ${c.name}',
onChanged: _onCitySelected,
enabled: _selectedProvinceId != null,
),
// District Dropdown
_buildDropdown<DistrictSummary>(
label: 'Kecamatan',
hint: 'Pilih Kecamatan',
value: _selectedDistrictId,
items: _districts,
getId: (d) => d.id,
getLabel: (d) => d.name,
onChanged: _onDistrictSelected,
enabled: _selectedCityId != null,
),
// Village Dropdown
_buildDropdown<VillageSummary>(
label: 'Kelurahan/Desa',
hint: 'Pilih Kelurahan/Desa',
value: _selectedVillageId,
items: _villages,
getId: (v) => v.id,
getLabel: (v) => v.name,
onChanged: _onVillageSelected,
enabled: _selectedDistrictId != null,
),
// Postal Code Display
if (_postalCodes.isNotEmpty) ...[
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Kode Pos',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: _postalCodes
.map((code) => Chip(label: Text(code)))
.toList(),
),
],
),
),
),
],
const SizedBox(height: 24),
// Search Demo
const Text(
'Demo Pencarian',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
_buildSearchDemo(),
],
),
),
);
}
Widget _buildStatisticsCard() {
final stats = _statistics!;
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Data Indonesia v${stats.version}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
'Updated: ${stats.updatedAt}',
style: TextStyle(color: Colors.grey[600]),
),
const Divider(),
_buildStatRow('Provinsi', stats.provinceCount),
_buildStatRow('Kota/Kabupaten', stats.cityCount),
_buildStatRow('Kecamatan', stats.districtCount),
_buildStatRow('Kelurahan/Desa', stats.villageCount),
_buildStatRow('Kode Pos', stats.postalCodeCount),
],
),
),
);
}
Widget _buildStatRow(String label, int value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label),
Text(
_formatNumber(value),
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
);
}
String _formatNumber(int number) {
return number.toString().replaceAllMapped(
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
(Match m) => '${m[1]},',
);
}
Widget _buildDropdown<T>({
required String label,
required String hint,
required String? value,
required List<T> items,
required String Function(T) getId,
required String Function(T) getLabel,
required void Function(String?) onChanged,
bool enabled = true,
}) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
DropdownButtonFormField<String>(
value: value,
hint: Text(hint),
isExpanded: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
items: items.map((item) {
return DropdownMenuItem<String>(
value: getId(item),
child: Text(getLabel(item)),
);
}).toList(),
onChanged: enabled ? onChanged : null,
),
],
),
);
}
Widget _buildSearchDemo() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Pencarian dengan typo tolerance:',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
_buildSearchExample('Jakrta', 'DKI Jakarta'),
_buildSearchExample('Bandug', 'Bandung'),
_buildSearchExample('Surbaaya', 'Surabaya'),
_buildSearchExample('Yogyakrta', 'Yogyakarta'),
],
),
),
);
}
Widget _buildSearchExample(String query, String expected) {
final results = NusantaraData.searchProvinces(query);
final found = results.isNotEmpty ? results.first.name : 'Not found';
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Expanded(
flex: 2,
child: Text('"$query"'),
),
const Icon(Icons.arrow_forward, size: 16),
const SizedBox(width: 8),
Expanded(
flex: 3,
child: Text(
found,
style: TextStyle(
fontWeight: FontWeight.bold,
color: found.toLowerCase().contains(expected.toLowerCase())
? Colors.green
: Colors.red,
),
),
),
],
),
);
}
}