flutter
/

Dart Encapsulation – Hiding Data with Private Fields

Last Sync: Today

On this page

11
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Dart Encapsulation – Hiding Data with Private Fields

What is Encapsulation?

Encapsulation is one of the four fundamental OOP concepts (along with inheritance, polymorphism, and abstraction). It refers to bundling data (variables) and methods that operate on that data into a single unit (a class) and restricting direct access to some of the object's internal details. This protects the integrity of the data and reduces complexity by hiding implementation details.

Encapsulation in Dart

In Dart, encapsulation is achieved through library‑level privacy. There are no private, protected, or public keywords. Instead, any identifier (variable, method, class) that starts with an underscore _ is considered library private – it is visible only within the same library (usually the same file). This simple mechanism allows you to hide internal details.

Private Fields (Using _)

To make a field private, prefix its name with an underscore. This field can only be accessed within the same file. Attempting to access it from another file results in a compile‑time error.

DARTRead-only
1
class BankAccount {
  String _accountNumber; // private field
  double _balance;       // private field

  BankAccount(this._accountNumber, this._balance);

  void deposit(double amount) {
    if (amount > 0) _balance += amount;
  }

  void withdraw(double amount) {
    if (amount > 0 && _balance >= amount) _balance -= amount;
  }

  // Public method to get the balance (read‑only)
  double getBalance() => _balance;
}

void main() {
  var account = BankAccount('123456', 1000);
  account.deposit(500);
  print(account.getBalance()); // 1500
  // account._balance = 10000; // Error: _balance is private
}

Getters and Setters

Getters and setters provide controlled access to private fields. They look like methods but are accessed like properties. You can add validation or logic when getting or setting a value.

DARTRead-only
1
class Person {
  String _name;
  int _age;

  Person(this._name, this._age);

  // Getter for name
  String get name => _name;

  // Setter for name (with validation)
  set name(String value) {
    if (value.isNotEmpty) {
      _name = value;
    } else {
      throw ArgumentError('Name cannot be empty');
    }
  }

  // Getter for age
  int get age => _age;

  // Setter for age (with validation)
  set age(int value) {
    if (value >= 0 && value <= 120) {
      _age = value;
    } else {
      throw ArgumentError('Age must be between 0 and 120');
    }
  }
}

void main() {
  var person = Person('Alice', 30);
  print(person.name); // Alice (using getter)
  person.age = 31;    // using setter
  print(person.age);  // 31
  // person.age = -5; // Throws ArgumentError
}

Read‑Only and Write‑Only Properties

You can create read‑only properties by providing only a getter (no setter), and write‑only properties by providing only a setter (though less common).

DARTRead-only
1
class Temperature {
  double _celsius;

  Temperature(this._celsius);

  // Read‑only property in Fahrenheit (computed)
  double get fahrenheit => _celsius * 9/5 + 32;

  // Setter for Fahrenheit – updates the internal Celsius value
  set fahrenheit(double value) {
    _celsius = (value - 32) * 5/9;
  }

  // Read‑only Celsius (only getter)
  double get celsius => _celsius;
}

void main() {
  var temp = Temperature(25);
  print(temp.celsius);    // 25
  print(temp.fahrenheit); // 77

  temp.fahrenheit = 68;   // set fahrenheit, which updates celsius
  print(temp.celsius);    // 20.0
}

Encapsulation in Different Files

Privacy in Dart is based on libraries. If you split your code across files, private members are only accessible within the same file. To share a private member within a package, you can use a part directive, but it's generally better to keep related classes together or provide public getters/setters.

DARTRead-only
1
// file: person.dart
class Person {
  String _secret; // private

  Person(this._secret);

  String get secret => _secret; // public getter
}

// file: main.dart
import 'person.dart';

void main() {
  var p = Person('hidden');
  // print(p._secret); // Error: _secret is private
  print(p.secret);     // OK
}

Why Use Encapsulation?

    • Data protection: Prevent accidental or unauthorized modification of data.
    • Validation: Ensure that data meets certain criteria before being set.
    • Flexibility: You can change internal implementation without affecting external code.
    • Reduced complexity: Users of your class only need to know the public interface, not the internal details.
    • Maintainability: Encapsulated code is easier to debug and modify.

Common Mistakes

    • Forgetting that privacy is library‑based: A private member is accessible anywhere in the same file, which might be surprising if you have multiple classes in one file.
    • Not providing getters/setters when needed: Making fields private without any access method makes them completely unusable outside the class.
    • Using getters/setters for simple fields without validation: If no logic is needed, a public field might be simpler (but then you lose the ability to add logic later without breaking API).
    • Creating unnecessary getters/setters: Over‑encapsulation can make code verbose.

Best Practices

    • Make fields private by default and provide public getters/setters only when needed.
    • Use getters for computed properties (like area of a rectangle).
    • Validate data in setters to maintain object integrity.
  • -Keep classes focused – each class should have a clear responsibility.
    • Use meaningful names for private fields (still follow camelCase, just with leading underscore).

Complete Example

DARTRead-only
1
class Student {
  String _id;
  String _name;
  List<double> _grades = [];

  Student(this._id, this._name);

  String get id => _id;
  String get name => _name;

  set name(String value) {
    if (value.isNotEmpty) {
      _name = value;
    } else {
      throw ArgumentError('Name cannot be empty');
    }
  }

  void addGrade(double grade) {
    if (grade >= 0 && grade <= 100) {
      _grades.add(grade);
    } else {
      throw ArgumentError('Grade must be between 0 and 100');
    }
  }

  double get averageGrade {
    if (_grades.isEmpty) return 0;
    return _grades.reduce((a, b) => a + b) / _grades.length;
  }

  void displayInfo() {
    print('Student: $_name (ID: $_id)');
    print('Grades: $_grades');
    print('Average: ${averageGrade.toStringAsFixed(2)}');
  }
}

void main() {
  var student = Student('S001', 'Alice');
  student.addGrade(85);
  student.addGrade(92);
  student.addGrade(78);
  student.displayInfo();
}

Key Takeaways

    • Encapsulation hides internal data and exposes a controlled interface.
    • In Dart, privacy is library‑based: use _ to make members private to the file.
    • Getters and setters provide controlled access to private fields and can include validation.
    • Use read‑only properties (only getter) for data that should not be changed externally.
    • Encapsulation improves code maintainability, flexibility, and security.

Try it yourself

class BankAccount {
  String _accountNumber;
  double _balance;

  BankAccount(this._accountNumber, this._balance);

  double get balance => _balance;

  void deposit(double amount) {
    if (amount > 0) _balance += amount;
  }

  void withdraw(double amount) {
    if (amount > 0 && _balance >= amount) _balance -= amount;
  }
}

void main() {
  var account = BankAccount('12345', 1000);
  account.deposit(500);
  print('Balance: ${account.balance}');
  // account._balance = 10000; // Error if uncommented
}

Test Your Knowledge

Q1
of 4

How do you make a field private in Dart?

A
Use the `private` keyword
B
Prefix the field name with an underscore `_`
C
Use the `protected` keyword
D
Declare it in a separate file
Q2
of 4

What is the scope of a private member in Dart?

A
Only within the same class
B
Only within the same file (library)
C
Only within the same package
D
Only within the same function
Q3
of 4

What is the purpose of a getter?

A
To modify a private field
B
To provide read access to a private field
C
To delete a field
D
To create a new object
Q4
of 4

What does the following code do? class Person { String _name; String get name => _name; }

A
Creates a read‑only property `name`
B
Creates a write‑only property `name`
C
Creates a public field `name`
D
Causes a compile error

Frequently Asked Questions

What is the difference between `private` in Dart and other languages like Java?

In Java, private members are accessible only within the same class. In Dart, privacy is at the library level – a member with _ is accessible anywhere within the same file (including other classes in that file). This is because Dart uses library‑based privacy, not class‑based.

Can I access a private field from another file if I use `part`?

Yes, if you use the part directive to combine multiple files into one library, private members are accessible across those files. However, using part is discouraged in modern Dart; it's better to keep related code together or provide public getters/setters.

Should I always use private fields and expose getters/setters?

Not necessarily. If a field is simple and you don't anticipate adding logic later, a public field is acceptable. However, using private fields + getters/setters gives you the flexibility to add validation or change the internal representation later without breaking the public API.

How do I create a read‑only property?

Define a getter without a corresponding setter. For example: String get name => _name;. This allows external code to read the value but not change it directly.

Can a getter or setter be static?

Yes, you can define static getters and setters, but they operate on static fields and are called on the class itself, not on instances.

What happens if I try to access a private member from another file?

You'll get a compile‑time error indicating that the member is not defined. This is how Dart enforces encapsulation across files.

Are there performance implications of using getters/setters?

No, getters and setters are inlined by the Dart compiler (in release mode) and have no runtime overhead compared to direct field access. They are a safe abstraction.

Previous

dart abstraction

Next

dart mixins

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.