Flutter: Navigation Architecture with BottomBar Menu - flutter

I still don't have full grasp of the build and navigation process in Flutter and was wondering on how to design the main navigation architecture of a more complex app with many pages.
In my case, I have a HomePage with a Scaffold, a Bottom Bar for Navigation and 5 items in it.
class _HomePageState extends State<HomePage> {
int _selectedIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(...),
body: [Page1(), Page2(), Page3(), Page4(), Page5()].elementAt(_selectedIndex),
bottomNavigationBar: BottomAppBar(
child: Row(
children: [
IconButton(
icon: Icon(
FeatherIcons.home,
size: 27,
color: (_selectedIndex == 0)
? Colors.yellow[600]
: Colors.grey[800],
),
onPressed: () {
setState(() {
_selectedIndex = 0;
});
},
),
// four more Icon Buttons ....
]
),
),
);
and on Page2() for example I have two tabs on the page, and I used the pattern:
Scaffold(
...
body: (_index = 0) ? TabPage1() : TabPage2(),
)
where I have two IconButtons in the AppBar.title to change the index.
Are these bad practices for Navigation? Should I use a PageView for the 5 main pages in the HomePage or what is the best way to handle the Navigation?

Related

Flutter GNav disappears when using Navigator.pushNamed()

I implemented an app that uses GNav from google_nav_bar.dart. Most things work fine and I really like it, i can manually switch pages by clicking on the tab icons of the navbar. But when I am trying to change the page in the code (e.g. Buttonpress or after the User did a certain action) with Navigator.pushNamed the navbar disappears. I know that this is normal bcs why should the navbar stay, but I have no idea how to manage this. There are not that many examples on the internet. Would be pretty nice if someone could help me!
Explanation of the app: the User scans a barcode of a book, and after the scan he gets redirected to the app page, where the user can find reviews to that book. And i want the navbar on that reviews page as well.
Here the simplified code where the navigation happens, the different screens just return a Scaffold, they are not implemented yet:
class MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
}
int _selectedIndex = 0;
static final List<Widget> _pages = <Widget>[
const ScannerScreen(),
const AllReviewsScreen(),
];
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('ReviewApp'),
),
body: _pages.elementAt(_selectedIndex),
bottomNavigationBar: Container(
color: Colors.black,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 0),
child: GNav(
gap: 8,
backgroundColor: Colors.black,
color: Colors.white,
activeColor: Colors.white,
tabBackgroundColor: Colors.grey.shade800,
padding: const EdgeInsets.all(12),
tabs: const [
GButton(
icon: Icons.qr_code_scanner,
text: 'Scanner',
),
GButton(
icon: Icons.reviews,
text: 'Alle Reviews',
),
],
selectedIndex: _selectedIndex,
onTabChange: (index) {
setState(() {
_selectedIndex = index;
});
},
),
),
),
),
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/scanner':
return FadeRoute(const ScannerScreen());
case '/all_reviews':
return FadeRoute(const AllReviewsScreen());
default:
return null;
}
});
}
}
I tried to use a tabController, but GoogleNavBar has no controller, so I think it handles this another way, but I hardly found stuff on the internet, in every example they only switch screen using the navbar. Of course i could just implement the navbar on every screen, but there must be an easier way
It is obvious behavior because when you call Navigator. push it immediately pushes the UI to a new screen where your bottomAppBar is not available.
Here is an article about what you want:
Multiple Navigators with BottomNavigationBar

Flutter: Inflate drawer in webview

I am using a Drawer in a Scaffold menu. When I make the window in the web-view smaller, the Drawer vanishes and a small button appears.
which opens the menu from the left.
I would like to keep the drawer in this deflated version for full window size, but I get a static menu. How do I change that?
Code:
Scaffold(
drawer: DrawerMenu(),
...
)
class DrawerMenu extends StatelessWidget {
const DrawerMenu({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: [
Container(
padding: EdgeInsets.all(appPadding),
child: Image.asset("assets/images/mylogo.png"),
),
DrawerListTile(
title: 'Something',
svgSrc: 'assets/icons/Dashboard.svg',
tap: () {Navigator.pop(context);}),
DrawerListTile(
title: 'Somethingelse',
svgSrc: 'assets/icons/BlogPost.svg',
tap: () {Navigator.pop(context);}),
],
),
);
}
}`
The usage of drawer property in Scaffold is to show & hide the drawer.
To make the drawer always visible in full window size, try create as a seperate widget based on width of the screen
#override
Widget build(BuildContext context) {
var isBig = MediaQuery.of(context).size.width > 600.0; // set width threshold
return Scaffold(
appBar: ...
drawer: isBig ? null : DrawerMenu(),
body: isBig
? Row(
children: [
DrawerMenu(),
Expanded(
child: bodyContent,
),
],
)
: bodyContent;
);

Flutter GetX with bottom navigation bar not update the widget

actually, i have fixed this problem. but it is weird implementation. maybe there are better solution for this situation.
so, i have list of screen that will show based of bottom navigation index
List<Widget> screenList = [
HomeScreen(),
KategoriScreen(),
PesananScreen(),
AccountScreen()
];
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Obx(() {
var user = Get.find<AccountController>().user.value; // <= weird Solution to trigger update
return screenList.elementAt(_mIndex);
}),
bottomNavigationBar: _bottomNavWidget(),
);
}
one of that Screen have Obx() in the widget. but when data change. the screen not updated. we need to change the screen index then return to that screen to update the widget. so, my current solution is add Obx() in Scaffold that handle the bottom navigation bar logic with useless var (var user). any better solution?
UPDATE
Sorry for the misunderstanding. my problem is when account controller is updated. the screen not updating. need to change other screen and going back. another solution is use Obx() in parent Widget. but the user value is not used
class ScreenController extends GetxController {
var tabIndex = 0;
void changeTabIndex(int index) {
tabIndex = index;
update();
}
}
This was apply to widget Screen page where you can use indexstack
GetBuilder<DashboardController>(
builder: (controller) {
return Scaffold(
body: SafeArea(
child: IndexedStack(
index: controller.tabIndex,
children: [
Home(),
HotDeals(),
ProfilePage(),
],
),
),
bottomNavigationBar: BottomNavigationBar(
unselectedItemColor: Colors.black,
selectedItemColor: Colors.redAccent,
onTap: controller.changeTabIndex,
currentIndex: controller.tabIndex,
showSelectedLabels: true,
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.white,
elevation: 0,
items: [
_bottomNavigationBarItem(
icon: CupertinoIcons.home,
label: 'Home',
),
_bottomNavigationBarItem(
icon: CupertinoIcons.photo,
label: 'Hotdeals',
),
_bottomNavigationBarItem(
icon: CupertinoIcons.person,
label: 'Profile',
),
],
),
);
},
);
This is the principle of state management. You need to set the state or notify the listeners in order to refresh the UI.
Here, only using Obx is not enough if it is not a controller that you are watching.
I would recommend that you create a class out of your screen list
class ScreenController extends GetxController{
int _selectedIndex = 0.obs;
List<Widget> _screenList = [
HomeScreen(),
KategoriScreen(),
PesananScreen(),
AccountScreen()
];
selectIndex(int index) => selectedIndex = index;
getScreen() => _screenList[_selectedIndex];
}
Then you can simply encapsulate your _selectedIndex inside your Obx widget, that will trigger the refresh when another button uses the selectIndex method.
(You need to put the ScreenController first, as usual)

How to place a Progress bar over or inside an App Bar in Flutter?

Does anyone have any idea whether or not I can include a progress bar within an app bar? See screenshot of what I am trying to achieve. I am trying to reuse code and if I can create an App Bar with a Progress Indicator and then the same without. This will enable the users to complete this screen on signup and then EDIT this screen without the progress bar once they have an account. Is this even possible?
Your best option here is to use the 'bottom' property of the appBar widget it would look something like this
class HomeScreen extends StatelessWidget {
// this is to hide the progress inidecator once the account is created
bool isSignUpComplete = false;
#override
Widget build(BuildContext context) {
// this is to retrieve the device screen size
final Size size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
bottom: !isSignUpComplete ? PreferredSize(
child: LinearProgressIndicator(
backgroundColor: Colors.red,
),
preferredSize: Size(size.width, 0),
) : null,
),
);
}
}
if you want to make a progress indicator in the app bar you can use the progress indicator in the title of the app bar but :
if you want to make it such as the image I will tell you how you can make a custom app bar below the progress indicator like this
class Myapplication extends StatelessWidget {
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
body: SafeArea(
child: Column(
children: [
LinearProgressIndicator(),
Row(
children: [
Icon(Icons.arrow_back_outlined, color: Colors.black),
],
),
],
),
),
);
}
}
I solved this by putting the progress bar in the body, extended the body behind the app bar and then made the app bar transparent.
class SignUpForm extends StatelessWidget {
double progress = 0.22;
#override
Widget build(BuildContext context) => Scaffold(
backgroundColor: Colors.white,
extendBodyBehindAppBar: true,
appBar: BasicFormAppBar(),
body: Column(
children: [
SizedBox(height: 45),
Container(
child: LinearProgressIndicator(
value: progress,
valueColor: AlwaysStoppedAnimation(Colors.Blue),
backgroundColor: Colors.Grey,
),
), //column & scaffold are not closed in this snippet

Refresh widget or page in Flutter without ListView et al

I want refresh my page without having a scrollable content, i.e. without having a ListView et al.
When I want use RefreshIndicator, the documentation says it needs a scrollable widget like ListView.
But if I want to refresh and want to use the refresh animation of RefreshIndicator without using a ListView, GridView or any other scorllable widget, how can i do that?
You can simply wrap your content in a SingleChildScrollView, which will allow you to use a RefreshIndicator. In order to make the pull down to refresh interaction work, you will have to use AlwaysScrollableScrollPhysics as your content will most likely not cover more space than available without a scroll view:
RefreshIndicator(
onRefresh: () async {
// Handle refresh.
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: /* your content */,
),
);
You can just use GestureDetector, I have created a sample for you, but it's not perfect, you can customize it to your own needs, it just detects when you swipe from the top.
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
var refresh=false;
void refreshData(){
if(!refresh){
refresh=true;
print("Refreshing");
Future.delayed(Duration(seconds: 4),(){
refresh =false;
print("Refreshed");
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text("Test"),
centerTitle: true,
),
body: GestureDetector(
child: Container(
color: Colors.yellow,
height: double.infinity,
width: double.infinity,
child: Center(child: Text('TURN LIGHTS ON')),
),
onVerticalDragUpdate: (DragUpdateDetails details){
print("direction ${details.globalPosition.direction}");
print("distance ${details.globalPosition.distance}");
print("dy ${details.globalPosition.dy}");
if(details.globalPosition.direction < 1 && (details.globalPosition.dy >200 && details.globalPosition.dy < 250)){
refreshData();
}
},
));
}
}