How to Navigate to parent of widget in flutter - flutter

I want to navigate to the second bottom item on a page in flutter, from other pages.
The parent here is BottomNavigationBar that I want to configure to change the page (I define a key for it in Provider to access it from other pages).
List<Widget> pages = new List<Widget>();
BottomNavigationBar bottomNavigationBar;
updateList(){
pages.addAll({
Dashboard(),
AllServices(),
Account(),
Charge(),
});
}
int _selectedIndex = 0;
Widget _bottomNavigationBar(int selectedIndex,key) {
return BottomNavigationBar(
key: key,
onTap: (int index) => setState(() => _selectedIndex = index),
currentIndex: selectedIndex,
type: BottomNavigationBarType.fixed,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: new Icon(Icons.home),
title: new Text("A page"),
),
BottomNavigationBarItem(
icon: new Icon(Icons.location_on),
title: new Text("B page")
),
BottomNavigationBarItem(
icon: new Icon(Icons.store),
title: new Text("C page")
),
BottomNavigationBarItem(
icon: new Icon(Icons.person),
title:new Text("D page")
),
],
);
}
#override
void initState() {
// TODO: implement initState
super.initState();
updateList();
}
#override
Widget build(BuildContext context) {
final globalKeyForBottomNavigate = Provider.of<Controller>(context, listen: false).bottomNavigate;
return Scaffold(
bottomNavigationBar: _bottomNavigationBar(_selectedIndex,globalKeyForBottomNavigate),
body: pages[_selectedIndex],
);
}
And children are Page A, B, C, D. now from page A I Navigate to Page F. Then if an action is triggered, I want to navigate to page B, but my solution is to change the index of BottomNavigationBar in page F and then navigate to page A. It works, but does not directly navigate to the content of page B and the BottomNavigationBar. In my solution, I say ok first change index of BottomNavigationBar, then go to last page (that here is Page A), but as you see, I do not know how to directly navigate to page B. This is the code for page F.
onTap: (){
var bottomKey=Provider.of<Controller>(context, listen: false).bottomNavigate;
final BottomNavigationBar navigationBar = bottomKey.currentWidget;
navigationBar.onTap(1);
Navigator.pop(context);
},

You can try using an environment variable in the app. and based on the action change the value of environment variable. Secondly bind the value of selectedIndex with that environment variable, so whenever that variable change, it will change the value of selectedIndex and call the dashboard page again with new value of the index it will navigate you to the required page from the bottom bar.

Related

How to show alert dialog box if page is switched in bottom navigation bar in flutter

Currently I am working on flutter project in which I have used bottom navigation bar and in one of the pages I have a form in which information needs to fill. In this case if the user switches to another page after filling information and does not submit it, I want to show dialog box to user that "You have information filled leaving this page will discard the info". Please help.
I have tried WillPopScope widget but it only works when you press back button I want to show dialog box when the text fields are filled but not submitted and without submitting user tries to switch to another page.
In the onTap callback, check if the Navbar Item index is not equal to the Item which contains the form. If it is different, show the dialog and await its response.
void _onItemTapped(int index) async {
if(_selectedIndex == formItemIndex && index != formItemIndex)
{
// await showDialog();
}
setState(() {
_selectedIndex = index;
});
}
BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
)

Flutter - getx controller not updated when data changed

I am developing an app that has a bottomnavitaionbar with five pages. I use getx. In first page, i am listing data. My problem is that, when i changed data(first page in bottomnavigationbar) manually from database and thn i pass over pages, came back to first page i could not see changes.
Controller;
class ExploreController extends GetxController {
var isLoading = true.obs;
var articleList = List<ExploreModel>().obs;
#override
void onInit() {
fetchArticles();
super.onInit();
}
void fetchArticles() async {
try {
isLoading(true);
var articles = await ApiService.fetchArticles();
if (articles != null) {
//articleList.clear();
articleList.assignAll(articles);
}
} finally {
isLoading(false);
}
update();
}
}
and my UI;
body: SafeArea(
child: Column(
children: <Widget>[
Header(),
Expanded(
child: GetX<ExploreController>(builder: (exploreController) {
if (exploreController.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),
);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {
thanks to #Baker for the right answer. However, if you have a list and in viewModel and want to update that list, just use the list.refresh() when the list updated
RxList<Models> myList = <Models>[].obs;
when add or insert data act like this:
myList.add(newItem);
myList.refresh();
GetX doesn't know / can't see when database data has changed / been updated.
You need to tell GetX to rebuild when appropriate.
If you use GetX observables with GetX or Obx widgets, then you just assign a new value to your observable field. Rebuilds will happen when the obs value changes.
If you use GetX with GetBuilder<MyController>, then you need to call update() method inside MyController, to rebuild GetBuilder<MyController> widgets.
The solution below uses a GetX Controller (i.e. TabX) to:
hold application state:
list of all tabs (tabPages)
which Tab is active (selectedIndex)
expose a method to change the active/visible tab (onItemTapped())
OnItemTapped()
This method is inside TabX, the GetXController.
When called, it will:
set which tab is visible
save the viewed tab to the database (FakeDB)
rebuild any GetBuilder widgets using update()
void onItemTapped(int index) {
selectedIndex = index;
db.insertViewedPage(index); // simulate database update while tabs change
update(); // ← rebuilds any GetBuilder<TabX> widget
}
Complete Example
Copy/paste this entire code into a dart page in your app to see a working BottomNavigationBar page.
This tabbed / BottomNavigationBar example is taken from
https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html
but edited to use GetX.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyTabHomePage(),
);
}
}
class FakeDB {
List<int> viewedPages = [0];
void insertViewedPage(int page) {
viewedPages.add(page);
}
}
/// BottomNavigationBar page converted to GetX. Original StatefulWidget version:
/// https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html
class TabX extends GetxController {
TabX({this.db});
final FakeDB db;
int selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
List<Widget> tabPages;
#override
void onInit() {
super.onInit();
tabPages = <Widget>[
ListViewTab(db),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
}
/// INTERESTING PART HERE ↓ ************************************
void onItemTapped(int index) {
selectedIndex = index;
db.insertViewedPage(index); // simulate database update while tabs change
update(); // ← rebuilds any GetBuilder<TabX> widget
// ↑ update() is like setState() to anything inside a GetBuilder using *this*
// controller, i.e. GetBuilder<TabX>
// Other GetX controllers are not affected. e.g. GetBuilder<BlahX>, not affected
// by this update()
// Use async/await above if data writes are slow & must complete before updating widget.
// This example does not.
}
}
/// REBUILT when Tab Page changes, rebuilt by GetBuilder in MyTabHomePage
class ListViewTab extends StatelessWidget {
final FakeDB db;
ListViewTab(this.db);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: db.viewedPages.length,
itemBuilder: (context, index) =>
ListTile(
title: Text('Page Viewed: ${db.viewedPages[index]}'),
),
);
}
}
class MyTabHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
Get.put(TabX(db: FakeDB()));
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
/// ↓ Tab Page currently visible - rebuilt by GetBuilder when
/// ↓ TabX.onItemTapped() called
child: GetBuilder<TabX>(
builder: (tx) => tx.tabPages.elementAt(tx.selectedIndex)
),
),
/// ↓ BottomNavBar's highlighted/active item, rebuilt by GetBuilder when
/// ↓ TabX.onItemTapped() called
bottomNavigationBar: GetBuilder<TabX>(
builder: (tx) => BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: tx.selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: tx.onItemTapped,
),
),
);
}
}
You don't need GetBuilder here, as its not meant for observable variables. Nor do you need to call update() in the fetchArticles function as that's only for use with GetBuilder and non observable variables.
So you had 2 widgets meant to update UI (GetBuilder and Obx) both following the same controller and all you need is just the OBX. So Rahuls answer works, or you can leave the Obx in place, get rid of of the GetBuilder and declare and initialize a controller in the beginning of your build method.
final exploreController = Get.put(ExploreController());
Then use that initialized controller in your OBX widget as the child of your Expanded.
Obx(() => exploreController.isLoading.value
? Center(
child:
SpinKitChasingDots(color: Colors.deepPurple[600], size: 40),
)
: ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {},
),
)
GetX< ExploreController >(builder: (controller) {
if (controller.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: controller.articleList.length,
separatorBuilder: (BuildContext context, int index) {});
});
If you change the value in the database 'manually', you need a STREAM to listen to the change on the database.
You can't do:
var articles = await ApiService.fetchArticles();
You need to do something like this:
var articles = await ApiService.listenToArticlesSnapshot();
The way you explained is like if you need the data to refresh after navigating to another page and clicking on a button, then navigating to first page (GetBuilder) OR automatically adds data from the within the first page (Obx). But your case is simple, just retrieve the articles SNAPSHOT, then in the controller onInit, subscribe to the snapshot with the bindStream method, and eventually use the function ever() to react to any change in the observable articleList.
Something like this:
create
final exploreController = Get.put(ExploreController());
Add
init: ExploreController();
body: SafeArea(
child: Column(
children: <Widget>[
Header(),
Expanded(
child: GetX<ExploreController>(builder: (exploreController) {
*** here ***
init: ExploreController();
if (exploreController.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),
);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {
using GetxBuilder approch on ui side and where you want update simple called built in function update();
The simplest way I could.
In the controller create an obs (var indexClick = 1.obs;)
On each Tile test the selected==index...;
On the click of each item change the indexClick sequentially
return Obx(() {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
ListTile(
leading: const Icon(Icons.dns),
title: const Text('Menu1'),
selected: controller.indexClick.value==1?true:false,
onTap: () {
controller.indexClick.value=1;
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.search),
title: const Text('Menu2'),
selected: controller.indexClick.value==2?true:false,
onTap: () {
controller.indexClick.value=2;
Navigator.pop(context);
},
),

Flutter - call Navigator inside switch which it is inside builder

I want to navigate to QrScan screen once the icons get pressed, instead, I got an error!!
setState() or markNeedsBuild() called during build
I want to navigate to that screen and get data from QR Codes, after that I want this data to be shown on another screen!
It says:
This Overlay widget cannot be marked as needing to build because the framework is already in the process of building widgets.
A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building.
This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built.
Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
Overlay- [LabeledGlobalKey#a5a46]
The widget which was currently being built when the offending call wasmade was: builder
class MainTabsScreen extends StatefulWidget {
#override
_MainTabsScreenState createState() => _MainTabsScreenState();
}
class _MainTabsScreenState extends State<MainTabsScreen> {
int page = 3;
void _openScanner() {
Navigator.push(context, MaterialPageRoute(builder: (context) => QrScan()));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Builder(
builder: (context) {
switch (page) {
case 0:
return ExploreScreen();
case 1:
return OffersScreen();
case 2:
_openScanner();
break;
case 3:
return AltersScreen();
case 4:
return ChatsScreen();
default:
return ExploreScreen();
}
},
),
),
bottomNavigationBar: ConvexAppBar(
top: -20.0,
backgroundColor: Colors.white,
activeColor: Color(0xBB0BCC83),
color: Color(0xBB0BCC83),
height: 53.0,
elevation: 0.0,
initialActiveIndex: 3,
items: [
TabItem(
icon: Icons.home,
title: 'Home',
),
TabItem(
icon: Icons.list,
title: 'Offers',
),
TabItem(
icon: Icons.qr_code,
title: 'Scan',
),
TabItem(
icon: Icons.add_alert,
title: 'Notification',
),
TabItem(
icon: Icons.chat,
title: 'Chats',
),
],
onTap: (id) {
setState(() => page = id);
},
),
);
}
}
As discussed in comments, a solution was to call the navigator.push when id == 2 within the onTap function.

bottom navigation bar not switching tabs in flutter

I want to use fancy bottom navigation in flutter. When i switch between tabs, it is showing the tab switching only at the navigation bar but, The body is not switching. It is not showing another tabs as i switch.
Here's my code
return Scaffold(
body: _getPage(currentPage),
bottomNavigationBar: FancyBottomNavigation(
key: bottomNavigationKey,
tabs: [
TabData(iconData: Icons.home,title: 'Home'),
TabData(iconData: Icons.search, title: 'Search'),
TabData(iconData: Icons.person, title: 'Profile'),
],
onTabChangedListener: (position){
setState(() {
currentPage = position;
final FancyBottomNavigationState fState =
bottomNavigationKey.currentState;
fState.setPage(position);
print('currentPage = $currentPage');
});
},
)
);
_getPage(int page){
switch(page) {
case 0:
return Page1();
case 1:
return Search();
case 2:
return Profile();
}
}
You don't need the GlobalKey here. It is enough to just set the currentPage value in the setState.
The Dev Nathan Withers writes here that the key prop defaults to null and is only used for Programmatic Selection of tabs. This special use case is only relevant if you want to change tabs by not clicking on the actual buttons. You don't need this feature. You can check out the example at line 48 to 50 for an actual use-case for the key prop.
Also examine if an IndexedStack suits you. It saves the pages state. You're _getPage() method destroys and builds each page new on each switch.
I put everything together in this example:
class _HomePageState extends State<HomePage> {
int _currentPage = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
top: false,
child: IndexedStack(
index: _currentPage,
children: [StartView(), AllServicesView()],
),
),
bottomNavigationBar: FancyBottomNavigation(
tabs: [
TabData(iconData: Icons.home,title: 'Home'),
TabData(iconData: Icons.search, title: 'Search'),
],
onTabChangedListener: (position){
setState(() {
_currentPage = position;
print('currentPage = $_currentPage');
});
},
)
);
}

How to get to instance in callback

I am trying to toggle selected in a list of ListTile in a Drawer?
ListTile(
title: Text("Name"),
leading: Icon(Icons.dashboard),
onTap: () {
currentSelected.selected = false
this.selected = true;
currentSelected = this; // << How to get the instance of ListTile
},
),
this points to the widget that contains the code in your question.
You can create a variable where you assign the ListTile, then you can reference it in onTap.
ListTile listTile;
listTile = ListTile(
title: Text("Name"),
leading: Icon(Icons.dashboard),
onTap: () {
currentSelected.selected = false
this.selected = true;
currentSelected = listTile
},
),
return listTile;
It would be better to use a value to store the selected item, like an itemId, instead of a widget reference.