added resources, coding challenges, and more study guides

This commit is contained in:
Hugh Ratsch
2024-10-05 16:05:26 -05:00
parent d159c2ae51
commit a35b6e5ef4
17 changed files with 1875 additions and 1 deletions
@@ -0,0 +1,82 @@
# Flutter Learning Challenge Set: Beginner to Senior
## Beginner Challenge: Task List App
Create a simple task list app that allows users to add, view, and delete tasks.
### Requirements:
1. Use a `StatefulWidget` for the main screen.
2. Implement a list to store tasks (use the `List` collection).
3. Create a text input field for adding new tasks.
4. Display tasks in a `ListView`.
5. Add a delete functionality for each task.
6. Use basic state management within the `StatefulWidget`.
### Concepts covered:
- Basic Flutter widgets
- StatefulWidget lifecycle
- Dart collections (List)
- Simple state management
## Junior Challenge: Weather Forecast App
Extend the task list app into a weather forecast app that fetches data from an API and uses more advanced Flutter concepts.
### Requirements:
1. Create a weather forecast screen that displays current weather and a 5-day forecast.
2. Implement navigation to switch between the task list and weather forecast screens.
3. Use `http` package to fetch weather data from a free API (e.g., OpenWeatherMap).
4. Implement error handling for API calls.
5. Use `Provider` for state management.
6. Create custom widgets for weather information display.
7. Add animations for loading states and transitions.
### Concepts covered:
- Flutter navigation
- Asynchronous programming (Futures)
- Error handling
- HTTP requests
- Provider for state management
- Custom widgets
- Basic animations
## Senior Challenge: Smart Home Control App
Develop a comprehensive smart home control app that incorporates advanced Flutter concepts and integrates features from the previous challenges.
### Requirements:
1. Create a dashboard that displays:
- Task list widget (from beginner challenge)
- Weather widget (from junior challenge)
- Smart device control widgets (new feature)
2. Implement user authentication (you can mock this locally).
3. Use Firebase (or a mock service) for backend data storage and real-time updates.
4. Implement advanced navigation using Navigator 2.0.
5. Create custom animations for device control interactions.
6. Use `BLoC` pattern for state management.
7. Implement unit and widget tests for critical components.
8. Add theme switching capability (light/dark mode).
9. Optimize the app for different screen sizes (responsive design).
10. Implement local notifications for task reminders and device alerts.
### Concepts covered:
- Advanced state management (BLoC)
- Navigator 2.0
- Firebase integration (or mocking advanced backend services)
- Custom animations
- Unit and widget testing
- Theme management
- Responsive design
- Local notifications
## Building on Previous Challenges:
- The task list from the beginner challenge becomes a widget in the senior challenge's dashboard.
- The weather forecast from the junior challenge is also integrated into the senior challenge's dashboard.
- The navigation and state management evolve from basic concepts in the beginner challenge to more advanced implementations in the senior challenge.
## Future Assistance Prompt:
If you need help with these challenges in the future, use the following prompt:
"I'm working on the Flutter Learning Challenge Set that was generated in a previous conversation. I'm currently on the [Beginner/Junior/Senior] challenge, specifically struggling with [describe the specific part you're stuck on]. Can you provide guidance on how to approach this problem, keeping in mind the concepts that should be demonstrated at this level of the challenge?"
This prompt will help the AI assistant understand the context of your question and provide appropriate guidance based on the challenge level and specific concepts you should be working with.
+4 -1
View File
@@ -6,4 +6,7 @@ tags: []
This is the start of my digital garden. I have high hopes that it will grow into something magical! This is the start of my digital garden. I have high hopes that it will grow into something magical!
\- [Study Notes](/posts) \- [Study Notes From Code Alongs](/posts)
\- [Study Guide](/study_guide)
\- [Resources](/resources)
\- [Coding Challenges](/coding_challenges)
@@ -0,0 +1,78 @@
# 🚀 Ultimate Flutter Learning Resources Guide
## 📚 Official Resources
### 1. Flutter's Official Documentation
The holy grail of Flutter knowledge, offering in-depth explanations of widgets, APIs, and core concepts.
🔗 [Dive into the Docs](https://docs.flutter.dev/)
### 2. Flutter's GitHub Repository
Explore the framework's source code, learn its inner workings, and find practical examples.
🐙 [Uncover the Code](https://github.com/flutter/flutter)
## 💬 Community Engagement
### 3. Flutter's Community Forums
Connect with fellow developers, share experiences, and get your questions answered.
🌐 [Join the Conversation](https://dev.to/t/flutter)
### 4. Stack Overflow
A treasure trove of Flutter-related questions and expert answers.
🔍 [Search for Solutions](https://stackoverflow.com/)
### 5. Flutter's Official Discord Server
Real-time chat, quick problem-solving, and a vibrant developer community.
🎮 [Enter the Discord](https://discord.com/invite/rflutterdev)
### 6. Flutter's Official Slack Channel
Another hub for developer interactions and knowledge sharing.
💼 [Slack Your Way In](https://fluttercommunity.dev/joinslack)
## 📺 Video Tutorials
### 7. YouTube Channels
Visual learners rejoice! These channels offer top-notch Flutter tutorials:
- 🎥 The Flutter Channel
- 🎥 Flutter Explained
- 🎥 Flutter Development
- 🎥 The Flutter Tutorials
## 📱 Stay Updated
### 8. Flutter's Official Twitter Account
Get the latest Flutter news, tips, and community highlights in your feed.
🐦 [Follow @flutterdev](https://twitter.com/flutterdev)
## 🤝 In-Person Learning
### 9. Flutter's Official Meetup Groups
Nothing beats face-to-face interactions. Find Flutter enthusiasts near you!
🗺️ [Discover Local Meetups](https://www.meetup.com/pro/flutter)
## 📖 Flutter Books
### 10. Must-Read Flutter Books
Dive deep into Flutter with these comprehensive guides:
- 📕 "Flutter in Action"
- 📗 "The Complete Flutter App Development Bootcamp"
- 📘 "Flutter: The Definitive Guide"
## 💡 Pro Tips for Learning Flutter
1. **Start with the basics**: Ensure you have a solid understanding of Dart before diving deep into Flutter.
2. **Build, build, build**: The best way to learn is by creating projects. Start small and gradually increase complexity.
3. **Engage with the community**: Don't hesitate to ask questions and share your progress.
4. **Stay updated**: Flutter evolves quickly. Keep an eye on the latest updates and best practices.
5. **Contribute to open source**: Once comfortable, consider contributing to Flutter packages or the framework itself.
Remember, learning Flutter is a journey. Enjoy the process, stay curious, and happy coding! 🎉
@@ -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.
+160
View File
@@ -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.
+152
View File
@@ -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.
+100
View File
@@ -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.
+206
View File
@@ -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.
+159
View File
@@ -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.
+104
View File
@@ -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.
+98
View File
@@ -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.
+82
View File
@@ -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.
+159
View File
@@ -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.