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?
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, didChangeDependencies()
MediaQueryFor 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 didChangeDependencies()
What Happens During Orientation Change?
State Lifecycle During Orientation Change
textOrientation 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)
dartimport '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:
textinitState 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
| Event | Android Activity | Flutter StatefulWidget |
|---|---|---|
| Orientation Change | Activity destroyed and recreated | State object persists |
| Lifecycle | text text | text text |
| Data Persistence | Must save/restore via text | Automatic (State survives) |
| Video Controller | Must save position manually | Controller persists automatically |
Why Flutter behaves differently:
- Flutter's widget tree is rebuilt, but State objects persist
- Only the method is called to adjust UI layouttext
build() - State fields (,text
_controller, etc.) remain intacttext_position - This is one of Flutter's advantages over native Android
When didChangeDependencies() Is Called
What Triggers didChangeDependencies()
dartdidChangeDependencies() 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
dartvoid 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
dartvoid 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
dartvoid 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
dartimport '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:
build()play()❌ 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 Method | Called During Orientation Change? | Can Access text | Use Case |
|---|---|---|---|
| initState() | ❌ No (only once) | ❌ No | Initialize controllers, fetch data |
| didChangeDependencies() | ✅ Yes (MediaQuery changes) | ✅ Yes | Detect orientation change, access InheritedWidgets |
| build() | ✅ Yes (rebuild UI) | ✅ Yes | Build UI based on new orientation |
| didUpdateWidget() | ❌ No (widget config didn't change) | ✅ Yes | Parent passes new widget properties |
| dispose() | ❌ No (State survives) | ⚠️ Limited | Clean up resources on permanent removal |
Summary Table
| Aspect | Details |
|---|---|
| State Persistence | ✅ State object survives orientation change |
| Video Controller | ✅ Persists automatically (no manual save/restore) |
| Lifecycle Method | text |
| Called Methods | text text |
| NOT Called | text text text |
| MediaQuery Change | ✅ Yes (triggers text |
| Manual Handling | ❌ Not needed for video position preservation |
| Best Practice | Store controller in State, use text |
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
to detect orientation changestextdidChangeDependencies()
State Persistence: Flutter's State objects survive orientation changes (unlike Android Activities)
Lifecycle:
→textdidChangeDependencies()during orientation changetextbuild()
Best Practice: Initialize controller in
, adjust UI intextinitState(), dispose intextdidChangeDependencies()textdispose()