Why we use mixins
Application of mixins
Coding example of mixins
Why we use mixins
Mixins in Flutter (and Dart) are a way to reuse a class’s code in multiple class hierarchies. Mixins allow you to add functionality to classes without needing inheritance, making it easier to maintain and reuse code. They are used to encapsulate and share behavior between classes.
Key Points about Mixins
Code Reusability: Mixins allow you to reuse methods and properties in multiple classes.
Avoid Multiple Inheritance: Dart does not support multiple inheritance, but mixins offer a way to achieve similar behavior.
Flexibility: You can mix in multiple mixins into a single class, providing a flexible way to combine functionality.
In other words mixins are normal classes from which we can borrow methods(or variables) from without extending the class. In dart we can do this by
For Java developers, it is a completely new concept to learn as Java does not supports multiple inheritance or mixins. Java tries to make up for this by using Interfaces, but that is not as useful or flexible as mixins.
class B { //B is not allowed to extend any other class other than object
method(){
....
}
}
class A with B {
....
......
}
void main() {
A a = A();
a.method(); //we got the method without inheriting B
}
Defining a Mixin
In Dart, a mixin is created using the mixin keyword.
Example: LoggerMixin
Let's define a mixin for logging purposes.
import 'package:logger/logger.dart';
// Define the LoggerMixin
mixin LoggerMixin {
final Logger _logger = Logger();
void logInfo(String message) {
_logger.i(message);
}
void logError(String message) {
_logger.e(message);
}
}
Using the LoggerMixin
To use the mixin, you simply include it in the class definition using the with keyword.
class MyClass with LoggerMixin {
final String name;
MyClass(this.name);
void doSomething() {
logInfo('$name is doing something.');
try {
// Simulate an error
int result = 1 ~/ 0;
} catch (e) {
logError('Error occurred: $e');
}
}
}
Complete Example in a Flutter App
Let's put everything together and create a simple Flutter app that uses the LoggerMixin.
Step 1: Add Dependencies
Add the logger package to your pubspec.yaml file.
dependencies:
flutter:
sdk: flutter
logger: ^1.0.0
Step 2: Create the LoggerMixin
Create a file logger_mixin.dart and define the LoggerMixin.
// logger_mixin.dart
import 'package:logger/logger.dart';
mixin LoggerMixin {
final Logger _logger = Logger();
void logInfo(String message) {
_logger.i(message);
}
void logError(String message) {
_logger.e(message);
}
}
Step 3: Create a Class Using the LoggerMixin
Create a file my_class.dart and define the MyClass which uses the LoggerMixin.
// my_class.dart
import 'logger_mixin.dart';
class MyClass with LoggerMixin {
final String name;
MyClass(this.name);
void doSomething() {
logInfo('$name is doing something.');
try {
// Simulate an error
int result = 1 ~/ 0;
} catch (e) {
logError('Error occurred: $e');
}
}
}
Step 4: Integrate MyClass in Flutter App
Update your main.dart to use MyClass and display the log output.
import 'package:flutter/material.dart';
import 'my_class.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
final MyClass myInstance = MyClass('TestInstance');
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Mixin Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
myInstance.doSomething();
},
child: Text('Do Something'),
),
),
);
}
}
Explanation
LoggerMixin: This mixin provides logging functionality through logInfo and logError methods.
MyClass: This class uses the LoggerMixin to gain logging capabilities. It logs messages when performing actions and handling errors.
Flutter App: The main Flutter app integrates MyClass and demonstrates how to use the logging functionality. When you press the "Do Something" button, the doSomething method is called, and log messages are printed to the console.
Running the Example
When you run the Flutter app and press the "Do Something" button, you should see log messages in the console, demonstrating the logging functionality added to MyClass via the LoggerMixin.
This example showcases how mixins in Flutter can be used to encapsulate and reuse functionality across different classes, promoting clean and maintainable code.
Application of mixins
LoggerMixin
A mixin to provide logging capabilities.
import 'package:logger/logger.dart';
mixin LoggerMixin {
final Logger _logger = Logger();
void logInfo(String message) {
_logger.i(message);
}
void logError(String message) {
_logger.e(message);
}
}
class MyClass with LoggerMixin {
void doSomething() {
logInfo('Doing something');
}
}
- ValidationMixin A mixin to provide form validation methods.
mixin ValidationMixin {
bool isValidEmail(String email) {
return email.contains('@');
}
bool isValidPassword(String password) {
return password.length > 6;
}
}
class LoginForm with ValidationMixin {
final String email;
final String password;
LoginForm(this.email, this.password);
bool validate() {
return isValidEmail(email) && isValidPassword(password);
}
}
- DisposalMixin A mixin to provide a method to dispose resources.
mixin DisposalMixin {
final List<void Function()> _disposeCallbacks = [];
void addDisposeCallback(void Function() callback) {
_disposeCallbacks.add(callback);
}
void dispose() {
for (var callback in _disposeCallbacks) {
callback();
}
}
}
class MyResource with DisposalMixin {
void initialize() {
// Initialize resources
addDisposeCallback(() {
// Dispose resource
print('Resource disposed');
});
}
}
- StateHelperMixin A mixin to provide common state management methods for stateful widgets.
import 'package:flutter/material.dart';
mixin StateHelperMixin<T extends StatefulWidget> on State<T> {
void showSnackbar(String message) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
}
}
class MyStatefulWidget extends StatefulWidget {
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> with StateHelperMixin<MyStatefulWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('State Helper Mixin Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
showSnackbar('Hello, World!');
},
child: Text('Show Snackbar'),
),
),
);
}
}
- ConnectivityMixin A mixin to check network connectivity.
import 'package:connectivity_plus/connectivity_plus.dart';
mixin ConnectivityMixin {
final Connectivity _connectivity = Connectivity();
Future<bool> isConnected() async {
var connectivityResult = await _connectivity.checkConnectivity();
return connectivityResult != ConnectivityResult.none;
}
}
class MyService with ConnectivityMixin {
Future<void> fetchData() async {
if (await isConnected()) {
// Fetch data
print('Fetching data...');
} else {
print('No internet connection');
}
}
}
- NavigationMixin A mixin to provide navigation methods.
import 'package:flutter/material.dart';
mixin NavigationMixin<T extends StatefulWidget> on State<T> {
void navigateTo(Widget page) {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => page));
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with NavigationMixin<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Navigation Mixin Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
navigateTo(SecondPage());
},
child: Text('Go to Second Page'),
),
),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: Text('This is the second page'),
),
);
}
}
- ErrorHandlingMixin A mixin to provide error handling methods.
mixin ErrorHandlingMixin {
void handleError(dynamic error) {
print('An error occurred: $error');
}
}
class MyErrorProneClass with ErrorHandlingMixin {
void riskyOperation() {
try {
// Simulate an error
throw Exception('Something went wrong');
} catch (error) {
handleError(error);
}
}
}
LifecycleMixin
A mixin to provide lifecycle methods, especially useful for handling logic when widgets are initialized and disposed.
import 'package:flutter/widgets.dart';
mixin LifecycleMixin<T extends StatefulWidget> on State<T> {
@override
void initState() {
super.initState();
onInit();
}
@override
void dispose() {
onDispose();
super.dispose();
}
void onInit() {
// Override this method in the class using this mixin
}
void onDispose() {
// Override this method in the class using this mixin
}
}
class MyStatefulWidget extends StatefulWidget {
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> with LifecycleMixin<MyStatefulWidget> {
@override
void onInit() {
super.onInit();
print('Widget Initialized');
}
@override
void onDispose() {
super.onDispose();
print('Widget Disposed');
}
@override
Widget build(BuildContext context) {
return Container();
}
}
- CacheMixin A mixin to provide caching capabilities for data.
mixin CacheMixin<T> {
final Map<String, T> _cache = {};
void cacheData(String key, T value) {
_cache[key] = value;
}
T? getCachedData(String key) {
return _cache[key];
}
void clearCache() {
_cache.clear();
}
}
class DataService with CacheMixin<String> {
void fetchData() {
// Fetch and cache data
cacheData('key1', 'value1');
}
String? retrieveData() {
return getCachedData('key1');
}
}
- AnimationMixin A mixin to provide common animation utilities.
import 'package:flutter/animation.dart';
mixin AnimationMixin<T extends StatefulWidget> on State<T>, SingleTickerProviderStateMixin<T> {
late AnimationController animationController;
@override
void initState() {
super.initState();
animationController = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..forward();
}
@override
void dispose() {
animationController.dispose();
super.dispose();
}
}
class AnimatedWidgetExample extends StatefulWidget {
@override
_AnimatedWidgetExampleState createState() => _AnimatedWidgetExampleState();
}
class _AnimatedWidgetExampleState extends State<AnimatedWidgetExample> with AnimationMixin<AnimatedWidgetExample> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FadeTransition(
opacity: animationController,
child: Text('Hello, World!'),
),
),
);
}
}
- LocalizationMixin A mixin to provide localization capabilities.
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
mixin LocalizationMixin {
String getTranslatedText(BuildContext context, String key) {
// Implement your localization logic here
// For example, using a localization package
return key;
}
}
class LocalizedWidget extends StatelessWidget with LocalizationMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(getTranslatedText(context, 'title')),
),
body: Center(
child: Text(getTranslatedText(context, 'hello_world')),
),
);
}
}
- ThemeMixin A mixin to provide theme management.
import 'package:flutter/material.dart';
mixin ThemeMixin<T extends StatefulWidget> on State<T> {
ThemeData get currentTheme => Theme.of(context);
bool get isDarkMode => currentTheme.brightness == Brightness.dark;
}
class ThemedWidget extends StatefulWidget {
@override
_ThemedWidgetState createState() => _ThemedWidgetState();
}
class _ThemedWidgetState extends State<ThemedWidget> with ThemeMixin<ThemedWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Themed Widget'),
),
body: Center(
child: Text(
isDarkMode ? 'Dark Mode' : 'Light Mode',
style: TextStyle(color: isDarkMode ? Colors.white : Colors.black),
),
),
);
}
}
- StateManagementMixin A mixin to provide state management utilities.
mixin StateManagementMixin<T extends StatefulWidget> on State<T> {
bool _isLoading = false;
bool get isLoading => _isLoading;
void setLoading(bool loading) {
setState(() {
_isLoading = loading;
});
}
}
class StateManagedWidget extends StatefulWidget {
@override
_StateManagedWidgetState createState() => _StateManagedWidgetState();
}
class _StateManagedWidgetState extends State<StateManagedWidget> with StateManagementMixin<StateManagedWidget> {
void loadData() async {
setLoading(true);
await Future.delayed(Duration(seconds: 2));
setLoading(false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('State Management Mixin'),
),
body: Center(
child: isLoading
? CircularProgressIndicator()
: ElevatedButton(
onPressed: loadData,
child: Text('Load Data'),
),
),
);
}
}
- PermissionMixin A mixin to handle permission requests.
import 'package:permission_handler/permission_handler.dart';
mixin PermissionMixin {
Future<bool> requestPermission(Permission permission) async {
final status = await permission.request();
return status == PermissionStatus.granted;
}
Future<bool> checkPermission(Permission permission) async {
final status = await permission.status;
return status == PermissionStatus.granted;
}
}
class PermissionWidget extends StatelessWidget with PermissionMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Permission Mixin Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () async {
if (await requestPermission(Permission.camera)) {
print('Camera permission granted');
} else {
print('Camera permission denied');
}
},
child: Text('Request Camera Permission'),
),
),
);
}
}
Coding example of mixins
save the data using flutter secure storage dependency
final secureStorage = FlutterSecureStorage();
await secureStorage.write(key: 'orgRoleId', value: orgRoleId.toString()); // Save orgRoleId
Get data using flutter secure storage
final orgSlug = widget.orgSlug;
final secureStorage = FlutterSecureStorage();
final orgRoleId = await secureStorage.read(key: 'orgRoleId');
setState(() {
_userRole = determineUserRole(orgRoleId); // Use the mixin to determine the role
});
apply logic in mixins dart file
Top comments (0)