Part 3: Understanding Widgets and Building UI

This entry is part 4 of 8 in the series Flutter Development Tutorial (Beginner to Intermediate)

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
  1. Use Composition Over Inheritance: Instead of extending widgets, compose them by combining smaller widgets.
  2. 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()
  1. Keep your UI logic and state management cleanly separated.
  2. 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');
Related posts<< Part 2: Dart Basics for FlutterPart 4: Navigation and Routing >>