Flutter custom navigation highlight selected page - flutter

I'm currently building a custom bottom bar for quick navigation.
I used the Navigation Service described in this article
Now I want to add highlighting based on which page the user has selected.
I tried to add RouteAware to my BottomNav widget to update the menu when the routing changed but I'm not receiving any events only when starting my app.
class _BottomNavState extends State<BottomNav> with RouteAware {
String _selectedRoute;
AppRouteObserver _routeObserver;
#override
void initState() {
super.initState();
_routeObserver = AppRouteObserver();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_routeObserver.subscribe(this, ModalRoute.of(context));
}
#override
void dispose() {
_routeObserver.unsubscribe(this);
super.dispose();
}
#override
void didPush() {
print('didPush');
}
The Route observer is a simple class:
class AppRouteObserver extends RouteObserver<PageRoute> {
factory AppRouteObserver() => _instance;
AppRouteObserver._private();
static final AppRouteObserver _instance = AppRouteObserver._private();
}
I'm guessing that it has to with me not using the Navigator.pushNamed but the direct implementation of the Navigation Service.
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(String routeName, {var content}) {
return navigatorKey.currentState.pushNamed(routeName, arguments: content);
}
bool goBack() {
return navigatorKey.currentState.pop();
}
}
The reason I created the NavigationService is because I want to show a consistent layout on every page (menubar / bottom bar / background).
Is there a better way to solve this problem?

I fixed the issue by extending the NavigationService with a ChangeNotifier.
Now when the user clicks the button I call a setSelected function and notify the menu items to redraw them self's.
setSelected(String newRoute) {
_showLeading = !checkIsHome(newRoute);
this._currentRoute = newRoute;
notifyListeners();
}

Related

Keep scroll controller open with Get.offNamed

i'm using Getx as state management
class ProductsController extends GetxController with StateMixin<Posts> {
ScrollController scrollController = ScrollController();
#override
void onInit() {
_getData();
scrollController = ScrollController()..addListener(_loadMore);
super.onInit();
}
_getData() async {}
_loadMore() async {}
#override
void onClose() {
scrollController.removeListener(_loadMore);
super.onClose();
}
}
and everything works perfectly, but for some reasons i need to navigate to same page as replacement by
Get.offNamed(Routes.products, prevent Duplicates: false);
but i note that this void
#override
void onClose() {
scrollController.removeListener(_loadMore);
super.onClose();
}
called and i lost the scroll in the bage ...
so how to keep scrollController open or how to initialize it again when navigate to the same page as replacement
or am i need to use another method to navigate to same page as replacement to it ??
thanks

Detect if current Widget/State is visible in page route?

I want to add a Listener when the widget/state is visible and remove myself from the Listener when I leave the route.
But if the user clicks on the back button and returns to the current widget/state, I want to do the same thing again.
Currently initState, didChangeDependencies, didUpdateWidget and build are NOT called when the user clicks back from the next page, therefore I cannot detect when the user is returning and the widget was loaded from cache.
After much poking around the API, I've discovered that ModalRoute and RouteObserver is what I want.
https://api.flutter.dev/flutter/widgets/ModalRoute-class.html
https://api.flutter.dev/flutter/widgets/RouteObserver-class.html
If I just want to check if the current route is active I can call isCurrent on ModalRoute.of(context):
void onNetworkData(String data) {
if (ModalRoute.of(context).isCurrent) {
setState(() => list = data);
}
}
If I want to listen to route load/unload, I just create it and serve it up the hood with Provider like this:
class HomePage extends StatelessWidget {
final spy = RouteObserver<ModalRoute<void>>();
build(BuildContext context) {
return Provider<RouteObserver<ModalRoute<void>>>.value(
value: spy,
child: MaterialApp(
navigatorObservers: [spy],
),
);
}
}
Then somewhere in another widget:
class _AboutPageState extends State<AboutPage> with RouteAware {
RouteObserver<ModalRoute<void>>? spy;
void didChangeDependencies() {
super.didChangeDependencies();
spy = context.read<RouteObserver<ModalRoute<void>>>();
spy.subscribe(this, ModelRoute.of(context)!);
}
void dispose() {
spy.unsubscribe(this);
super.dispose();
}
void didPush() => attachListeners();
void didPopNext() => attachListeners();
void didPop() => removeListeners();
void didPushNext() => removeListeners();
attachListeners() {
}
removeListeners() {
}
}

Flutter GetX call fetch data only once in the build methode

I have a method called getData() which used to load data from API,
and i am displaying data in a separate screen, I have one issue which whenever I navigate to this page it rebuild the whole screen and call the API again and again.
PS: I'm using getX Obx to control the UI
Question: how to call the function only when new data has added
class CategoryPageBinding extends Bindings {
#override
void dependencies() {
Get.put(CategoryPageController(), permanent: true);
}
}
class CategoryPageController extends GetxController {
#override
void onInit() {
super.onInit();
getAllCategories();
}
}
You can call the method in Controller using getX.
#override
void onInit() {
super.onInit();
getData();
}

How to use Provider in a particular view/screen

I am using the Flutter provider package to manage all the states and separate the business logic from the UI part, and have all the API call present in the provider class that I need to call every time the user moves to that page.
But the issue is I don't want to hold the data even when the user moves to another screen that is the case when I declare provider in main.dart.
class _HomeScreenAppState extends State<HomeScreenApp> {
bool _isLoading;
int counter = 0;
String seller, user;
#override
void initState() {
_isLoading = true;
super.initState();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_fetchHomedetails();
}
Future<void> _fetchHomedetails() async {
await Provider.of<HomeDetailProvider>(context, listen: false)
.getEarnignStatus(context);}
}
I have used ChangeNotifierProvider(create:(context) =>HomeProvider(),
builder:(context) => HomeScreen()
But if there is any dialog (bottomsheet, alertdialog) which is using HomeProvider, the dialog cannot access the HomeProvider data present on its parent widget.

How to update the state of a bottom navigation bar from NavigatorObserver?

I'm creating a bottom navigation bar and everything works fine except when the user presses the back button on their device. The state of the navigation bar doesn't update to reflect the page they're on. To fix this, I found out about NavigatorObserver. Now I can see when a route gets popped but I have no way of updating the state. My navigation bar uses routes so when a user taps a button, it'll push a new route. I'm trying to update the index of the navigation bar but I can't see a way to do so. The navbar is using a StatefulWidget so I can use the setState callback.
I've tried using keys but I can't re-use them since they're on different Scaffolds. I can't access the BuildContext from a NavigationObserver so I can't use things like Provider to notify a change and to rebuild.
class CustomBottomNavBar extends StatefulWidget {
#override
_CustomBottomNavBarState createState() => _CustomBottomNavBarState();
}
class _CustomBottomNavBarState extends State<CustomBottomNavBar> {
static int _selectedIndex;
int get selectedIndex => _selectedIndex;
set selectedIndex(int newIndex) {
setState(() {
_selectedIndex = newIndex;
});
}
//...
}
class NavBarObserver extends NavigatorObserver {
#override
void didPop(Route route, Route previousRoute) {
final navBarRoutes = Routes.all.sublist(0,4);
final routeName = route.settings.name;
if (navBarRoutes.contains(routeName)) {
final index = navBarRoutes.indexOf(routeName);
// selectedIndex = index;
}
}
}
You can do it couple ways. Here I have given an example with ValueNotifier. First you can create a enum that define different pages of you bottom navigation bar. Then you create a ValueNotifier with your enum type and share it between NavBarObserver and CustomBottomNavBar. In NavBarObserver when any tab change occurred you simply updated the ValueNotifier value with corresponding tab's enum value. You can listen ValueNotifier value change in _CustomBottomNavBarState to update the bottom navigation bar state.
enum Tabs{one, two, three}
class CustomBottomNavBar extends StatefulWidget {
final ValueNotifier<Tabs> tabsChangeNotifier;
CustomBottomNavBar(this.tabsChangeNotifier);
#override
_CustomBottomNavBarState createState() => _CustomBottomNavBarState();
}
class _CustomBottomNavBarState extends State<CustomBottomNavBar> {
Tabs _currentTab;
#override
void initState() {
super.initState();
widget.tabsChangeNotifier.addListener(() {
setState(() {
_currentTab = widget.tabsChangeNotifier.value;
});
});
}
}
class NavBarObserver extends NavigatorObserver {
final ValueNotifier<Tabs> tabsChangeNotifier;
NavBarObserver(this.tabsChangeNotifier);
#override
void didPop(Route route, Route previousRoute) {
final navBarRoutes = Routes.all.sublist(0,4);
final routeName = route.settings.name;
if (navBarRoutes.contains(routeName)) {
tabsChangeNotifier.value = Tabs.two;
// selectedIndex = index;
}
}
}