added resources, coding challenges, and more study guides
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# Asynchronous Programming in Dart: A Comprehensive Guide
|
||||
|
||||
Asynchronous programming is crucial in Dart and Flutter for handling operations that might take some time to complete, such as network requests or file I/O. This guide covers the key concepts and tools for asynchronous programming in Dart.
|
||||
|
||||
## 1. Futures
|
||||
|
||||
A Future represents a computation that doesn't complete immediately. It's a way to represent asynchronous operations.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
Future<String> fetchUserOrder() {
|
||||
return Future.delayed(Duration(seconds: 2), () => "Large Latte");
|
||||
}
|
||||
|
||||
void main() {
|
||||
print('Fetching user order...');
|
||||
fetchUserOrder().then((order) => print('Your order is: $order'));
|
||||
print('This runs before the order is fetched!');
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Async and Await
|
||||
|
||||
The `async` and `await` keywords provide a cleaner syntax for working with Futures.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
Future<String> fetchUserOrder() {
|
||||
return Future.delayed(Duration(seconds: 2), () => "Large Latte");
|
||||
}
|
||||
|
||||
void main() async {
|
||||
print('Fetching user order...');
|
||||
String order = await fetchUserOrder();
|
||||
print('Your order is: $order');
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Streams
|
||||
|
||||
Streams provide a way to receive a sequence of events over time.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
Stream<int> countStream(int max) async* {
|
||||
for (int i = 1; i <= max; i++) {
|
||||
yield i;
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
countStream(5).listen((data) => print('Count: $data'));
|
||||
}
|
||||
```
|
||||
|
||||
## 4. StreamController
|
||||
|
||||
StreamController gives you a way to create and manage your own streams.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
import 'dart:async';
|
||||
|
||||
void main() {
|
||||
final controller = StreamController<String>();
|
||||
|
||||
controller.stream.listen((data) => print('Received: $data'));
|
||||
|
||||
controller.add('Hello');
|
||||
controller.add('World');
|
||||
|
||||
controller.close();
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Mastering asynchronous programming is essential for building responsive and efficient Dart and Flutter applications. Practice using Futures, async/await, Streams, and StreamControllers to become proficient in handling asynchronous operations.
|
||||
@@ -0,0 +1,160 @@
|
||||
# Dart Collections: A Comprehensive Guide
|
||||
|
||||
Dart provides several built-in collection types to help you manage and manipulate groups of objects. This guide covers the main collection types and their operations.
|
||||
|
||||
## 1. Lists
|
||||
|
||||
Lists are ordered, indexable collections of objects.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
// Creating lists
|
||||
var numbers = [1, 2, 3, 4, 5];
|
||||
List<String> fruits = ['apple', 'banana', 'orange'];
|
||||
|
||||
// Accessing elements
|
||||
print(numbers[0]); // 1
|
||||
print(fruits.last); // orange
|
||||
|
||||
// Adding elements
|
||||
numbers.add(6);
|
||||
fruits.addAll(['grape', 'mango']);
|
||||
|
||||
// Removing elements
|
||||
numbers.remove(3);
|
||||
fruits.removeAt(0);
|
||||
|
||||
// Other operations
|
||||
print(numbers.length); // 5
|
||||
print(fruits.contains('banana')); // true
|
||||
|
||||
// Iterating
|
||||
for (var fruit in fruits) {
|
||||
print(fruit);
|
||||
}
|
||||
|
||||
// Functional operations
|
||||
var doubledNumbers = numbers.map((n) => n * 2).toList();
|
||||
var evenNumbers = numbers.where((n) => n.isEven).toList();
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Sets
|
||||
|
||||
Sets are unordered collections of unique items.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
// Creating sets
|
||||
var uniqueNumbers = {1, 2, 3, 4, 5};
|
||||
Set<String> uniqueFruits = {'apple', 'banana', 'orange'};
|
||||
|
||||
// Adding elements
|
||||
uniqueNumbers.add(6);
|
||||
uniqueFruits.addAll(['grape', 'mango']);
|
||||
|
||||
// Removing elements
|
||||
uniqueNumbers.remove(3);
|
||||
|
||||
// Set operations
|
||||
var setA = {1, 2, 3, 4};
|
||||
var setB = {3, 4, 5, 6};
|
||||
print(setA.union(setB)); // {1, 2, 3, 4, 5, 6}
|
||||
print(setA.intersection(setB)); // {3, 4}
|
||||
print(setA.difference(setB)); // {1, 2}
|
||||
|
||||
// Checking membership
|
||||
print(uniqueFruits.contains('banana')); // true
|
||||
|
||||
// Iterating
|
||||
for (var number in uniqueNumbers) {
|
||||
print(number);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Maps
|
||||
|
||||
Maps are collections of key-value pairs.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
// Creating maps
|
||||
var ages = {'Alice': 30, 'Bob': 25, 'Charlie': 35};
|
||||
Map<String, int> scores = {'Math': 90, 'Science': 85, 'History': 95};
|
||||
|
||||
// Accessing values
|
||||
print(ages['Alice']); // 30
|
||||
print(scores['Math']); // 90
|
||||
|
||||
// Adding or updating entries
|
||||
ages['David'] = 28;
|
||||
scores.addAll({'Geography': 88, 'Literature': 92});
|
||||
|
||||
// Removing entries
|
||||
ages.remove('Bob');
|
||||
|
||||
// Checking keys and values
|
||||
print(ages.containsKey('Charlie')); // true
|
||||
print(scores.containsValue(100)); // false
|
||||
|
||||
// Iterating
|
||||
ages.forEach((key, value) {
|
||||
print('$key is $value years old');
|
||||
});
|
||||
|
||||
// Getting all keys or values
|
||||
print(scores.keys);
|
||||
print(scores.values);
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Collection Methods and Operations
|
||||
|
||||
Dart provides many useful methods for working with collections.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
var numbers = [1, 2, 3, 4, 5];
|
||||
|
||||
// Functional methods
|
||||
var doubled = numbers.map((n) => n * 2);
|
||||
var evenNumbers = numbers.where((n) => n.isEven);
|
||||
var sum = numbers.reduce((a, b) => a + b);
|
||||
var product = numbers.fold(1, (a, b) => a * b);
|
||||
|
||||
print(doubled); // (2, 4, 6, 8, 10)
|
||||
print(evenNumbers); // (2, 4)
|
||||
print(sum); // 15
|
||||
print(product); // 120
|
||||
|
||||
// Sorting
|
||||
var fruits = ['banana', 'apple', 'orange'];
|
||||
fruits.sort();
|
||||
print(fruits); // [apple, banana, orange]
|
||||
|
||||
// Reversing
|
||||
var reversedNumbers = numbers.reversed;
|
||||
print(reversedNumbers); // (5, 4, 3, 2, 1)
|
||||
|
||||
// Shuffling
|
||||
numbers.shuffle();
|
||||
print(numbers); // Random order
|
||||
|
||||
// Finding elements
|
||||
var firstEven = numbers.firstWhere((n) => n.isEven, orElse: () => -1);
|
||||
print(firstEven);
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Dart's collection types provide powerful tools for managing groups of objects. Understanding how to effectively use Lists, Sets, and Maps, along with their associated methods and operations, is crucial for efficient Dart programming.
|
||||
@@ -0,0 +1,152 @@
|
||||
# Error Handling in Dart: A Comprehensive Guide
|
||||
|
||||
Proper error handling is crucial for writing robust and reliable Dart applications. This guide covers the main concepts and techniques for handling errors in Dart.
|
||||
|
||||
## 1. Try-Catch Blocks
|
||||
|
||||
The try-catch block is the primary mechanism for handling exceptions in Dart.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
try {
|
||||
int result = 12 ~/ 0; // Integer division by zero
|
||||
print(result);
|
||||
} on IntegerDivisionByZeroException {
|
||||
print('Cannot divide by zero');
|
||||
} catch (e) {
|
||||
print('An error occurred: $e');
|
||||
} finally {
|
||||
print('This always executes');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Throwing Exceptions
|
||||
|
||||
You can throw exceptions using the `throw` keyword.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void validateAge(int age) {
|
||||
if (age < 0) {
|
||||
throw ArgumentError('Age cannot be negative');
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
try {
|
||||
validateAge(-5);
|
||||
} catch (e) {
|
||||
print('Error: $e');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Custom Exceptions
|
||||
|
||||
You can create custom exception classes by extending `Exception` or `Error`.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class InsufficientFundsException implements Exception {
|
||||
final double balance;
|
||||
final double withdrawal;
|
||||
|
||||
InsufficientFundsException(this.balance, this.withdrawal);
|
||||
|
||||
@override
|
||||
String toString() => 'Insufficient funds: $balance available, $withdrawal requested';
|
||||
}
|
||||
|
||||
void withdraw(double balance, double amount) {
|
||||
if (amount > balance) {
|
||||
throw InsufficientFundsException(balance, amount);
|
||||
}
|
||||
// Proceed with withdrawal
|
||||
}
|
||||
|
||||
void main() {
|
||||
try {
|
||||
withdraw(100, 150);
|
||||
} on InsufficientFundsException catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Async Error Handling
|
||||
|
||||
When working with asynchronous code, you can use try-catch with async-await.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
Future<String> fetchData() async {
|
||||
// Simulating network request
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
throw Exception('Failed to fetch data');
|
||||
}
|
||||
|
||||
void main() async {
|
||||
try {
|
||||
String data = await fetchData();
|
||||
print(data);
|
||||
} catch (e) {
|
||||
print('Error occurred: $e');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Error Propagation
|
||||
|
||||
You can rethrow exceptions to propagate them up the call stack.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void processData(String data) {
|
||||
try {
|
||||
// Process data
|
||||
throw FormatException('Invalid data format');
|
||||
} catch (e) {
|
||||
print('Error in processData: $e');
|
||||
rethrow; // Rethrow the caught exception
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
try {
|
||||
processData('some data');
|
||||
} catch (e) {
|
||||
print('Error in main: $e');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Using `assert`
|
||||
|
||||
The `assert` statement is used to check boolean conditions during development.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void setVolume(int volume) {
|
||||
assert(volume >= 0 && volume <= 100, 'Volume must be between 0 and 100');
|
||||
// Set volume
|
||||
}
|
||||
|
||||
void main() {
|
||||
setVolume(50); // OK
|
||||
setVolume(150); // AssertionError in debug mode
|
||||
}
|
||||
```
|
||||
|
||||
Note: Assertions are only enabled in debug mode and are ignored in production code.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Effective error handling is essential for creating robust Dart applications. By using these techniques, you can gracefully handle exceptions, provide meaningful error messages, and improve the reliability of your code.
|
||||
@@ -0,0 +1,100 @@
|
||||
# Extension Methods in Dart: A Comprehensive Guide
|
||||
|
||||
Extension methods allow you to add new functionality to existing libraries or classes without modifying their source code.
|
||||
|
||||
## 1. Creating an Extension
|
||||
|
||||
To create an extension, use the `extension` keyword followed by the extension name and the type you're extending.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
extension StringExtension on String {
|
||||
String capitalize() {
|
||||
return '${this[0].toUpperCase()}${this.substring(1)}';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Using an Extension
|
||||
|
||||
Once defined, you can use the extension method as if it were a part of the original class.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
String name = 'john';
|
||||
print(name.capitalize()); // Prints: John
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Extension on Built-in Types
|
||||
|
||||
You can add extensions to any type, including built-in types like `int` or `List`.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
extension IntExtension on int {
|
||||
bool isEven() => this % 2 == 0;
|
||||
}
|
||||
|
||||
extension ListExtension<T> on List<T> {
|
||||
T getRandomElement() => this[DateTime.now().microsecond % length];
|
||||
}
|
||||
|
||||
void main() {
|
||||
print(42.isEven()); // Prints: true
|
||||
|
||||
var fruits = ['apple', 'banana', 'orange'];
|
||||
print(fruits.getRandomElement()); // Prints a random fruit
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Naming Conflicts
|
||||
|
||||
If there's a naming conflict, you can use the `hide` keyword to resolve it.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
extension MyStringExtension on String {
|
||||
bool isLong() => length > 10;
|
||||
}
|
||||
|
||||
extension AnotherStringExtension on String {
|
||||
bool isLong() => length > 20;
|
||||
}
|
||||
|
||||
void main() {
|
||||
String text = 'Hello, World!';
|
||||
|
||||
// Use specific extension
|
||||
print(MyStringExtension(text).isLong()); // Prints: true
|
||||
print(AnotherStringExtension(text).isLong()); // Prints: false
|
||||
|
||||
// Or hide one extension
|
||||
import 'my_extensions.dart' hide MyStringExtension;
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Static Extension Methods
|
||||
|
||||
You can also define static extension methods.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
extension NumberParsing on String {
|
||||
static int parseInt(String input) => int.parse(input);
|
||||
}
|
||||
|
||||
void main() {
|
||||
print(NumberParsing.parseInt('42')); // Prints: 42
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Extension methods provide a clean way to add functionality to existing types without modifying their source code. They are particularly useful when working with types from external libraries or core Dart types that you can't modify directly.
|
||||
@@ -0,0 +1,206 @@
|
||||
# Flutter Animations: A Comprehensive Guide
|
||||
|
||||
Animations add life to your Flutter applications, making them more engaging and intuitive. This guide covers implicit animations, explicit animations, and custom animations using AnimationController.
|
||||
|
||||
## 1. Implicit Animations
|
||||
|
||||
Implicit animations are the easiest way to add animations to your app. Flutter provides several implicitly animated widgets that automatically animate changes to their properties.
|
||||
|
||||
### Example: AnimatedContainer
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ImplicitAnimationExample extends StatefulWidget {
|
||||
@override
|
||||
_ImplicitAnimationExampleState createState() => _ImplicitAnimationExampleState();
|
||||
}
|
||||
|
||||
class _ImplicitAnimationExampleState extends State<ImplicitAnimationExample> {
|
||||
bool _isBig = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_isBig = !_isBig;
|
||||
});
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(seconds: 1),
|
||||
curve: Curves.easeInOut,
|
||||
width: _isBig ? 300 : 100,
|
||||
height: _isBig ? 300 : 100,
|
||||
color: _isBig ? Colors.blue : Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Explicit Animations
|
||||
|
||||
Explicit animations give you more control over the animation process. They use an AnimationController to drive the animation.
|
||||
|
||||
### Example: AnimatedBuilder
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ExplicitAnimationExample extends StatefulWidget {
|
||||
@override
|
||||
_ExplicitAnimationExampleState createState() => _ExplicitAnimationExampleState();
|
||||
}
|
||||
|
||||
class _ExplicitAnimationExampleState extends State<ExplicitAnimationExample> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(seconds: 2),
|
||||
vsync: this,
|
||||
)..repeat(reverse: true);
|
||||
_animation = CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return Container(
|
||||
width: 200 * _animation.value,
|
||||
height: 200 * _animation.value,
|
||||
color: Colors.blue,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Custom Animations with AnimationController
|
||||
|
||||
For more complex animations, you can use AnimationController directly.
|
||||
|
||||
### Example: Custom Animation
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
class CustomAnimationExample extends StatefulWidget {
|
||||
@override
|
||||
_CustomAnimationExampleState createState() => _CustomAnimationExampleState();
|
||||
}
|
||||
|
||||
class _CustomAnimationExampleState extends State<CustomAnimationExample> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(seconds: 10),
|
||||
vsync: this,
|
||||
)..repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (_, child) {
|
||||
return Transform.rotate(
|
||||
angle: _controller.value * 2 * math.pi,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: 200,
|
||||
height: 200,
|
||||
color: Colors.green,
|
||||
child: Center(
|
||||
child: Text('Rotating!'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Hero Animations
|
||||
|
||||
Hero animations create a visual connection between two screens, making transitions feel smooth and connected.
|
||||
|
||||
### Example: Hero Animation
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HeroAnimationExample extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Hero Animation')),
|
||||
body: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_) {
|
||||
return DetailScreen();
|
||||
}));
|
||||
},
|
||||
child: Hero(
|
||||
tag: 'imageHero',
|
||||
child: Image.network(
|
||||
'https://picsum.photos/250?image=9',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DetailScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Center(
|
||||
child: Hero(
|
||||
tag: 'imageHero',
|
||||
child: Image.network(
|
||||
'https://picsum.photos/250?image=9',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Animations are a powerful tool in Flutter for creating engaging and intuitive user interfaces. By mastering implicit animations, explicit animations, custom animations with AnimationController, and hero animations, you can create rich, interactive experiences in your Flutter applications.
|
||||
@@ -0,0 +1,267 @@
|
||||
# Flutter Navigation and Routing: A Comprehensive Guide
|
||||
|
||||
Navigation and routing are essential for creating multi-screen applications in Flutter. This guide covers both Navigator 1.0 and 2.0 approaches, as well as named routes and generated routes.
|
||||
|
||||
## 1. Navigator 1.0
|
||||
|
||||
Navigator 1.0 is the traditional way of handling navigation in Flutter.
|
||||
|
||||
### Basic Navigation
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() => runApp(MyApp());
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: FirstScreen(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FirstScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('First Screen')),
|
||||
body: Center(
|
||||
child: ElevatedButton(
|
||||
child: Text('Go to Second Screen'),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => SecondScreen()),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SecondScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Second Screen')),
|
||||
body: Center(
|
||||
child: ElevatedButton(
|
||||
child: Text('Go back'),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Named Routes
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() => runApp(MyApp());
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
initialRoute: '/',
|
||||
routes: {
|
||||
'/': (context) => HomeScreen(),
|
||||
'/details': (context) => DetailsScreen(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomeScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Home')),
|
||||
body: Center(
|
||||
child: ElevatedButton(
|
||||
child: Text('Go to Details'),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, '/details');
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DetailsScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Details')),
|
||||
body: Center(
|
||||
child: ElevatedButton(
|
||||
child: Text('Go back'),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Navigator 2.0
|
||||
|
||||
Navigator 2.0 provides a more declarative approach to navigation.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
@override
|
||||
_MyAppState createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
final RouteInformationParser<List<String>> _routeInformationParser = MyRouteInformationParser();
|
||||
final RouterDelegate<List<String>> _routerDelegate = MyRouterDelegate();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
title: 'Navigator 2.0 Demo',
|
||||
routerDelegate: _routerDelegate,
|
||||
routeInformationParser: _routeInformationParser,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyRouteInformationParser extends RouteInformationParser<List<String>> {
|
||||
@override
|
||||
Future<List<String>> parseRouteInformation(RouteInformation routeInformation) async {
|
||||
final uri = Uri.parse(routeInformation.location!);
|
||||
return uri.pathSegments;
|
||||
}
|
||||
|
||||
@override
|
||||
RouteInformation restoreRouteInformation(List<String> configuration) {
|
||||
return RouteInformation(location: '/' + configuration.join('/'));
|
||||
}
|
||||
}
|
||||
|
||||
class MyRouterDelegate extends RouterDelegate<List<String>>
|
||||
with ChangeNotifier, PopNavigatorRouterDelegateMixin<List<String>> {
|
||||
final List<String> _stack = [''];
|
||||
@override
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
List<String> get stack => List.unmodifiable(_stack);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Navigator(
|
||||
key: navigatorKey,
|
||||
pages: [
|
||||
MaterialPage(
|
||||
key: ValueKey('HomePage'),
|
||||
child: HomePage(
|
||||
onPush: (String value) {
|
||||
_stack.add(value);
|
||||
notifyListeners();
|
||||
},
|
||||
),
|
||||
),
|
||||
if (_stack.length > 1)
|
||||
MaterialPage(
|
||||
key: ValueKey('DetailsPage'),
|
||||
child: DetailsPage(
|
||||
data: _stack.last,
|
||||
onPop: () {
|
||||
_stack.removeLast();
|
||||
notifyListeners();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
onPopPage: (route, result) {
|
||||
if (!route.didPop(result)) {
|
||||
return false;
|
||||
}
|
||||
if (_stack.length > 1) {
|
||||
_stack.removeLast();
|
||||
notifyListeners();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setNewRoutePath(List<String> configuration) async {
|
||||
_stack
|
||||
..clear()
|
||||
..addAll(configuration);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
final void Function(String) onPush;
|
||||
|
||||
HomePage({required this.onPush});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Home')),
|
||||
body: Center(
|
||||
child: ElevatedButton(
|
||||
child: Text('Go to Details'),
|
||||
onPressed: () => onPush('details'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DetailsPage extends StatelessWidget {
|
||||
final String data;
|
||||
final VoidCallback onPop;
|
||||
|
||||
DetailsPage({required this.data, required this.onPop});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Details')),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Details for: $data'),
|
||||
ElevatedButton(
|
||||
child: Text('Go back'),
|
||||
onPressed: onPop,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Understanding both Navigator 1.0 and 2.0 approaches is crucial for effective navigation in Flutter applications. While Navigator 1.0 is simpler and sufficient for many apps, Navigator 2.0 offers more control and is especially useful for web applications and complex navigation scenarios.
|
||||
@@ -0,0 +1,159 @@
|
||||
# Flutter Widget Lifecycle: A Comprehensive Guide
|
||||
|
||||
Understanding the lifecycle of widgets in Flutter is crucial for managing state and resources effectively. This guide covers the lifecycle of stateful widgets and the importance of keys.
|
||||
|
||||
## 1. Stateful Widget Lifecycle
|
||||
|
||||
Stateful widgets go through several lifecycle methods. Here's the order of execution:
|
||||
|
||||
1. `createState()`
|
||||
2. `initState()`
|
||||
3. `didChangeDependencies()`
|
||||
4. `build()`
|
||||
5. `didUpdateWidget()`
|
||||
6. `setState()`
|
||||
7. `deactivate()`
|
||||
8. `dispose()`
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LifecycleWidget extends StatefulWidget {
|
||||
final String name;
|
||||
|
||||
LifecycleWidget({Key? key, required this.name}) : super(key: key);
|
||||
|
||||
@override
|
||||
_LifecycleWidgetState createState() => _LifecycleWidgetState();
|
||||
}
|
||||
|
||||
class _LifecycleWidgetState extends State<LifecycleWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
print('initState');
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
print('didChangeDependencies');
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(LifecycleWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
print('didUpdateWidget');
|
||||
}
|
||||
|
||||
@override
|
||||
void setState(VoidCallback fn) {
|
||||
super.setState(fn);
|
||||
print('setState');
|
||||
}
|
||||
|
||||
@override
|
||||
void deactivate() {
|
||||
print('deactivate');
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
print('dispose');
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
print('build');
|
||||
return Text(widget.name);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Explanation of Lifecycle Methods
|
||||
|
||||
- `createState()`: Called when the StatefulWidget is inserted into the widget tree.
|
||||
- `initState()`: Called once when the widget is inserted into the widget tree.
|
||||
- `didChangeDependencies()`: Called when a dependency of this State object changes.
|
||||
- `build()`: Called every time the widget needs to be rebuilt.
|
||||
- `didUpdateWidget()`: Called whenever the widget configuration changes.
|
||||
- `setState()`: Called when the internal state of the widget changes.
|
||||
- `deactivate()`: Called when the widget is removed from the tree.
|
||||
- `dispose()`: Called when the widget is removed permanently from the widget tree.
|
||||
|
||||
## 3. Keys in Flutter
|
||||
|
||||
Keys are used to preserve state when widgets move around in the widget tree.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class KeyExample extends StatefulWidget {
|
||||
@override
|
||||
_KeyExampleState createState() => _KeyExampleState();
|
||||
}
|
||||
|
||||
class _KeyExampleState extends State<KeyExample> {
|
||||
List<Widget> tiles = [
|
||||
StatefulColorfulTile(key: UniqueKey()),
|
||||
StatefulColorfulTile(key: UniqueKey()),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Row(children: tiles),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
child: Icon(Icons.swap_horiz),
|
||||
onPressed: swapTiles,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
swapTiles() {
|
||||
setState(() {
|
||||
tiles.insert(1, tiles.removeAt(0));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class StatefulColorfulTile extends StatefulWidget {
|
||||
StatefulColorfulTile({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_StatefulColorfulTileState createState() => _StatefulColorfulTileState();
|
||||
}
|
||||
|
||||
class _StatefulColorfulTileState extends State<StatefulColorfulTile> {
|
||||
late Color myColor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
myColor = Colors.primaries[
|
||||
DateTime.now().microsecondsSinceEpoch % Colors.primaries.length];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: myColor,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(70.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this example, using `UniqueKey()` ensures that each tile maintains its state (color) even when their positions are swapped.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Understanding the widget lifecycle and proper use of keys is essential for managing state and optimizing performance in Flutter applications. By leveraging these concepts, you can create more efficient and predictable widget behaviors.
|
||||
@@ -0,0 +1,141 @@
|
||||
# Functional Programming Concepts in Dart: A Comprehensive Guide
|
||||
|
||||
While Dart is primarily an object-oriented language, it also supports many functional programming concepts. This guide covers key functional programming features in Dart.
|
||||
|
||||
## 1. Higher-Order Functions
|
||||
|
||||
Higher-order functions are functions that can take other functions as parameters or return functions.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
var numbers = [1, 2, 3, 4, 5];
|
||||
|
||||
// Using a higher-order function (map)
|
||||
var doubled = numbers.map((n) => n * 2);
|
||||
print(doubled); // (2, 4, 6, 8, 10)
|
||||
|
||||
// Creating a higher-order function
|
||||
Function multiplyBy(int factor) {
|
||||
return (int number) => number * factor;
|
||||
}
|
||||
|
||||
var tripler = multiplyBy(3);
|
||||
print(tripler(4)); // 12
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Closures
|
||||
|
||||
Closures are functions that have access to variables in their lexical scope, even when the function is used outside of its original scope.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
Function counter() {
|
||||
int count = 0;
|
||||
return () {
|
||||
count++;
|
||||
return count;
|
||||
};
|
||||
}
|
||||
|
||||
void main() {
|
||||
var increment = counter();
|
||||
print(increment()); // 1
|
||||
print(increment()); // 2
|
||||
print(increment()); // 3
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Pure Functions
|
||||
|
||||
Pure functions always produce the same output for the same input and have no side effects.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
// Pure function
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
// Impure function (has side effect)
|
||||
int currentCount = 0;
|
||||
int incrementAndAdd(int value) {
|
||||
currentCount++;
|
||||
return currentCount + value;
|
||||
}
|
||||
|
||||
void main() {
|
||||
print(add(3, 4)); // Always 7
|
||||
print(incrementAndAdd(3)); // 4
|
||||
print(incrementAndAdd(3)); // 5
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Immutability
|
||||
|
||||
Immutability involves working with unchangeable data. Dart supports this through `final` and `const` keywords.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
final list = [1, 2, 3];
|
||||
// list = [4, 5, 6]; // Error: Can't assign to final variable
|
||||
list.add(4); // OK, but modifies the list
|
||||
|
||||
const constList = [1, 2, 3];
|
||||
// constList.add(4); // Error: Can't modify a const list
|
||||
|
||||
// Creating a new list instead of modifying
|
||||
final newList = [...list, 5];
|
||||
print(newList); // [1, 2, 3, 4, 5]
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Recursion
|
||||
|
||||
Recursion is a technique where a function calls itself to solve a problem.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
int factorial(int n) {
|
||||
if (n <= 1) return 1;
|
||||
return n * factorial(n - 1);
|
||||
}
|
||||
|
||||
void main() {
|
||||
print(factorial(5)); // 120
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Function Composition
|
||||
|
||||
Function composition involves creating a new function by combining other functions.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
Function compose(Function f, Function g) {
|
||||
return (x) => f(g(x));
|
||||
}
|
||||
|
||||
int square(int x) => x * x;
|
||||
int addOne(int x) => x + 1;
|
||||
|
||||
void main() {
|
||||
var squareThenAddOne = compose(addOne, square);
|
||||
var addOneThenSquare = compose(square, addOne);
|
||||
|
||||
print(squareThenAddOne(3)); // 10
|
||||
print(addOneThenSquare(3)); // 16
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
While Dart is not a purely functional language, it provides many features that support functional programming paradigms. Understanding and using these concepts can lead to more concise, maintainable, and testable code.
|
||||
@@ -0,0 +1,104 @@
|
||||
# Generics in Dart: A Comprehensive Guide
|
||||
|
||||
Generics allow you to write flexible, reusable code that can work with different types while maintaining type safety.
|
||||
|
||||
## 1. Generic Classes
|
||||
|
||||
You can create classes that work with different types by using type parameters.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class Box<T> {
|
||||
T contents;
|
||||
|
||||
Box(this.contents);
|
||||
|
||||
T getContents() {
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
var intBox = Box<int>(42);
|
||||
var stringBox = Box<String>("Hello");
|
||||
|
||||
print(intBox.getContents()); // Prints 42
|
||||
print(stringBox.getContents()); // Prints Hello
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Generic Methods
|
||||
|
||||
Methods can also use generics to work with different types.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
T first<T>(List<T> list) {
|
||||
if (list.isEmpty) {
|
||||
throw StateError("List is empty");
|
||||
}
|
||||
return list[0];
|
||||
}
|
||||
|
||||
void main() {
|
||||
var numbers = [1, 2, 3];
|
||||
var strings = ["a", "b", "c"];
|
||||
|
||||
print(first(numbers)); // Prints 1
|
||||
print(first(strings)); // Prints a
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Type Bounds
|
||||
|
||||
You can restrict the types that can be used with a generic by using type bounds.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class NumberBox<T extends num> {
|
||||
T value;
|
||||
|
||||
NumberBox(this.value);
|
||||
|
||||
void square() {
|
||||
print(value * value);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
var intBox = NumberBox<int>(5);
|
||||
var doubleBox = NumberBox<double>(2.5);
|
||||
|
||||
intBox.square(); // Prints 25
|
||||
doubleBox.square(); // Prints 6.25
|
||||
|
||||
// var stringBox = NumberBox<String>("Hello"); // Compile-time error
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Generic Collections
|
||||
|
||||
Dart's collection types are all generic.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
List<int> numbers = [1, 2, 3];
|
||||
Map<String, int> ages = {"Alice": 30, "Bob": 25};
|
||||
Set<double> scores = {9.5, 8.7, 9.2};
|
||||
|
||||
numbers.add(4); // OK
|
||||
// numbers.add("5"); // Compile-time error
|
||||
|
||||
print(ages["Alice"]); // Prints 30
|
||||
print(scores.contains(9.2)); // Prints true
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Generics in Dart provide a powerful way to write reusable, type-safe code. They are extensively used in Dart's core libraries and are a fundamental concept for writing efficient and flexible Dart and Flutter applications.
|
||||
@@ -0,0 +1,98 @@
|
||||
# Mixins in Dart: A Comprehensive Guide
|
||||
|
||||
Mixins are a way of reusing a class's code in multiple class hierarchies. They allow you to add features to a class without inheritance.
|
||||
|
||||
## 1. Creating a Mixin
|
||||
|
||||
To create a mixin, use the `mixin` keyword followed by the mixin name.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
mixin Logger {
|
||||
void log(String message) {
|
||||
print('Log: $message');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Using a Mixin
|
||||
|
||||
To use a mixin, use the `with` keyword followed by the mixin name.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class User with Logger {
|
||||
String name;
|
||||
|
||||
User(this.name);
|
||||
|
||||
void greet() {
|
||||
log('Hello, $name!');
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
var user = User('Alice');
|
||||
user.greet(); // Prints: Log: Hello, Alice!
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Multiple Mixins
|
||||
|
||||
A class can use multiple mixins.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
mixin Swimmer {
|
||||
void swim() => print('Swimming');
|
||||
}
|
||||
|
||||
mixin Flyer {
|
||||
void fly() => print('Flying');
|
||||
}
|
||||
|
||||
class Duck with Swimmer, Flyer {}
|
||||
|
||||
void main() {
|
||||
var duck = Duck();
|
||||
duck.swim(); // Prints: Swimming
|
||||
duck.fly(); // Prints: Flying
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Mixin Inheritance
|
||||
|
||||
Mixins can inherit from other mixins using the `on` keyword.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
mixin MusicalPerformer on Musician {
|
||||
void perform() {
|
||||
playInstrument();
|
||||
sing();
|
||||
}
|
||||
}
|
||||
|
||||
class Musician {
|
||||
void playInstrument() => print('Playing instrument');
|
||||
void sing() => print('Singing');
|
||||
}
|
||||
|
||||
class Singer extends Musician with MusicalPerformer {}
|
||||
|
||||
void main() {
|
||||
var singer = Singer();
|
||||
singer.perform();
|
||||
// Prints:
|
||||
// Playing instrument
|
||||
// Singing
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Mixins provide a powerful way to reuse code across different class hierarchies. They are particularly useful for adding behavior to classes without the complexity of multiple inheritance.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Null Safety in Dart: A Comprehensive Guide
|
||||
|
||||
Null safety is a feature in Dart that helps prevent null reference exceptions, making your code more robust and less prone to runtime errors.
|
||||
|
||||
## 1. Nullable and Non-nullable Types
|
||||
|
||||
In Dart with null safety, types are non-nullable by default. To make a type nullable, add a question mark (?) after the type.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
String nonNullableString = "Hello"; // Can't be null
|
||||
String? nullableString = null; // Can be null
|
||||
|
||||
int nonNullableInt = 42; // Can't be null
|
||||
int? nullableInt = null; // Can be null
|
||||
```
|
||||
|
||||
## 2. The `?`, `!`, and `??` Operators
|
||||
|
||||
- `?`: Null-aware operator
|
||||
- `!`: Null assertion operator
|
||||
- `??`: Null-coalescing operator
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
String? nullableString = null;
|
||||
|
||||
// Null-aware operator
|
||||
print(nullableString?.length); // Prints null
|
||||
|
||||
// Null assertion operator (use with caution!)
|
||||
// print(nullableString!.length); // Throws exception if nullableString is null
|
||||
|
||||
// Null-coalescing operator
|
||||
String nonNullString = nullableString ?? "Default";
|
||||
print(nonNullString); // Prints "Default"
|
||||
```
|
||||
|
||||
## 3. Late Variables
|
||||
|
||||
The `late` keyword allows you to declare a non-nullable variable that's initialized after its declaration.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class Person {
|
||||
late String name;
|
||||
|
||||
void setName(String newName) {
|
||||
name = newName;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
final person = Person();
|
||||
person.setName("Alice");
|
||||
print(person.name); // Prints "Alice"
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Required Named Parameters
|
||||
|
||||
Use the `required` keyword to make named parameters non-nullable and required.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
void printPersonInfo({required String name, required int age}) {
|
||||
print("Name: $name, Age: $age");
|
||||
}
|
||||
|
||||
void main() {
|
||||
printPersonInfo(name: "Bob", age: 30); // OK
|
||||
// printPersonInfo(name: "Charlie"); // Compile-time error: missing required parameter 'age'
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Null safety helps catch null-related errors at compile-time rather than runtime, leading to more reliable code. Practice using these features to write more robust Dart and Flutter applications.
|
||||
@@ -0,0 +1,159 @@
|
||||
# State Management in Flutter: A Comprehensive Guide
|
||||
|
||||
State management is a crucial aspect of Flutter development. It involves managing the data and UI state of your application efficiently. This guide covers some popular state management approaches in Flutter.
|
||||
|
||||
## 1. Provider
|
||||
|
||||
Provider is a wrapper around InheritedWidget to make them easier to use and more reusable.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class CounterModel extends ChangeNotifier {
|
||||
int _count = 0;
|
||||
int get count => _count;
|
||||
|
||||
void increment() {
|
||||
_count++;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => CounterModel(),
|
||||
child: MyApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: Text('Provider Example')),
|
||||
body: Center(
|
||||
child: Consumer<CounterModel>(
|
||||
builder: (context, counter, child) => Text(
|
||||
'Count: ${counter.count}',
|
||||
style: TextStyle(fontSize: 24),
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => Provider.of<CounterModel>(context, listen: false).increment(),
|
||||
child: Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Riverpod
|
||||
|
||||
Riverpod is a complete rewrite of Provider to make it more type-safe and able to catch programming errors at compile time.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter());
|
||||
|
||||
class Counter extends StateNotifier<int> {
|
||||
Counter() : super(0);
|
||||
void increment() => state++;
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(ProviderScope(child: MyApp()));
|
||||
}
|
||||
|
||||
class MyApp extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final count = ref.watch(counterProvider);
|
||||
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: Text('Riverpod Example')),
|
||||
body: Center(
|
||||
child: Text(
|
||||
'Count: $count',
|
||||
style: TextStyle(fontSize: 24),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => ref.read(counterProvider.notifier).increment(),
|
||||
child: Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. BLoC (Business Logic Component)
|
||||
|
||||
BLoC separates the business logic from the UI, making the code more testable and reusable.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
// Events
|
||||
abstract class CounterEvent {}
|
||||
class IncrementEvent extends CounterEvent {}
|
||||
|
||||
// BLoC
|
||||
class CounterBloc extends Bloc<CounterEvent, int> {
|
||||
CounterBloc() : super(0) {
|
||||
on<IncrementEvent>((event, emit) => emit(state + 1));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(BlocProvider(
|
||||
create: (context) => CounterBloc(),
|
||||
child: MyApp(),
|
||||
));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: Text('BLoC Example')),
|
||||
body: Center(
|
||||
child: BlocBuilder<CounterBloc, int>(
|
||||
builder: (context, count) {
|
||||
return Text(
|
||||
'Count: $count',
|
||||
style: TextStyle(fontSize: 24),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => context.read<CounterBloc>().add(IncrementEvent()),
|
||||
child: Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Choosing the right state management solution depends on your app's complexity and your team's preferences. Each approach has its strengths, and understanding these different methods will help you make informed decisions in your Flutter projects.
|
||||
@@ -0,0 +1,192 @@
|
||||
# Class Constructors in Dart and Flutter: A Comprehensive Guide
|
||||
|
||||
Constructors are special methods used to create and initialize objects of a class. Dart offers several types of constructors to cater to different initialization needs. This guide will cover the main types of constructors in Dart and their usage.
|
||||
|
||||
## 1. Default Constructor
|
||||
|
||||
If you don't declare a constructor, Dart provides a default (no-argument) constructor.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class Point {
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
}
|
||||
|
||||
void main() {
|
||||
var point = Point(); // Uses the default constructor
|
||||
print('${point.x}, ${point.y}'); // Output: 0, 0
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Named Constructor
|
||||
|
||||
You can define multiple constructors for a class using named constructors.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class Point {
|
||||
double x, y;
|
||||
|
||||
Point(this.x, this.y);
|
||||
|
||||
// Named constructor
|
||||
Point.origin() {
|
||||
x = 0;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
// Another named constructor
|
||||
Point.fromJson(Map<String, double> json) {
|
||||
x = json['x']!;
|
||||
y = json['y']!;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
var p1 = Point(2, 3);
|
||||
var p2 = Point.origin();
|
||||
var p3 = Point.fromJson({'x': 1, 'y': 2});
|
||||
|
||||
print('p1: ${p1.x}, ${p1.y}'); // Output: p1: 2, 3
|
||||
print('p2: ${p2.x}, ${p2.y}'); // Output: p2: 0, 0
|
||||
print('p3: ${p3.x}, ${p3.y}'); // Output: p3: 1, 2
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Parameterized Constructor
|
||||
|
||||
This is the most common type of constructor, which accepts parameters to initialize the object's properties.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class Person {
|
||||
String name;
|
||||
int age;
|
||||
|
||||
Person(this.name, this.age);
|
||||
}
|
||||
|
||||
void main() {
|
||||
var person = Person('Alice', 30);
|
||||
print('${person.name} is ${person.age} years old');
|
||||
// Output: Alice is 30 years old
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Optional Parameters Constructor
|
||||
|
||||
Constructors can have optional parameters, either positional or named.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class Rectangle {
|
||||
double width;
|
||||
double height;
|
||||
|
||||
// Constructor with optional named parameters
|
||||
Rectangle({this.width = 0, this.height = 0});
|
||||
|
||||
// Constructor with optional positional parameter
|
||||
// Rectangle([this.width = 0, this.height = 0]);
|
||||
}
|
||||
|
||||
void main() {
|
||||
var rect1 = Rectangle(width: 10, height: 20);
|
||||
var rect2 = Rectangle(width: 15);
|
||||
var rect3 = Rectangle();
|
||||
|
||||
print('rect1: ${rect1.width} x ${rect1.height}'); // Output: rect1: 10 x 20
|
||||
print('rect2: ${rect2.width} x ${rect2.height}'); // Output: rect2: 15 x 0
|
||||
print('rect3: ${rect3.width} x ${rect3.height}'); // Output: rect3: 0 x 0
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Initializer List
|
||||
|
||||
You can initialize instance variables before the constructor body runs using an initializer list.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class Point {
|
||||
final double x;
|
||||
final double y;
|
||||
final double distanceFromOrigin;
|
||||
|
||||
Point(double x, double y)
|
||||
: x = x,
|
||||
y = y,
|
||||
distanceFromOrigin = sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
void main() {
|
||||
var point = Point(3, 4);
|
||||
print('Distance from origin: ${point.distanceFromOrigin}');
|
||||
// Output: Distance from origin: 5.0
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Factory Constructor
|
||||
|
||||
Factory constructors can return an instance that might not be a new instance of the class.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class Logger {
|
||||
final String name;
|
||||
static final Map<String, Logger> _cache = <String, Logger>{};
|
||||
|
||||
factory Logger(String name) {
|
||||
return _cache.putIfAbsent(name, () => Logger._internal(name));
|
||||
}
|
||||
|
||||
Logger._internal(this.name);
|
||||
|
||||
void log(String msg) {
|
||||
print('$name: $msg');
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
var logger1 = Logger('UI');
|
||||
var logger2 = Logger('UI');
|
||||
print(identical(logger1, logger2)); // Output: true
|
||||
|
||||
logger1.log('Button clicked');
|
||||
logger2.log('Page loaded');
|
||||
// Output:
|
||||
// UI: Button clicked
|
||||
// UI: Page loaded
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Const Constructor
|
||||
|
||||
For classes whose objects never change, you can define a const constructor.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class ImmutablePoint {
|
||||
final int x;
|
||||
final int y;
|
||||
|
||||
const ImmutablePoint(this.x, this.y);
|
||||
}
|
||||
|
||||
void main() {
|
||||
var p1 = const ImmutablePoint(1, 2);
|
||||
var p2 = const ImmutablePoint(1, 2);
|
||||
print(identical(p1, p2)); // Output: true
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Understanding these different types of constructors in Dart and Flutter allows you to create flexible and efficient class initializations. Each type serves a specific purpose, from simple object creation to complex initialization scenarios. Practice using these constructors in your Dart and Flutter projects to become proficient in object-oriented programming with these languages.
|
||||
@@ -0,0 +1,181 @@
|
||||
# Object-Oriented Programming Concepts in Dart and Flutter
|
||||
|
||||
## 1. Abstraction
|
||||
|
||||
Abstraction is the process of hiding complex implementation details and showing only the essential features of an object.
|
||||
|
||||
### Key Points:
|
||||
- Abstraction focuses on what an object does rather than how it does it.
|
||||
- In Dart, abstraction is achieved using abstract classes and interfaces.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
// Abstract class
|
||||
abstract class Shape {
|
||||
double calculateArea();
|
||||
double calculatePerimeter();
|
||||
}
|
||||
|
||||
// Concrete implementation
|
||||
class Circle extends Shape {
|
||||
double radius;
|
||||
|
||||
Circle(this.radius);
|
||||
|
||||
@override
|
||||
double calculateArea() {
|
||||
return 3.14 * radius * radius;
|
||||
}
|
||||
|
||||
@override
|
||||
double calculatePerimeter() {
|
||||
return 2 * 3.14 * radius;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
Shape circle = Circle(5);
|
||||
print(circle.calculateArea()); // Output: 78.5
|
||||
```
|
||||
|
||||
In this example, `Shape` is an abstract class that defines a common interface for all shapes. The `Circle` class provides concrete implementations of the abstract methods.
|
||||
|
||||
## 2. Encapsulation
|
||||
|
||||
Encapsulation is the bundling of data and the methods that operate on that data within a single unit (class). It restricts direct access to some of an object's components.
|
||||
|
||||
### Key Points:
|
||||
- In Dart, encapsulation is achieved using private variables and methods.
|
||||
- Private members are denoted by prefixing an underscore (_) to the identifier.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class BankAccount {
|
||||
String _accountNumber;
|
||||
double _balance;
|
||||
|
||||
BankAccount(this._accountNumber, this._balance);
|
||||
|
||||
double getBalance() {
|
||||
return _balance;
|
||||
}
|
||||
|
||||
void deposit(double amount) {
|
||||
if (amount > 0) {
|
||||
_balance += amount;
|
||||
}
|
||||
}
|
||||
|
||||
bool withdraw(double amount) {
|
||||
if (amount > 0 && _balance >= amount) {
|
||||
_balance -= amount;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
var account = BankAccount('123456', 1000);
|
||||
account.deposit(500);
|
||||
print(account.getBalance()); // Output: 1500
|
||||
```
|
||||
|
||||
In this example, `_accountNumber` and `_balance` are private variables. They can only be accessed and modified through public methods like `getBalance()`, `deposit()`, and `withdraw()`.
|
||||
|
||||
## 3. Inheritance
|
||||
|
||||
Inheritance is a mechanism where a new class is derived from an existing class, inheriting its properties and methods.
|
||||
|
||||
### Key Points:
|
||||
- Dart supports single inheritance, where a class can only inherit from one superclass.
|
||||
- The `extends` keyword is used to create a child class.
|
||||
- The `super` keyword is used to refer to the parent class.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
class Animal {
|
||||
void breathe() {
|
||||
print('Breathing...');
|
||||
}
|
||||
}
|
||||
|
||||
class Mammal extends Animal {
|
||||
void walk() {
|
||||
print('Walking...');
|
||||
}
|
||||
}
|
||||
|
||||
class Dog extends Mammal {
|
||||
void bark() {
|
||||
print('Woof!');
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
var dog = Dog();
|
||||
dog.breathe(); // Inherited from Animal
|
||||
dog.walk(); // Inherited from Mammal
|
||||
dog.bark(); // Dog's own method
|
||||
```
|
||||
|
||||
In this example, `Dog` inherits from `Mammal`, which in turn inherits from `Animal`. This creates a hierarchy where `Dog` has access to all methods from its parent classes.
|
||||
|
||||
## 4. Polymorphism
|
||||
|
||||
Polymorphism allows objects of different classes to be treated as objects of a common superclass. It enables a single interface to represent different underlying forms (data types).
|
||||
|
||||
### Key Points:
|
||||
- In Dart, polymorphism is achieved through method overriding and interfaces.
|
||||
- The `@override` annotation is used to indicate that a method is intended to override a superclass method.
|
||||
|
||||
### Example:
|
||||
|
||||
```dart
|
||||
abstract class Shape {
|
||||
double calculateArea();
|
||||
}
|
||||
|
||||
class Rectangle extends Shape {
|
||||
double width;
|
||||
double height;
|
||||
|
||||
Rectangle(this.width, this.height);
|
||||
|
||||
@override
|
||||
double calculateArea() {
|
||||
return width * height;
|
||||
}
|
||||
}
|
||||
|
||||
class Circle extends Shape {
|
||||
double radius;
|
||||
|
||||
Circle(this.radius);
|
||||
|
||||
@override
|
||||
double calculateArea() {
|
||||
return 3.14 * radius * radius;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
void printArea(Shape shape) {
|
||||
print('Area: ${shape.calculateArea()}');
|
||||
}
|
||||
|
||||
var rectangle = Rectangle(5, 3);
|
||||
var circle = Circle(2);
|
||||
|
||||
printArea(rectangle); // Output: Area: 15.0
|
||||
printArea(circle); // Output: Area: 12.56
|
||||
```
|
||||
|
||||
In this example, both `Rectangle` and `Circle` are treated as `Shape` objects. The `printArea()` function demonstrates polymorphism by working with any `Shape` object, regardless of its specific type.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Understanding these four pillars of object-oriented programming is crucial for effective Dart and Flutter development. They provide a solid foundation for creating modular, maintainable, and scalable applications. Practice implementing these concepts in your projects to reinforce your understanding and improve your coding skills.
|
||||
Reference in New Issue
Block a user