Reputation: 650
I am following a tutorial that uses the following code:
final selectedVideoProvider = StateProvider<Video?>((ref) => null);
final miniPlayerControllerProvider =
StateProvider.autoDispose<MiniplayerController>(
(ref) => MiniplayerController(),
);
class NavScreen extends StatefulWidget {
@override
_NavScreenState createState() => _NavScreenState();
}
class _NavScreenState extends State<NavScreen> {
static const double _playerMinHeight = 60.0;
int _selectedIndex = 0;
final _screens = [
HomeScreen(),
const Scaffold(body: Center(child: Text('Explore'))),
const Scaffold(body: Center(child: Text('Add'))),
const Scaffold(body: Center(child: Text('Subscriptions'))),
const Scaffold(body: Center(child: Text('Library'))),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer(
builder: (context, watch, _) {
final selectedVideo = watch(selectedVideoProvider).state;
final miniPlayerController =
watch(miniPlayerControllerProvider).state;
return Stack(
children: _screens
.asMap()
.map((i, screen) => MapEntry(
i,
Offstage(
offstage: _selectedIndex != i,
child: screen,
),
))
.values
.toList()
..add(
Offstage(
offstage: selectedVideo == null,
child: Miniplayer(
controller: miniPlayerController,
minHeight: _playerMinHeight,
maxHeight: MediaQuery.of(context).size.height,
builder: (height, percentage) {
if (selectedVideo == null)
return const SizedBox.shrink();
if (height <= _playerMinHeight + 50.0)
return Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: Column(
children: [
Row(
children: [
Image.network(
selectedVideo.thumbnailUrl,
height: _playerMinHeight - 4.0,
width: 120.0,
fit: BoxFit.cover,
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
selectedVideo.title,
overflow:
TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.caption!
.copyWith(
color: Colors.white,
fontWeight:
FontWeight.w500,
),
),
),
Flexible(
child: Text(
selectedVideo.author.username,
overflow:
TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.caption!
.copyWith(
fontWeight:
FontWeight.w500),
),
),
],
),
),
),
IconButton(
icon: const Icon(Icons.play_arrow),
onPressed: () {},
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () {
context
.read(selectedVideoProvider)
.state = null;
},
),
],
),
const LinearProgressIndicator(
value: 0.4,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.red,
),
),
],
),
);
return VideoScreen();
},
),
),
),
);
},
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _selectedIndex,
onTap: (i) => setState(() => _selectedIndex = i),
selectedFontSize: 10.0,
unselectedFontSize: 10.0,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home_outlined),
activeIcon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.explore_outlined),
activeIcon: Icon(Icons.explore),
label: 'Explore',
),
BottomNavigationBarItem(
icon: Icon(Icons.add_circle_outline),
activeIcon: Icon(Icons.add_circle),
label: 'Add',
),
BottomNavigationBarItem(
icon: Icon(Icons.subscriptions_outlined),
activeIcon: Icon(Icons.subscriptions),
label: 'Subscriptions',
),
BottomNavigationBarItem(
icon: Icon(Icons.video_library_outlined),
activeIcon: Icon(Icons.video_library),
label: 'Library',
),
],
),
);
}
}
I can not understand the behavior of the offstage
widget in the following section of the code:
screens
.asMap()
.map((i, screen) => MapEntry(
i,
Offstage(
offstage: _selectedIndex != i,
child: screen,
),
))
.values
.toList()
..add(
Offstage(
offstage: selectedVideo == null,
child: Miniplayer(
There is also not enough information/examples about MapEntry
widget. Why should we use it and what does it exactly do?
It's a pleasure if someone could explain the behavior of the part I specified and also the whole widget in general.
Upvotes: 3
Views: 530
Reputation: 23277
An Offstage
is a widget that doesn't show itself when the value in the offstage
parameter is true. It looks like this code uses it to only show the selected screen. and hide the other screens. It would have been better to not use an Offstage
here and just put the desired screen there in my opinion.
A MapEntry
is not a widget. It's simply an entry in a map. Consider this map
var map = {
"key1" : "value1",
"key2" : "value2",
"key3" : "value3",
};
then you could this to add an entry to it.
map.addEntries([MapEntry("key4", "value4")]);
Your code turns the list of screens into a map. Then change the values of the map to wrap them with an Offstage
and then turns it into a list again. This is such a unnecessary way to do it. In my opinion is that tutorial of pretty bad quality.
This part:
_screens
.asMap()
.map((i, screen) => MapEntry(
i,
Offstage(
offstage: _selectedIndex != i,
child: screen,
),
))
.values
.toList()
would be identical to
_screens.map((screen) => Offstage(
offstage: _selectedIndex != i,
child: screen,
),
))
.toList()
And could be improved even further by not using Offstage
at all. I think it could simply be replaced with
[_screens[_selectedIndex]]
EDIT:
I just realized that the turning to map is done so you have access to the index i
. Still that is totally unnecessary I believe if you do it like [_screens[_selectedIndex]]
Upvotes: 2