Question #172MediumFlutter Basics

What is the use of didChangeMetrics ? this is in flutter lifecycle state

#flutter#state

Answer

Overview

text
didChangeMetrics()
is a lifecycle callback in Flutter that is called when the device metrics change, such as when the screen orientation changes, keyboard appears/disappears, or window size changes.


What is didChangeMetrics?

Part of the

text
WidgetsBindingObserver
interface, it notifies when:

  • Keyboard shows/hides
  • Screen orientation changes (portrait ↔ landscape)
  • Window size changes (resize on desktop/web)
  • Safe area insets change (status bar, notch)

Basic Usage

dart
class MyWidget extends StatefulWidget {
  
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> with WidgetsBindingObserver {
  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this); // Register observer
  }

  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this); // Unregister observer
    super.dispose();
  }

  
  void didChangeMetrics() {
    super.didChangeMetrics();
    print('Metrics changed!');

    // Access updated metrics
    final mediaQuery = MediaQuery.of(context);
    print('Size: ${mediaQuery.size}');
    print('Orientation: ${mediaQuery.orientation}');
    print('Keyboard visible: ${mediaQuery.viewInsets.bottom > 0}');
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Metrics Example')),
      body: Center(child: Text('Hello')),
    );
  }
}

Use Case 1: Detect Keyboard Visibility

dart
class KeyboardDetector extends StatefulWidget {
  
  _KeyboardDetectorState createState() => _KeyboardDetectorState();
}

class _KeyboardDetectorState extends State<KeyboardDetector>
    with WidgetsBindingObserver {
  bool _isKeyboardVisible = false;

  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  
  void didChangeMetrics() {
    super.didChangeMetrics();

    final bottomInset = View.of(context).viewInsets.bottom;
    final isKeyboardVisible = bottomInset > 0;

    if (_isKeyboardVisible != isKeyboardVisible) {
      setState(() {
        _isKeyboardVisible = isKeyboardVisible;
      });

      print(isKeyboardVisible ? 'Keyboard opened' : 'Keyboard closed');
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Keyboard Detector')),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(_isKeyboardVisible ? 'Keyboard Open' : 'Keyboard Closed'),
          TextField(
            decoration: InputDecoration(labelText: 'Type here'),
          ),
        ],
      ),
    );
  }
}

Use Case 2: Hide FloatingActionButton When Keyboard Opens

dart
class HideFABOnKeyboard extends StatefulWidget {
  
  _HideFABOnKeyboardState createState() => _HideFABOnKeyboardState();
}

class _HideFABOnKeyboardState extends State<HideFABOnKeyboard>
    with WidgetsBindingObserver {
  bool _isKeyboardVisible = false;

  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  
  void didChangeMetrics() {
    super.didChangeMetrics();

    final bottomInset = View.of(context).viewInsets.bottom;
    setState(() {
      _isKeyboardVisible = bottomInset > 0;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Hide FAB')),
      body: TextField(
        decoration: InputDecation(labelText: 'Type here'),
      ),
      floatingActionButton: _isKeyboardVisible
          ? null // Hide FAB when keyboard is visible
          : FloatingActionButton(
              onPressed: () {},
              child: Icon(Icons.add),
            ),
    );
  }
}

Use Case 3: Detect Orientation Change

dart
class OrientationDetector extends StatefulWidget {
  
  _OrientationDetectorState createState() => _OrientationDetectorState();
}

class _OrientationDetectorState extends State<OrientationDetector>
    with WidgetsBindingObserver {
  Orientation? _orientation;

  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _orientation = MediaQuery.of(context).orientation;
  }

  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  
  void didChangeMetrics() {
    super.didChangeMetrics();

    final newOrientation = MediaQuery.of(context).orientation;
    if (_orientation != newOrientation) {
      setState(() {
        _orientation = newOrientation;
      });

      print('Orientation changed to: $_orientation');
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Orientation Detector')),
      body: Center(
        child: Text(
          _orientation == Orientation.portrait ? 'Portrait' : 'Landscape',
          style: TextStyle(fontSize: 32),
        ),
      ),
    );
  }
}

Use Case 4: Adjust UI When Window Resizes (Desktop/Web)

dart
class ResizeDetector extends StatefulWidget {
  
  _ResizeDetectorState createState() => _ResizeDetectorState();
}

class _ResizeDetectorState extends State<ResizeDetector>
    with WidgetsBindingObserver {
  Size? _windowSize;

  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _windowSize = MediaQuery.of(context).size;
  }

  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  
  void didChangeMetrics() {
    super.didChangeMetrics();

    final newSize = MediaQuery.of(context).size;
    if (_windowSize != newSize) {
      setState(() {
        _windowSize = newSize;
      });

      print('Window resized to: ${newSize.width} x ${newSize.height}');
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Resize Detector')),
      body: Center(
        child: Text(
          'Window: ${_windowSize?.width.toInt()} x ${_windowSize?.height.toInt()}',
          style: TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}

Metrics Available in MediaQuery

dart

void didChangeMetrics() {
  super.didChangeMetrics();

  final mediaQuery = MediaQuery.of(context);

  print('Size: ${mediaQuery.size}');                   // Screen size
  print('Orientation: ${mediaQuery.orientation}');     // Portrait/Landscape
  print('Device pixel ratio: ${mediaQuery.devicePixelRatio}'); // DPI
  print('Padding: ${mediaQuery.padding}');             // Safe area insets
  print('View insets: ${mediaQuery.viewInsets}');      // Keyboard insets
  print('Text scale factor: ${mediaQuery.textScaleFactor}'); // Font scale
}

Other WidgetsBindingObserver Callbacks

text
didChangeMetrics()
is part of a suite of lifecycle callbacks.

CallbackTriggered When
text
didChangeMetrics()
Screen size, orientation, keyboard changes
text
didChangeAppLifecycleState()
App goes to background/foreground
text
didChangePlatformBrightness()
Dark mode toggled
text
didChangeLocales()
Device language changes
text
didChangeAccessibilityFeatures()
Accessibility settings change

Example:

dart
class MyObserver extends StatefulWidget {
  
  _MyObserverState createState() => _MyObserverState();
}

class _MyObserverState extends State<MyObserver> with WidgetsBindingObserver {
  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  
  void didChangeMetrics() {
    print('Metrics changed');
  }

  
  void didChangeAppLifecycleState(AppLifecycleState state) {
    print('App state: $state');
  }

  
  void didChangePlatformBrightness() {
    print('Brightness changed');
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Observer')),
      body: Center(child: Text('Observing...')),
    );
  }
}

Best Practices

dart
// ✅ Always register observer in initState

void initState() {
  super.initState();
  WidgetsBinding.instance.addObserver(this);
}

// ✅ Always unregister in dispose

void dispose() {
  WidgetsBinding.instance.removeObserver(this);
  super.dispose();
}

// ✅ Use setState to rebuild UI

void didChangeMetrics() {
  super.didChangeMetrics();
  setState(() {
    // Update UI
  });
}

// ❌ Don't access context before widget is built
// Use didChangeDependencies() if needed earlier

Summary

Use CaseTrigger
Keyboard visibility
text
viewInsets.bottom > 0
Orientation change
text
MediaQuery.orientation
Window resize
text
MediaQuery.size
Safe area changes
text
MediaQuery.padding

When to use:

  • ✅ Detect keyboard show/hide
  • ✅ Handle orientation changes
  • ✅ Adjust UI on window resize (desktop/web)
  • ✅ Respond to safe area changes

Learn more: WidgetsBindingObserver