Instantly share code, notes, and snippets.
daiki1003/bottom_navigation_bar_with_badge.dart
Last active Dec 10, 2020
An app which has BottomNavigationBar displaying badge count also has app icon.
import 'package:flutter/material.dart'; | |
/// Add dependencies these four packages :) | |
import 'package:flutter_hooks/flutter_hooks.dart'; | |
import 'package:hooks_riverpod/hooks_riverpod.dart'; | |
import 'package:flutter_riverpod/flutter_riverpod.dart'; | |
import 'package:flutter_app_badger/flutter_app_badger.dart'; | |
/// Define providers for holding badge number. | |
final _homeBadgeProvider = StateProvider<int>( | |
(ref) => 0, | |
); | |
final _notificationBadgeProvider = StateProvider<int>( | |
(ref) => 12, | |
); | |
final _myPageBadgeProvider = StateProvider<int>( | |
(ref) => 20, | |
); | |
/// Watch all providers for app icon. | |
final _appBadgeProvider = Provider<int>( | |
(ref) { | |
final homeBadge = ref.watch(_homeBadgeProvider).state; | |
final notificationBadge = ref.watch(_notificationBadgeProvider).state; | |
final myPageBadge = ref.watch(_myPageBadgeProvider).state; | |
return homeBadge + notificationBadge + myPageBadge; | |
}, | |
); | |
void main() => runApp(ProviderScope(child: App())); | |
class App extends StatefulWidget { | |
@override | |
_AppState createState() => _AppState(); | |
} | |
class _AppState extends State<App> with WidgetsBindingObserver { | |
@override | |
Widget build(BuildContext context) => MaterialApp(home: Sample()); | |
/// for watching app lifecycle state, addObserver. | |
@override | |
void initState() { | |
super.initState(); | |
WidgetsBinding.instance.addObserver(this); | |
} | |
@override | |
void dispose() { | |
WidgetsBinding.instance.addObserver(this); | |
super.dispose(); | |
} | |
@override | |
void didChangeAppLifecycleState(AppLifecycleState state) async { | |
super.didChangeAppLifecycleState(state); | |
if (state != AppLifecycleState.inactive) { | |
return; | |
} | |
if (!await FlutterAppBadger.isAppBadgeSupported()) { | |
return; | |
} | |
FlutterAppBadger.updateBadgeCount( | |
context.read(_appBadgeProvider), | |
); | |
} | |
} | |
/// for describing tab types. | |
enum TabType { | |
home, | |
notification, | |
myPage, | |
} | |
extension TabTypeEx on TabType { | |
String get title { | |
switch (this) { | |
case TabType.home: | |
return 'ホーム'; | |
case TabType.notification: | |
return 'お知らせ'; | |
case TabType.myPage: | |
return 'マイページ'; | |
} | |
throw UnimplementedError(); | |
} | |
IconData get iconData { | |
switch (this) { | |
case TabType.home: | |
return Icons.home; | |
case TabType.notification: | |
return Icons.notifications; | |
case TabType.myPage: | |
return Icons.people; | |
} | |
throw UnimplementedError(); | |
} | |
/// choose provider for observing | |
StateProvider get provider { | |
switch (this) { | |
case TabType.home: | |
return _homeBadgeProvider; | |
case TabType.notification: | |
return _notificationBadgeProvider; | |
case TabType.myPage: | |
return _myPageBadgeProvider; | |
} | |
throw UnimplementedError(); | |
} | |
} | |
/// icon Widget for displaying in BottomNavigationBarItem | |
class TabItem extends HookWidget { | |
const TabItem( | |
this.type, { | |
@required this.selected, | |
}); | |
final TabType type; | |
final bool selected; | |
@override | |
Widget build(BuildContext context) { | |
final badgeCount = useProvider(type.provider).state; | |
final showBadge = 0 < badgeCount; | |
return Stack( | |
overflow: Overflow.visible, | |
children: [ | |
Icon(type.iconData), | |
if (showBadge) | |
Positioned( | |
top: -7, | |
right: -12, | |
child: NotificationBadge(badgeCount: badgeCount), | |
), | |
], | |
); | |
} | |
} | |
/// An widget which displays a badge number with red background. | |
class NotificationBadge extends StatelessWidget { | |
const NotificationBadge({ | |
@required this.badgeCount, | |
}); | |
final int badgeCount; | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), | |
decoration: BoxDecoration( | |
color: Color.fromRGBO(219, 73, 58, 1.0), | |
shape: BoxShape.circle, | |
), | |
child: Text( | |
badgeCount.toString(), | |
style: const TextStyle( | |
color: Colors.white, | |
fontSize: 10, | |
fontWeight: FontWeight.bold, | |
letterSpacing: 0.75, | |
), | |
), | |
); | |
} | |
} | |
/// Main widget with BottomNavigationBar. | |
class Sample extends HookWidget { | |
@override | |
Widget build(BuildContext context) { | |
final tabType = useState(TabType.home); | |
return Scaffold( | |
bottomNavigationBar: BottomNavigationBar( | |
currentIndex: tabType.value.index, | |
items: TabType.values | |
.map( | |
(tabType) => BottomNavigationBarItem( | |
icon: TabItem(tabType, selected: false), | |
activeIcon: TabItem(tabType, selected: true), | |
label: tabType.title, | |
), | |
) | |
.toList(), | |
onTap: (value) => tabType.value = TabType.values[value], | |
), | |
body: Center( | |
child: ButtonBar( | |
alignment: MainAxisAlignment.center, | |
children: [ | |
FlatButton.icon( | |
onPressed: () { | |
context.read(tabType.value.provider).state--; | |
}, | |
icon: Icon(Icons.remove), | |
label: Text(''), | |
), | |
FlatButton.icon( | |
onPressed: () { | |
context.read(tabType.value.provider).state++; | |
}, | |
icon: Icon(Icons.add), | |
label: Text(''), | |
), | |
], | |
), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment