Project Goal
Build a contact manager CLI application with full CRUD, search, tagging, JSON export/import, and formatted display. This project combines file I/O, JSON, sorting, and the args package pattern.
Full Code
import 'dart:io';
import 'dart:convert';
class Contact {
final String id;
String name;
String email;
String phone;
String? company;
List<String> tags;
Contact({required this.id, required this.name, required this.email,
required this.phone, this.company, List<String>? tags})
: tags = tags ?? [];
factory Contact.fromJson(Map<String, dynamic> j) => Contact(
id: j['id'], name: j['name'], email: j['email'], phone: j['phone'],
company: j['company'], tags: List<String>.from(j['tags'] ?? []),
);
Map<String, dynamic> toJson() => {
'id': id, 'name': name, 'email': email, 'phone': phone,
'company': company, 'tags': tags,
};
bool matches(String query) {
var q = query.toLowerCase();
return name.toLowerCase().contains(q) ||
email.toLowerCase().contains(q) ||
phone.contains(q) ||
(company?.toLowerCase().contains(q) ?? false) ||
tags.any((t) => t.toLowerCase().contains(q));
}
@override String toString() {
var lines = [
' Name: $name',
' Email: $email',
' Phone: $phone',
];
if (company != null) lines.add(' Company: $company');
if (tags.isNotEmpty) lines.add(' Tags: $${tags.join(', ')}');
return lines.join('\n');
}
}
class ContactManager {
final String _dbPath;
final Map<String, Contact> _contacts = {};
int _nextId = 1;
ContactManager([this._dbPath = 'contacts.json']);
void load() {
var f = File(_dbPath);
if (!f.existsSync()) return;
var data = jsonDecode(f.readAsStringSync()) as Map;
_nextId = data['nextId'] ?? 1;
for (var j in (data['contacts'] as List)) {
var c = Contact.fromJson(j);
_contacts[c.id] = c;
}
print('Loaded $${_contacts.length} contacts');
}
void save() {
File(_dbPath).writeAsStringSync(JsonEncoder.withIndent(' ').convert({
'nextId': _nextId,
'contacts': _contacts.values.map((c) => c.toJson()).toList(),
}));
}
Contact add({required String name, required String email, required String phone,
String? company, List<String>? tags}) {
var c = Contact(id: 'C$${_nextId++}', name: name, email: email,
phone: phone, company: company, tags: tags);
_contacts[c.id] = c;
save();
return c;
}
List<Contact> search(String query) =>
_contacts.values.where((c) => c.matches(query)).toList()
..sort((a, b) => a.name.compareTo(b.name));
List<Contact> get all =>
_contacts.values.toList()..sort((a, b) => a.name.compareTo(b.name));
bool delete(String id) {
if (!_contacts.containsKey(id)) return false;
_contacts.remove(id);
save();
return true;
}
void printAll() {
if (_contacts.isEmpty) { print('No contacts.'); return; }
print('\n===== Contacts ($${_contacts.length}) =====');
for (var c in all) {
print('\n[$${c.id}]');
print(c);
}
}
}
void main(List<String> args) {
var mgr = ContactManager()..load();
// Demo usage when no args:
if (args.isEmpty) {
// Seed some data if empty
if (mgr.all.isEmpty) {
mgr.add(name: 'Alice Johnson', email: 'alice@example.com',
phone: '+1-555-1234', company: 'Tech Corp', tags: ['friend', 'developer']);
mgr.add(name: 'Bob Smith', email: 'bob@example.com',
phone: '+1-555-5678', company: 'Design Studio', tags: ['client']);
mgr.add(name: 'Carol Lee', email: 'carol@example.com',
phone: '+855-12-345678', company: 'Startup Inc', tags: ['friend', 'designer']);
print('Added sample contacts');
}
mgr.printAll();
print('\n--- Search "friend" ---');
mgr.search('friend').forEach((c) { print('\n$${c.name}'); print(c); });
return;
}
switch (args[0]) {
case 'add':
// todo: parse --name, --email, --phone flags
print('Use: add --name X --email Y --phone Z');
case 'search':
if (args.length < 2) { print('Usage: search <query>'); return; }
var results = mgr.search(args[1]);
print('Found $${results.length} contacts:');
results.forEach((c) { print('\n$${c.name}'); print(c); });
case 'list':
mgr.printAll();
case 'delete':
if (args.length < 2) { print('Usage: delete <id>'); return; }
print(mgr.delete(args[1]) ? 'Deleted' : 'Not found');
default:
print('Commands: add, list, search <q>, delete <id>');
}
}
Challenges
- Add tag-based filtering (
list --tag developer)
- Add contact groups/categories
- Export to vCard (.vcf) format
- Build a Flutter contacts UI with search