Flutter: Widget Fundamentals

Widgets are Flutter's building blocks — understand Widgets and you've grasped Flutter's DNA.

📋 Prerequisites: You need to have completed the following first

1. What You Will Learn


2. A Real Story from a Frontend Developer Transitioning to Flutter

(1) The Pain: The DOM Mindset Trap

Bob transitioned from React to Flutter and instinctively tried to understand Widgets through a "component renders DOM" mental model. He found that product cards were frequently rebuilt during list scrolling, causing a 1000-item product page to lag at 30fps. Even more confusing, adding key to the list made performance worse — because he was using UniqueKey.

(2) The Three-Tree Architecture Solution

Flutter is not a simple "component → DOM" mapping, but a "Widget → Element → RenderObject" three-layer architecture. Widgets are configuration descriptions (lightweight), Elements manage instances (diff core), and RenderObjects handle layout and painting (heavyweight). Understanding the three trees, Bob realized he should optimize at the Element layer via diffing, rather than rebuilding at the Widget layer.

DART
import 'package:flutter/material.dart';

// Lightweight widget: just configuration
const ProductCard({required this.product, super.key});

// Flutter internally creates Element and RenderObject
// Widget rebuilds are cheap - Element diffing avoids RenderObject rebuild
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

(3) The Result: Smooth 60fps Scrolling

After switching to ValueKey(product.id), Element diff precisely matched items, and the 1000-product list maintained a stable 60fps.


3. Widget-Element-RenderObject Three Trees

100%
graph LR
    W[Widget Tree] --> E[Element Tree]
    E --> R[RenderObject Tree]
    W -.->|canUpdate| E
    E -.->|adoptChild| R
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

(1) Three-Tree Responsibilities

Layer Created By Responsibility Weight
Widget Developer's build() Immutable configuration description Very light
Element Framework automatically Manage tree structure, diff updates Medium
RenderObject Framework automatically Measure, layout, paint Heavy

(2) canUpdate Rule

Element determines whether to reuse via Widget.canUpdate(oldWidget, newWidget):

DART
import 'package:flutter/material.dart';

// canUpdate logic
static bool canUpdate(Widget oldWidget, Widget newWidget) {
  return oldWidget.runtimeType == newWidget.runtimeType
      && oldWidget.key == newWidget.key;
}
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.
Scenario runtimeType key Result
Same type, same key Same Same Reuse Element
Same type, different key Same Different Rebuild Element
Different type Different - Rebuild Element

4. Common Basic Widgets

(1) Text and Styles

▶ Example

TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

: Text with various styles

DART
import 'package:flutter/material.dart';

// Basic text
const Text('ShopApp')

// Styled text
Text(
  'Flash Sale!',
  style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: Colors.red,
    letterSpacing: 1.5,
  ),
)

// Rich text with multiple styles
Text.rich(
  TextSpan(
    text: 'Price: ',
    style: const TextStyle(fontSize: 16, color: Colors.grey),
    children: [
      TextSpan(
        text: '\$99.99',
        style: const TextStyle(
          fontSize: 20,
          fontWeight: FontWeight.bold,
          color: Colors.green,
        ),
      ),
      TextSpan(
        text: ' \$149.99',
        style: TextStyle(
          fontSize: 14,
          decoration: TextDecoration.lineThrough,
          color: Colors.grey[400],
        ),
      ),
    ],
  ),
)
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

(2) Container: The Universal Container

Container Property Purpose Type
padding Inner padding EdgeInsets
margin Outer margin EdgeInsets
decoration Background decoration BoxDecoration
constraints Constraints BoxConstraints
alignment Child widget alignment Alignment

▶ Example

TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

: Container product card

DART
import 'package:flutter/material.dart';

Container(
  margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  padding: const EdgeInsets.all(12),
  decoration: BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.circular(12),
    boxShadow: [
      BoxShadow(
        color: Colors.black.withOpacity(0.1),
        blurRadius: 8,
        offset: const Offset(0, 2),
      ),
    ],
  ),
  child: Row(
    children: [
      ClipRRect(
        borderRadius: BorderRadius.circular(8),
        child: Image.network(
          'https://cdn.shopapp.com/products/laptop.jpg',
          width: 80,
          height: 80,
          fit: BoxFit.cover,
        ),
      ),
      const SizedBox(width: 12),
      Expanded(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('Pro Laptop', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
            const SizedBox(height: 4),
            Text('\$1,299.99', style: const TextStyle(fontSize: 18, color: Colors.green)),
          ],
        ),
      ),
    ],
  ),
)
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

(3) Icon and IconButton

▶ Example

TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

: Icons in an e-commerce context

DART
import 'package:flutter/material.dart';

// Icon with color and size
const Icon(Icons.shopping_cart, color: Colors.blue, size: 32)

// IconButton for actions
IconButton(
  icon: const Icon(Icons.favorite_border),
  onPressed: () {
    // Add to wishlist
  },
  tooltip: 'Add to Wishlist',
)

// Badge icon for cart count
Stack(
  children: [
    const Icon(Icons.shopping_cart, size: 28),
    Positioned(
      right: 0,
      top: 0,
      child: Container(
        padding: const EdgeInsets.all(2),
        decoration: const BoxDecoration(color: Colors.red, shape: BoxShape.circle),
        constraints: const BoxConstraints(minWidth: 16, minHeight: 16),
        child: const Text('3', style: TextStyle(fontSize: 10, color: Colors.white),
          textAlign: TextAlign.center),
      ),
    ),
  ],
)
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

5. Key Mechanism

Keys control Element reuse strategy, which is especially critical during list updates.

(1) Key Types

Key Type Comparison Basis Use Case
ValueKey(value) Value equality List items with unique identifiers (e.g., product.id)
ObjectKey(object) Object reference Unique object instances
UniqueKey() Different every time Force no reuse (rarely used)
GlobalKey Globally unique Cross-widget State access

▶ Example

TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

: Key's role in lists

DART
import 'package:flutter/material.dart';

// Simplified class definition
class Product {
  final int id;
  final String name;
  final double price;
  const Product({required this.id, required this.name, required this.price});
}

// BAD: No key, Flutter matches by position
ListView(
  children: [
    ProductTile(product: products[0]), // index 0
    ProductTile(product: products[1]), // index 1
  ],
)

// When products reorder, Element mismatches State
// Solution: Use ValueKey with unique id

// GOOD: ValueKey enables correct Element-Widget matching
ListView(
  children: [
    ProductTile(key: ValueKey(products[0].id), product: products[0]),
    ProductTile(key: ValueKey(products[1].id), product: products[1]),
  ],
)
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

6. Complete Example: ShopApp Product Card Widget

DART
import 'package:flutter/material.dart';

class Product {
  final int id;
  final String name;
  final double price;
  final String imageUrl;
  final double rating;
  final int reviewCount;

  const Product({
    required this.id,
    required this.name,
    required this.price,
    required this.imageUrl,
    this.rating = 0.0,
    this.reviewCount = 0,
  });
}

class ProductCard extends StatelessWidget {
  final Product product;
  final VoidCallback? onAddToCart;
  final VoidCallback? onTap;

  const ProductCard({
    super.key,
    required this.product,
    this.onAddToCart,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Card(
        clipBehavior: Clip.antiAlias,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
        elevation: 2,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
              flex: 3,
              child: Stack(
                children: [
                  Image.network(product.imageUrl, fit: BoxFit.cover,
                    width: double.infinity),
                  if (product.price < 50)
                    Positioned(top: 8, left: 8,
                      child: _buildBadge('SALE', Colors.red)),
                ],
              ),
            ),
            Expanded(
              flex: 2,
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(product.name, maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                      style: const TextStyle(fontWeight: FontWeight.w600)),
                    const SizedBox(height: 4),
                    Text('\$${product.price.toStringAsFixed(2)}',
                      style: const TextStyle(color: Colors.green, fontSize: 16)),
                    const Spacer(),
                    Align(
                      alignment: Alignment.centerRight,
                      child: IconButton(
                        icon: const Icon(Icons.add_shopping_cart, size: 20),
                        onPressed: onAddToCart,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildBadge(String text, Color color) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
      decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(4)),
      child: Text(text, style: const TextStyle(color: Colors.white, fontSize: 10)),
    );
  }
}
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

❓ FAQ

Q When is a StatelessWidget's build method called?
A When a parent widget rebuilds, or when setState triggers a parent rebuild. A StatelessWidget itself cannot trigger a rebuild.
Q Why are Widgets designed to be immutable?
A Immutability means they can be created frequently without worrying about side effects. The Flutter framework can safely compare old and new Widgets to decide whether to update the Element.
Q When do I need to use a Key?
A When Widgets in a list share the same type but have different data, and items may be added, removed, or reordered. Static lists usually don't need Keys.
Q GlobalKey allows cross-widget State access — doesn't this break encapsulation?
A Yes, GlobalKey is an "escape hatch" and should be used sparingly. Common use cases: Form validation, ScrollController management. Prefer passing data via callbacks.
Q What's the difference between Container and Scaffold?
A Container is a general-purpose decoration container; Scaffold is a Material Design page scaffold providing slots for AppBar/Body/FAB/Drawer/BottomNav.
Q What if Image.network fails to load?
A Use the errorBuilder parameter to show a placeholder image, combined with the cached_network_image package for caching and loading indicators.

📖 Summary


📝 Exercises

  1. Basic (difficulty ⭐): Create a UserAvatar StatelessWidget that accepts name and imageUrl, displaying a circular avatar and username.
  2. Intermediate (difficulty ⭐⭐): Implement a PriceTag widget supporting original/discount price display, with the discount price in large green text and the original price with a strikethrough.
  3. Challenge (difficulty ⭐⭐⭐): Build a sortable product list where drag-reordering uses ValueKey to ensure State follows correctly, and after sorting, display the new order via SnackBar.

← Previous | Next →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏