Question #431MediumFlutter BasicsImportant

In Flutter, when using a StatefulWidget, if the device orientation changes (for example, from landscape to portrait), and we want the video to continue playing from the exact same duration, which lifecycle method of the State class should be used to handle this?

#lifecycle#orientation#state-management#video-player#mediaquery

Answer

Quick Answer

When device orientation changes in Flutter, the State object persists by default, so the video position is automatically preserved if stored in the State class. However,

text
didChangeDependencies()
is called during orientation change because
text
MediaQuery
(an InheritedWidget) changes.

For video playback continuity, you don't need a special lifecycle method — just store the video controller in State and it will persist. But if you need to detect the orientation change to adjust UI or perform specific actions, use

text
didChangeDependencies()
.


What Happens During Orientation Change?

State Lifecycle During Orientation Change

text
Orientation changes (Portrait → Landscape)
    MediaQuery changes (InheritedWidget updates)
    didChangeDependencies() called  ←  Detect orientation change here
    build() called  ←  Rebuild UI with new layout
    State object persists (NOT disposed)
    Video controller persists (position maintained)

Key Point: Unlike Android's Activity recreation, Flutter's State object survives orientation changes by default.


Lifecycle Methods Called

During Orientation Change

dart
// ✅ Called during orientation change:
1. didChangeDependencies()  // MediaQuery changed
2. build()                   // UI rebuilt with new size

// ❌ NOT called during orientation change:
- initState()    // Only called once
- dispose()      // State survives orientation change
- deactivate()   // State not removed from tree

Implementation: Video Playback Continuity

Correct Approach (Video Position Auto-Preserved)

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

class VideoPlayerScreen extends StatefulWidget {
  
  _VideoPlayerScreenState createState() => _VideoPlayerScreenState();
}

class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
  late VideoPlayerController _controller;
  bool _isPortrait = true;

  
  void initState() {
    super.initState();
    // Initialize video controller once
    _controller = VideoPlayerController.network(
      'https://example.com/video.mp4',
    )
      ..initialize().then((_) {
        setState(() {});
        _controller.play();
      });

    print('initState called');
  }

  
  void didChangeDependencies() {
    super.didChangeDependencies();

    // Detect orientation change
    final orientation = MediaQuery.of(context).orientation;
    final newIsPortrait = orientation == Orientation.portrait;

    if (newIsPortrait != _isPortrait) {
      print('Orientation changed to: $orientation');
      print('Current video position: ${_controller.value.position}');
      _isPortrait = newIsPortrait;

      // Video position is automatically preserved!
      // No need to save/restore manually
    }
  }

  
  Widget build(BuildContext context) {
    print('build called');

    final orientation = MediaQuery.of(context).orientation;

    return Scaffold(
      body: Center(
        child: _controller.value.isInitialized
            ? orientation == Orientation.portrait
                ? _buildPortraitLayout()
                : _buildLandscapeLayout()
            : CircularProgressIndicator(),
      ),
    );
  }

  Widget _buildPortraitLayout() {
    return Column(
      children: [
        AspectRatio(
          aspectRatio: _controller.value.aspectRatio,
          child: VideoPlayer(_controller),
        ),
        _buildControls(),
      ],
    );
  }

  Widget _buildLandscapeLayout() {
    return AspectRatio(
      aspectRatio: _controller.value.aspectRatio,
      child: VideoPlayer(_controller),
    );
  }

  Widget _buildControls() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        IconButton(
          icon: Icon(_controller.value.isPlaying ? Icons.pause : Icons.play_arrow),
          onPressed: () {
            setState(() {
              _controller.value.isPlaying ? _controller.pause() : _controller.play();
            });
          },
        ),
        Text('Position: ${_controller.value.position}'),
      ],
    );
  }

  
  void dispose() {
    print('dispose called - permanently removing widget');
    _controller.dispose();
    super.dispose();
  }
}

Output when rotating device:

text
initState called
build called
didChangeDependencies called
build called

// Rotate device (portrait → landscape):
didChangeDependencies called
Orientation changed to: Orientation.landscape
Current video position: 0:00:15.234  ← Position preserved!
build called

// Rotate back (landscape → portrait):
didChangeDependencies called
Orientation changed to: Orientation.portrait
Current video position: 0:00:20.567  ← Still preserved!
build called

// Navigate away from screen:
dispose called - permanently removing widget

Why State Persists During Orientation Change

Flutter vs Android Comparison

EventAndroid ActivityFlutter StatefulWidget
Orientation ChangeActivity destroyed and recreatedState object persists
Lifecycle
text
onDestroy()
text
onCreate()
text
didChangeDependencies()
text
build()
Data PersistenceMust save/restore via
text
onSaveInstanceState
Automatic (State survives)
Video ControllerMust save position manuallyController persists automatically

Why Flutter behaves differently:

  • Flutter's widget tree is rebuilt, but State objects persist
  • Only the
    text
    build()
    method is called to adjust UI layout
  • State fields (
    text
    _controller
    ,
    text
    _position
    , etc.) remain intact
  • This is one of Flutter's advantages over native Android

When didChangeDependencies() Is Called

What Triggers didChangeDependencies()

dart
didChangeDependencies() is called when:

1. After initState() (always called once)
2. When InheritedWidget dependencies change:
   ├─ MediaQuery changes (orientation, size, padding)
   ├─ Theme changes (Theme.of(context))
   ├─ Locale changes (Localizations)
   └─ Any InheritedWidget ancestor updates

Example - All Triggers

dart

void didChangeDependencies() {
  super.didChangeDependencies();

  // Access inherited widgets safely
  final mediaQuery = MediaQuery.of(context);
  final theme = Theme.of(context);
  final locale = Localizations.localeOf(context);

  print('Screen size: ${mediaQuery.size}');
  print('Orientation: ${mediaQuery.orientation}');
  print('Theme brightness: ${theme.brightness}');
  print('Locale: ${locale.languageCode}');

  // This method is called whenever any of these change!
}

Advanced: Handling Specific Scenarios

Scenario 1: Pause Video in Landscape, Auto-Play in Portrait

dart

void didChangeDependencies() {
  super.didChangeDependencies();

  final orientation = MediaQuery.of(context).orientation;

  if (orientation == Orientation.portrait) {
    if (!_controller.value.isPlaying) {
      _controller.play();
    }
  } else if (orientation == Orientation.landscape) {
    if (_controller.value.isPlaying) {
      _controller.pause();
    }
  }
}

Scenario 2: Adjust Playback Speed Based on Orientation

dart

void didChangeDependencies() {
  super.didChangeDependencies();

  final orientation = MediaQuery.of(context).orientation;

  if (orientation == Orientation.landscape) {
    // Faster playback in landscape (e.g., preview mode)
    _controller.setPlaybackSpeed(1.5);
  } else {
    // Normal speed in portrait
    _controller.setPlaybackSpeed(1.0);
  }
}

Scenario 3: Enter Fullscreen in Landscape

dart
import 'package:flutter/services.dart';


void didChangeDependencies() {
  super.didChangeDependencies();

  final orientation = MediaQuery.of(context).orientation;

  if (orientation == Orientation.landscape) {
    // Enter fullscreen
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
  } else {
    // Exit fullscreen
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
  }
}


void dispose() {
  // Restore system UI when leaving screen
  SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
  _controller.dispose();
  super.dispose();
}

What NOT to Do

❌ Wrong: Reinitializing Controller in didChangeDependencies

dart
// ❌ BAD - Creates new controller on every orientation change!

void didChangeDependencies() {
  super.didChangeDependencies();

  _controller = VideoPlayerController.network('https://example.com/video.mp4');
  _controller.initialize();
  // This resets video position to 0:00!
}

Problem: Creates a new controller every time, losing video position.


❌ Wrong: Using build() for Orientation Logic

dart
// ❌ BAD - build() is called too frequently

Widget build(BuildContext context) {
  final orientation = MediaQuery.of(context).orientation;

  if (orientation == Orientation.landscape) {
    _controller.play();  // Called every rebuild!
  }

  return ...
}

Problem:

text
build()
is called multiple times per second during animations, causing repeated
text
play()
calls.


❌ Wrong: Manually Saving/Restoring Position

dart
// ❌ UNNECESSARY - State already persists!
Duration? _savedPosition;


void didChangeDependencies() {
  super.didChangeDependencies();

  // Save position
  _savedPosition = _controller.value.position;

  // Restore position
  _controller.seekTo(_savedPosition!);
}

Problem: This is unnecessary! State persists automatically in Flutter.


Lifecycle Method Comparison

When Each Method Is Called

Lifecycle MethodCalled During Orientation Change?Can Access
text
context
?
Use Case
initState()❌ No (only once)❌ NoInitialize controllers, fetch data
didChangeDependencies()✅ Yes (MediaQuery changes)✅ YesDetect orientation change, access InheritedWidgets
build()✅ Yes (rebuild UI)✅ YesBuild UI based on new orientation
didUpdateWidget()❌ No (widget config didn't change)✅ YesParent passes new widget properties
dispose()❌ No (State survives)⚠️ LimitedClean up resources on permanent removal

Summary Table

AspectDetails
State Persistence✅ State object survives orientation change
Video Controller✅ Persists automatically (no manual save/restore)
Lifecycle Method
text
didChangeDependencies()
detects orientation change
Called Methods
text
didChangeDependencies()
text
build()
NOT Called
text
initState()
,
text
dispose()
,
text
deactivate()
MediaQuery Change✅ Yes (triggers
text
didChangeDependencies()
)
Manual Handling❌ Not needed for video position preservation
Best PracticeStore controller in State, use
text
didChangeDependencies()
for UI adjustments

Best Practices

1. Store Video Controller in State

dart
Good:
class _VideoScreenState extends State<VideoScreen> {
  late VideoPlayerController _controller;  // State field persists

  
  void initState() {
    super.initState();
    _controller = VideoPlayerController.network(url);
  }
}

2. Use didChangeDependencies() for Orientation-Specific Logic

dart
Good:

void didChangeDependencies() {
  super.didChangeDependencies();

  final orientation = MediaQuery.of(context).orientation;
  if (orientation == Orientation.landscape) {
    // Enter fullscreen mode
  }
}

3. Avoid Rebuilding Controllers

dart
Bad:

void didChangeDependencies() {
  _controller = VideoPlayerController.network(url);  // Resets position!
}

Good:

void initState() {
  _controller = VideoPlayerController.network(url);  // Initialize once
}

4. Clean Up in dispose()

dart
Always dispose controllers:

void dispose() {
  _controller.dispose();
  super.dispose();
}

Key Takeaways

Video Position: Automatically preserved when stored in State (no manual save/restore needed)

Orientation Detection: Use

text
didChangeDependencies()
to detect orientation changes

State Persistence: Flutter's State objects survive orientation changes (unlike Android Activities)

Lifecycle:

text
didChangeDependencies()
text
build()
during orientation change

Best Practice: Initialize controller in

text
initState()
, adjust UI in
text
didChangeDependencies()
, dispose in
text
dispose()


Resources