- Flutter Development Tutorial (Beginner to Intermediate)
- Part 1: Introduction to Flutter and Environment Setup
- Part 2: Dart Basics for Flutter
- Part 3: Understanding Widgets and Building UI
- Part 4: Navigation and Routing
- Part 5: Working with Forms and Input Handling
- Part 6: Lists, Grids, and Dynamic Content
- Part 7: Advanced State Management
6. Widgets Overview (Deeper Dive)
Stateless Widgets vs. Stateful Widgets (More Details)
- StatelessWidget: Does not store any state (e.g., UI that remains the same).
- StatefulWidget: Stores state and re-renders when the state changes (e.g., a counter that increases on button press).
Understanding build() Method
Every widget must implement a build() method, which returns the UI.
- StatelessWidget: The build() method is only called once (or when parent widget changes).
- StatefulWidget: The build() method is called multiple times whenever setState() is called to update the UI.
Code Example of Stateless Widget:
class MyStatelessWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('I am stateless!');
}
}
Code Example of Stateful Widget:
class MyStatefulWidget extends StatefulWidget {
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int counter = 0;
void _incrementCounter() {
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Button pressed $counter times'),
ElevatedButton(
onPressed: _incrementCounter,
child: Text('Increment Counter'),
),
],
);
}
}
Note: setState(): Notifies the framework to rebuild the widget with the updated state.
7. Building a Simple User Interface (Deeper Dive)
Exploring Layout Widgets
- Column: Stacks its children vertically.
- Row: Stacks its children horizontally.
- Container: A multi-purpose widget that can contain child widgets and apply styling (e.g., padding, margins, borders).
Container(
padding: EdgeInsets.all(16.0),
margin: EdgeInsets.symmetric(vertical: 10.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.blue, width: 2.0),
),
child: Text('Container with Padding and Border'),
)
Styling Widgets
You can use the TextStyle class to style Text widgets.
Text(
'Stylish Text',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
);
Aligning Widgets
Use Center, Align, Padding, and SizedBox to control widget alignment and spacing.
Center(
child: Text('Centered Text'),
);
Button Widgets
ElevatedButton: A simple button with elevation (raised look).
ElevatedButton(
onPressed: () {
print('Button pressed');
},
child: Text('Click Me'),
);
TextButton and OutlinedButton: Flat buttons with different styles.
Best Practices for UI Building
- Use Composition Over Inheritance: Instead of extending widgets, compose them by combining smaller widgets.
- Minimize Nesting: Deeply nested widgets can be hard to manage. Use helper methods or Flexible/Expanded to organize layout.
State Management Basics (Deeper Dive)
Managing State with setState()
In Flutter, state is any data that can change. For simple state management, setState() is often sufficient.
When to Use setState()
Use setState() when:
- You have local state that only affects the widget itself (e.g., a counter, form inputs).
class CounterApp extends StatefulWidget {
@override
_CounterAppState createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
int counter = 0;
void increment() {
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Counter: $counter'),
),
floatingActionButton: FloatingActionButton(
onPressed: increment,
child: Icon(Icons.add),
),
);
}
}
Best Practices for setState()
- Keep your UI logic and state management cleanly separated.
- Avoid calling setState() in build methods or frequently called functions.
8. Navigation and Routing in Flutter
In Flutter, navigation between screens is handled by the Navigator widget and routes. There are two primary methods for navigation: named routes and direct route navigation.
8.1 Direct Route Navigation
You can navigate to a new screen by pushing a new route directly to the stack.
Example: Pushing a New Route
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NewScreen()),
);
This code pushes NewScreen onto the stack, and the back button automatically pops the current screen, returning to the previous one.
Code Example: Two Screens Navigation
// First Screen
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('First Screen')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
child: Text('Go to Second Screen'),
),
),
);
}
}
// Second Screen
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Second Screen')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go Back'),
),
),
);
}
}
Navigator.pop()
When you want to go back to the previous screen, use Navigator.pop().
8.2 Named Route Navigation
For more complex apps, named routes allow cleaner management of multiple screens.
Step 1: Define Routes in MaterialApp
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
);
Step 2: Navigate Using Route Names
Navigator.pushNamed(context, '/second');