Skip to content

Instantly share code, notes, and snippets.

@md-riaz
Created May 17, 2025 03:44
Show Gist options
  • Save md-riaz/f801ce35b5a998c9fec831ae09e41a44 to your computer and use it in GitHub Desktop.
Save md-riaz/f801ce35b5a998c9fec831ae09e41a44 to your computer and use it in GitHub Desktop.
add contact flutter demo2
import 'package:flutter/material.dart';
import 'dart:convert';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Contact Phone Input Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const ContactInputScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class ContactInputScreen extends StatefulWidget {
const ContactInputScreen({super.key});
@override
State<ContactInputScreen> createState() => _ContactInputScreenState();
}
class _ContactInputScreenState extends State<ContactInputScreen> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _contactNameController;
List<PhoneEntry> phoneNumbers = [];
final List<PhoneType> phoneTypes = [
PhoneType.mobile,
PhoneType.home,
PhoneType.work,
PhoneType.other,
];
@override
void initState() {
super.initState();
_contactNameController = TextEditingController();
addPhoneNumber(); // Start with one empty phone number
}
@override
void dispose() {
_contactNameController.dispose();
for (var entry in phoneNumbers) {
entry.controller.dispose();
}
super.dispose();
}
void addPhoneNumber() {
setState(() {
if (phoneNumbers.isEmpty) {
phoneNumbers.add(PhoneEntry(
number: '',
isPrimary: true,
phoneType: phoneTypes.first,
controller: TextEditingController()));
} else {
phoneNumbers.add(PhoneEntry(
number: '',
isPrimary: false,
phoneType: phoneTypes.first,
controller: TextEditingController()));
}
});
}
void updatePhoneNumber(int index, String number) {
setState(() {
phoneNumbers[index] = phoneNumbers[index].copyWith(number: number);
});
}
void updatePhoneType(int index, PhoneType? phoneType) {
if (phoneType == null) {
return;
}
setState(() {
phoneNumbers[index] = phoneNumbers[index].copyWith(phoneType: phoneType);
});
}
void setPrimaryNumber(int index) {
setState(() {
for (int i = 0; i < phoneNumbers.length; i++) {
phoneNumbers[i] = phoneNumbers[i].copyWith(isPrimary: i == index);
}
});
}
void deletePhoneNumber(int index) {
setState(() {
phoneNumbers[index].controller.dispose();
phoneNumbers.removeAt(index);
if (phoneNumbers.isEmpty) {
addPhoneNumber(); // Ensure at least one phone number exists
} else if (phoneNumbers.every((element) => !element.isPrimary)) {
setPrimaryNumber(0);
}
});
}
String toJson() {
final numbersJson = phoneNumbers
.map((entry) => {
'number': entry.number,
'is_primary': entry.isPrimary ? 1 : 0,
'phone_type': entry.phoneType.index,
})
.toList();
final json = {
'name': _contactNameController.text,
'numbers': numbersJson,
};
return jsonEncode(json);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Contact Phone Numbers'),
),
body: Form(
key: _formKey,
child: ListView(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Contact Name',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4.0),
TextFormField(
controller: _contactNameController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
isDense: true,
contentPadding: EdgeInsets.symmetric(
horizontal: 12, vertical: 8), // Add content padding
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a name';
}
return null;
},
),
],
),
),
...phoneNumbers.asMap().entries.map((entry) {
int index = entry.key;
PhoneEntry phoneEntry = entry.value;
return PhoneEntryWidget(
index: index,
phoneEntry: phoneEntry,
updatePhoneNumber: updatePhoneNumber,
updatePhoneType: updatePhoneType,
setPrimaryNumber: setPrimaryNumber,
deletePhoneNumber: deletePhoneNumber,
phoneTypes: phoneTypes,
key: Key(index.toString()),
);
}).toList(),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
addPhoneNumber();
},
child: const Text('Add Phone Number'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Saving Contact: ${toJson()}')),
);
}
},
child: const Text('Save Contact'),
),
),
],
),
),
);
}
}
enum PhoneType {
mobile,
home,
work,
other,
}
class PhoneEntry {
final String number;
final bool isPrimary;
final PhoneType phoneType;
final TextEditingController controller;
PhoneEntry({
required this.number,
required this.isPrimary,
required this.phoneType,
required this.controller,
});
PhoneEntry copyWith(
{String? number, bool? isPrimary, PhoneType? phoneType}) {
return PhoneEntry(
number: number ?? this.number,
isPrimary: isPrimary ?? this.isPrimary,
phoneType: phoneType ?? this.phoneType,
controller: controller,
);
}
}
class PhoneEntryWidget extends StatefulWidget {
final int index;
final PhoneEntry phoneEntry;
final Function(int, String) updatePhoneNumber;
final Function(int, PhoneType?) updatePhoneType;
final Function(int) setPrimaryNumber;
final Function(int) deletePhoneNumber;
final List<PhoneType> phoneTypes;
const PhoneEntryWidget({
required Key key,
required this.index,
required this.phoneEntry,
required this.updatePhoneNumber,
required this.updatePhoneType,
required this.setPrimaryNumber,
required this.deletePhoneNumber,
required this.phoneTypes,
}) : super(key: key);
@override
State<PhoneEntryWidget> createState() => _PhoneEntryWidgetState();
}
class _PhoneEntryWidgetState extends State<PhoneEntryWidget> {
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(8.0),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Phone Number',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4.0),
TextFormField(
controller: widget.phoneEntry.controller,
decoration: const InputDecoration(
border: OutlineInputBorder(),
isDense: true,
contentPadding: EdgeInsets.symmetric(
horizontal: 12,
vertical: 8), // Add content padding
),
keyboardType: TextInputType.phone,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a phone number';
}
return null;
},
onChanged: (text) {
widget.updatePhoneNumber(widget.index, text);
},
),
],
),
),
const SizedBox(width: 8.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Type',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4.0),
DropdownButton<PhoneType>(
value: widget.phoneEntry.phoneType,
items: widget.phoneTypes.map((PhoneType type) {
return DropdownMenuItem<PhoneType>(
value: type,
child: Text(type.toString().split('.').last),
);
}).toList(),
onChanged: (PhoneType? newValue) {
widget.updatePhoneType(widget.index, newValue);
},
),
],
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Text('Primary: '),
Radio<bool>(
value: true,
groupValue: widget.phoneEntry.isPrimary,
onChanged: (bool? value) {
if (value != null && value) {
widget.setPrimaryNumber(widget.index);
}
},
),
],
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
widget.deletePhoneNumber(widget.index);
},
),
],
),
],
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment