Skip to content

Getters & Setters

Understand how to use getters and setters to control access to class fields in Dart.


What is it?

Getters and setters are special methods that provide controlled access to class fields. Getters allow you to retrieve values (possibly computed), and setters allow you to modify values with validation or side effects.


Why does it exist?

Getters and setters exist to:

  • Encapsulate field access
  • Add validation when setting values
  • Compute values on demand
  • Provide read-only or write-only access
  • Hide implementation details
  • Maintain class invariants

Basic Getters

Simple Getters

class Person {
  String _name;
  int _age;

  Person(this._name, this._age);

  // Basic getter
  String get name => _name;

  // Getter with logic
  String get greeting => 'Hello, I am $_name';

  // Boolean getter
  bool get isAdult => _age >= 18;

  // Computed getter
  String get displayName => '$_name ($_age)';
}

// Usage
var person = Person('Alice', 25);
print(person.name); // Alice
print(person.greeting); // Hello, I am Alice
print(person.isAdult); // true
print(person.displayName); // Alice (25)

Computed Getters

class Rectangle {
  double _width;
  double _height;

  Rectangle(this._width, this._height);

  // Computed getters (no backing field)
  double get area => _width * _height;
  double get perimeter => 2 * (_width + _height);
  double get diagonal => sqrt(_width * _width + _height * _height);

  // Boolean getter
  bool get isSquare => _width == _height;

  // String getter
  String get description => 'Rectangle(${_width}x${_height})';

  // Getter with calculation
  String get dimensions => '${_width.toStringAsFixed(1)} x ${_height.toStringAsFixed(1)}';
}

// Usage
var rect = Rectangle(10, 20);
print(rect.area); // 200
print(rect.perimeter); // 60
print(rect.isSquare); // false
print(rect.description); // Rectangle(10x20)

Basic Setters

Simple Setters

class Temperature {
  double _celsius;

  Temperature(this._celsius);

  // Getter
  double get celsius => _celsius;
  double get fahrenheit => (_celsius * 9 / 5) + 32;

  // Setter with validation
  set celsius(double value) {
    if (value < -273.15) {
      throw ArgumentError('Temperature cannot be below absolute zero');
    }
    _celsius = value;
  }

  // Setter with calculation
  set fahrenheit(double value) {
    _celsius = (value - 32) * 5 / 9;
  }
}

// Usage
var temp = Temperature(25);
print(temp.celsius); // 25
print(temp.fahrenheit); // 77

temp.celsius = 30;
print(temp.fahrenheit); // 86

temp.fahrenheit = 100;
print(temp.celsius); // 37.777...

// Throws error
// temp.celsius = -300; // Error: Below absolute zero

Setters with Validation

class User {
  String _email;
  String _password;
  int _age;

  User(this._email, this._password, this._age);

  // Getter for email (read-only)
  String get email => _email;

  // Setter with validation
  set email(String value) {
    if (!value.contains('@')) {
      throw ArgumentError('Invalid email format');
    }
    _email = value;
  }

  // Password setter with strength check
  set password(String value) {
    if (value.length < 8) {
      throw ArgumentError('Password must be at least 8 characters');
    }
    if (!RegExp(r'[A-Z]').hasMatch(value)) {
      throw ArgumentError('Password must contain uppercase letter');
    }
    if (!RegExp(r'[a-z]').hasMatch(value)) {
      throw ArgumentError('Password must contain lowercase letter');
    }
    if (!RegExp(r'\d').hasMatch(value)) {
      throw ArgumentError('Password must contain a number');
    }
    _password = value;
  }

  // Age setter with range validation
  set age(int value) {
    if (value < 0 || value > 150) {
      throw ArgumentError('Invalid age range');
    }
    _age = value;
  }

  // Read-only getters
  String get password => '******'; // Never return actual password
  int get age => _age;

  bool get isAdult => _age >= 18;
}

// Usage
var user = User('user@example.com', 'Password123', 25);
user.email = 'new@example.com';
print(user.email); // new@example.com

// Throws errors
// user.password = 'short'; // Error: Too short
// user.age = -5; // Error: Invalid age

Advanced Getters

Getters with Logic

class BankAccount {
  String _accountNumber;
  double _balance;
  List<Transaction> _transactions = [];

  BankAccount(this._accountNumber, this._balance);

  // Getter with calculation
  double get balance {
    // Balance is just the current value
    return _balance;
  }

  // Getter with aggregation
  double get totalDeposits {
    return _transactions
        .where((t) => t.type == TransactionType.deposit)
        .fold(0, (sum, t) => sum + t.amount);
  }

  double get totalWithdrawals {
    return _transactions
        .where((t) => t.type == TransactionType.withdraw)
        .fold(0, (sum, t) => sum + t.amount);
  }

  // Getter with condition
  bool get hasTransactions => _transactions.isNotEmpty;

  // Getter returning list
  List<Transaction> get recentTransactions {
    return _transactions.take(10).toList();
  }

  // Getter with JSON format
  Map<String, dynamic> get toJson => {
    'accountNumber': _accountNumber,
    'balance': _balance,
    'transactionCount': _transactions.length,
  };
}

enum TransactionType { deposit, withdraw }

class Transaction {
  final TransactionType type;
  final double amount;
  final DateTime date;

  Transaction(this.type, this.amount, this.date);
}

// Usage
var account = BankAccount('123456', 1000);
print(account.balance); // 1000
print(account.hasTransactions); // false

Read-only and Write-only

Controlled Access

class Configuration {
  String _apiKey;
  bool _debugMode;
  static const int MAX_RETRIES = 3;

  Configuration(this._apiKey, this._debugMode);

  // Read-only getter
  String get apiKey => _apiKey;
  bool get debugMode => _debugMode;
  int get maxRetries => MAX_RETRIES;

  // Write-only setter (no getter)
  set apiKey(String value) {
    if (value.isEmpty) {
      throw ArgumentError('API key cannot be empty');
    }
    _apiKey = value;
  }

  // Getter with transformation
  String get maskedApiKey {
    if (_apiKey.length <= 4) return '****';
    return '${_apiKey.substring(0, 4)}...';
  }

  // Setter with side effects
  set debugMode(bool value) {
    _debugMode = value;
    if (value) {
      print('Debug mode enabled');
    }
  }
}

// Usage
var config = Configuration('abcdef123456', false);
print(config.maskedApiKey); // abcd...
print(config.apiKey); // abcdef123456 (read-only)

config.apiKey = 'newkey789'; // Write-only
// print(config.apiKey); // Still read-only, shows original

Getters and Setters vs Public Fields

Comparison

// Using public fields (less control)
class PublicUser {
  String name;
  int age;

  PublicUser(this.name, this.age);
}

// Using getters and setters (more control)
class PrivateUser {
  String _name;
  int _age;

  PrivateUser(this._name, this._age);

  // Getters
  String get name => _name;
  int get age => _age;

  // Setters with validation
  set name(String value) {
    if (value.isEmpty) {
      throw ArgumentError('Name cannot be empty');
    }
    _name = value;
  }

  set age(int value) {
    if (value < 0 || value > 150) {
      throw ArgumentError('Invalid age');
    }
    _age = value;
  }
}

// Usage
var pub = PublicUser('Alice', 25);
pub.name = ''; // No validation!
pub.age = -5; // No validation!

var priv = PrivateUser('Alice', 25);
// priv.name = ''; // Throws error
// priv.age = -5; // Throws error

Best Practices

Use Getters for Computed Values

// Good: Getters for computed values
class Circle {
  double radius;

  Circle(this.radius);

  double get area => pi * radius * radius;
  double get circumference => 2 * pi * radius;
  double get diameter => 2 * radius;
}

// Bad: Methods for simple computations
class BadCircle {
  double radius;

  BadCircle(this.radius);

  double calculateArea() => pi * radius * radius;
  double calculateCircumference() => 2 * pi * radius;
}

Use Setters for Validation

// Good: Validation in setters
class Product {
  String _name;
  double _price;

  Product(this._name, this._price);

  set name(String value) {
    if (value.isEmpty) {
      throw ArgumentError('Product name cannot be empty');
    }
    _name = value;
  }

  set price(double value) {
    if (value < 0) {
      throw ArgumentError('Price cannot be negative');
    }
    _price = value;
  }
}

// Bad: No validation
class BadProduct {
  String name;
  double price;

  BadProduct(this.name, this.price);
  // Anyone can set invalid values
}

Use Getters for Read-only Access

// Good: Read-only through getter
class Singleton {
  static final Singleton _instance = Singleton._internal();
  int _counter = 0;

  factory Singleton() => _instance;
  Singleton._internal();

  int get counter => _counter;
  void increment() => _counter++;
}

// Bad: Public mutable field
class BadSingleton {
  static final BadSingleton _instance = BadSingleton._internal();
  int counter = 0;

  factory BadSingleton() => _instance;
  BadSingleton._internal();
  // Anyone can modify counter directly
}

Common Mistakes

Not Using Private Fields

Wrong:

class Person {
  String name; // Public
  int age; // Public

  set name(String value) { // Setter exists but field is also public
    this.name = value; // Can still be accessed directly
  }
}

Correct:

class Person {
  String _name;
  int _age;

  Person(this._name, this._age);

  String get name => _name;
  set name(String value) => _name = value;
}

Exposing Internal State

Wrong:

class Configuration {
  List<String> _settings = [];

  List<String> get settings => _settings; // Exposes internal list
  // Caller can mutate the list!
}

Correct:

class Configuration {
  List<String> _settings = [];

  List<String> get settings => List.unmodifiable(_settings);
  // Returns immutable copy
}

Summary

Getters and setters provide controlled access to class fields, enabling validation, computed properties, and encapsulation. They're essential for maintaining class integrity and hiding implementation details.


Next Steps

Now that you understand getters and setters, continue to:


Did You Know?

  • Getters and setters don't create new fields
  • Computed getters have no backing field
  • Setters can have validation logic
  • You can have a setter without a getter
  • Getters can return immutable copies
  • Dart's getters are called like properties
  • Setters use the assignment syntax (obj.value = x)