Inheritance & Mixins
Sharing and Combining Abilities Across Classes
Open interactive version (quiz + challenge)Real-world analogy
What is it?
Inheritance lets a class absorb the properties and methods of another class, creating a parent-child relationship. Mixins let you add capabilities to a class without a strict parent-child hierarchy -- like plugging in USB accessories. Abstract classes define contracts without implementations. Dart supports single inheritance (one parent) but unlimited mixins. Together, these tools let you share code, enforce contracts, and compose behaviors flexibly.
Real-world relevance
In team_mvp_kit, inheritance and interfaces are the backbone of Clean Architecture. Abstract repository classes in the domain layer define what operations are available. Concrete classes in the data layer implement those interfaces with real API calls via Dio and local storage via Hive. BLoC classes extend Bloc from the bloc package. Sealed classes like AuthState enable type-safe state management where the compiler ensures you handle every possible state.
Key points
- Inheritance with extends — A subclass inherits all public properties and methods from its superclass. Use extends to create the relationship. The subclass can add new members or override existing ones.
- The super Keyword — Use super to call the parent class constructor or access parent methods. The super constructor call must be in the initializer list. You can also call super.methodName() to invoke the parent's version of an overridden method.
- Overriding Methods — Use @override to replace a parent method with a new implementation. The annotation is not strictly required but is strongly recommended -- it helps catch typos and signals intent to other developers.
- Abstract Classes — An abstract class cannot be instantiated directly. It can contain abstract methods (no body) that subclasses must implement. Use abstract classes to define contracts for a family of classes.
- Interfaces (implicit) — Every class in Dart implicitly defines an interface. Use implements to promise that your class will provide implementations of all properties and methods. Unlike extends, implements does not inherit any code.
- Mixins with mixin Keyword — A mixin is a class whose methods can be mixed into another class without inheritance. Define a mixin with the mixin keyword and apply it with the with keyword. Classes can use multiple mixins.
- Mixin Constraints (on keyword) — Use the on keyword to restrict which classes can use a mixin. This ensures the mixin can safely call methods from the constrained superclass.
- Repository Pattern in team_mvp_kit — The project uses abstract classes as repository interfaces in the domain layer and concrete implementations in the data layer. This is the core of Clean Architecture's dependency inversion.
- Sealed Classes (Dart 3) — Sealed classes restrict which classes can extend or implement them. All subtypes must be in the same library. This enables exhaustive pattern matching in switch statements -- the compiler knows all possible cases.
Code example
abstract class AuthRepository {
Future<UserEntity> login(String email, String password);
Future<void> logout();
Future<bool> isLoggedIn();
}
mixin CacheManager {
final Map<String, dynamic> _cache = {};
void cacheData(String key, dynamic value) {
_cache[key] = value;
}
dynamic getCachedData(String key) {
return _cache[key];
}
void clearCache() {
_cache.clear();
}
}
class AuthRepositoryImpl
implements AuthRepository
with CacheManager {
final Dio dio;
AuthRepositoryImpl({required this.dio});
@override
Future<UserEntity> login(String email, String password) async {
final response = await dio.post('/auth/login', data: {
'email': email,
'password': password,
});
final user = UserModel.fromJson(response.data);
cacheData('current_user', user);
return user;
}
@override
Future<void> logout() async {
await dio.post('/auth/logout');
clearCache();
}
@override
Future<bool> isLoggedIn() async {
return getCachedData('current_user') != null;
}
}Line-by-line walkthrough
- 1. Define abstract class AuthRepository -- this is the interface that lives in the domain layer
- 2. Declare abstract method login that takes email and password, returns Future
- 3. Declare abstract method logout that returns Future
- 4. Declare abstract method isLoggedIn that returns Future
- 5. Define mixin CacheManager -- this provides caching capability to any class that uses it
- 6. Create a private cache Map to store key-value pairs
- 7. Define cacheData method to store a value under a key
- 8. Define getCachedData method to retrieve a value by key
- 9. Define clearCache method to empty the cache
- 10. AuthRepositoryImpl implements AuthRepository and uses CacheManager mixin
- 11. Declare a Dio instance for HTTP requests
- 12. Constructor requires a Dio instance via named parameter
- 13. Implement login: make a POST request to /auth/login with credentials
- 14. Parse the response data into a UserModel using fromJson factory
- 15. Cache the current user using the mixin's cacheData method
- 16. Return the user (UserModel extends UserEntity)
- 17. Implement logout: make a POST request to /auth/logout
- 18. Clear the cache using the mixin's clearCache method
- 19. Implement isLoggedIn: check if current_user exists in the cache
- 20. Return true if cached data is not null, false otherwise
Spot the bug
abstract class Repository {
void save(String data);
}
class FileRepository extends Repository {
void save(String data) {
print('Saving to file: $data');
}
}
class DbRepository extends Repository {
void save(int data) {
print('Saving to database: $data');
}
}Need a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- Dart Inheritance (dart.dev)
- Dart Mixins (dart.dev)
- Abstract Classes in Dart (dart.dev)