Flutter Web Navigation - Change container content based on named Route - flutter

I have been searching for tutorial for Flutter Web with Navigation Menu (Side, bottom or top) and named routes. Basically, I want to have the Menu and the MenuContent Container. I created a HomePage Container to display both Menu and MenuContainer.
I want to change the MenuContent content based on the named routes. I used the onGenerateRoute of the Material Page that calls the HomePage. It works, but it refreshes the entire HomePage when I change the route. Is there a way to just change the widget content of my MenuContent rather than loading the entire HomePage. Below is the sample code
main.dart
import 'package:edar_app/cubit/categories/categories_cubit.dart';
import 'package:edar_app/presentation/pages/category/category_bloc_provider.dart';
import 'package:edar_app/presentation/pages/home/home_page.dart';
import 'package:edar_app/presentation/pages/sales/sales_form.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
onGenerateRoute: (settings) {
print("on generate route.. ${settings.name}");
if (settings.name == "sales") {
return MaterialPageRoute(
builder: (context) => HomePage(wChild: SalesForm()));
} else if (settings.name == "/categories") {
print('categories is selected');
return MaterialPageRoute(
builder: (context) => HomePage(wChild: CategoryPage());
}
},
onUnknownRoute: (settings) => MaterialPageRoute(
builder: (context) => HomePage(wChild: CategoryPage())),
initialRoute: "/",
home: HomePage(wChild: CategoryPage()),
);
}
}
home_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:side_navigation/side_navigation.dart';
import '../../../cubit/categories/categories_cubit.dart';
import '../../../data/network/network_service.dart';
import '../../../data/repository/category_repository.dart';
import '../sales/sales_form.dart';
class HomePage extends StatefulWidget {
final Widget wChild;
HomePage({required this.wChild});
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int selectedIndex = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Row(
children: [
SideNavigationBar(
theme: SideNavigationBarTheme(
backgroundColor: Colors.green.shade900,
togglerTheme: SideNavigationBarTogglerTheme.standard(),
itemTheme: const SideNavigationBarItemTheme(
unselectedItemColor: Colors.white,
),
dividerTheme: SideNavigationBarDividerTheme.standard(),
),
selectedIndex: selectedIndex,
items: const [
SideNavigationBarItem(
icon: Icons.dashboard,
label: 'Dashboard',
),
SideNavigationBarItem(
icon: Icons.point_of_sale,
label: 'Sales',
),
SideNavigationBarItem(
icon: Icons.category,
label: 'Category',
),
],
onTap: (index) {
setState(() {
selectedIndex = index;
// navigationChild = widget.wChild;
});
},
toggler: SideBarToggler(
expandIcon: Icons.keyboard_arrow_left,
shrinkIcon: Icons.keyboard_arrow_right,
onToggle: () {
print('Toggle');
}),
),
Expanded(child: widget.wChild)
],
),
),
);
}
}

Related

Flutter Stateful red screen (begginer)

I'm starting out and trying to create a simple application layout with a side menuMy code:
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:side_navigation/side_navigation.dart';
void main() {
runApp(const MainView());
}
class MainView extends StatefulWidget {
const MainView({Key? key}) : super(key: key);
#override
_MainViewState createState() => _MainViewState();
}
class _MainViewState extends State<MainView> {
List<Widget> views = const [
Center(
child: Text('Dashboard'),
),
Center(
child: Text('Account'),
),
Center(
child: Text('Settings'),
),
];
int selectedIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SideNavigationBar(
selectedIndex: selectedIndex,
items: const [
SideNavigationBarItem(
icon: Icons.dashboard,
label: 'Dashboard',
),
SideNavigationBarItem(
icon: Icons.person,
label: 'Account',
),
SideNavigationBarItem(
icon: Icons.settings,
label: 'Settings',
),
],
onTap: (index) {
setState(() {
selectedIndex = index;
});
},
// Change the background color and disabled header/footer dividers
// Make use of standard() constructor for other themes
theme: SideNavigationBarTheme(
backgroundColor: Colors.grey,
togglerTheme: SideNavigationBarTogglerTheme.standard(),
itemTheme: SideNavigationBarItemTheme.standard(),
dividerTheme: SideNavigationBarDividerTheme.standard(),
),
),
Expanded(
child: views.elementAt(selectedIndex),
)
],
),
);
}
}
Source: https://pub.dev/packages/side_navigation#getting-started
Response: Red screen with no media query widget ancestor found....
I'm just starting to learn but I'm stuck on this
I tried different return types but it didn't help
Create a StatelessWidget which returns a MaterialApp, with your MainView widget as a children.
Like this:
class MyApp extends StatelessWidget{
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MainView(),
);
}
}
And then you use runApp(MyApp()), you always need a MaterialApp or CupertinoApp widget to run your application. They configure several stuff for you.
if MainView() is your root class then you are missing MaterialApp
create a StatelessWidget or StatefulWiget and return a Material App
example:
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MainView(),
);
}
}

Flutter change theme and icon of a iconbutton with Getx

I have the following example to change from dark to light mode using Getx. So far it is working. But i would also that the IconButton who change the Theme, would also change is own icon.
What im doing wrong?
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Change Theme',
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.system,
home: HomeScreen(),
);
}
}
// Home Screen
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
var selected = true;
return Scaffold(
appBar: AppBar(
title: Text('Change Theme'),
actions: [
IconButton(
icon: Icon(
selected ? Icons.dark_mode : Icons.light_mode,
),
onPressed: () {
Get.isDarkMode
? Get.changeTheme(ThemeData.light())
: Get.changeTheme(ThemeData.dark());
})
],
),
);
}
}
You need to wrap the icon in a GetX widget that rebuilds based on a variable that lives in a GetX class.
Create a Getx class with a function that changes the theme based on the value of the local boolean.
class ThemeController extends GetxController {
bool isDarkMode = true;
void toggleDarkMode() {
isDarkMode = !isDarkMode;
if (isDarkMode) {
Get.changeTheme(ThemeData.dark());
} else {
Get.changeTheme(ThemeData.light());
}
update();
}
Then wrap your icon in a GetBuilder<ThemeController>
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
Get.put(ThemeController());
return Scaffold(
appBar: AppBar(
title: Text('Change Theme'),
actions: [
GetBuilder<ThemeController>(
builder: (controller) => IconButton(
icon: Icon(
controller.isDarkMode ? Icons.dark_mode : Icons.light_mode,
),
onPressed: () => controller.toggleDarkMode()
],
),
);
}
}
My VsCode is outta commission because I'm traveling with painfully slow internet and I'm in the middle of a multi hour flutter upgrade. So there might be some syntax or minor formatting errors but besides that this should work.

How to push a Route from inside the body of a scaffold with a bottomnavigationbar?

I have a MaterialApp in my main.dart within a Nav() Widget which contains a Scaffold with an appBar and a BottomNavigationBar. The NavigationBar has 4 BottomNavigationBarItems, but I have more Pages than 4 in my whole App. The other pages can be accessed via the first BottomNavigationBarItem 'Home'. But when I push the new NamedRoute the AppBar disapears. How can I solve this Problem?
I've already tried using the bottomNavigationBar in my own Widget in a new File. Problem: setState() doesn't work.
Here's some code
main.dart:
import 'package:flutter/material.dart';
import 'pages/nav.dart';
import 'route/route.dart' as route;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Lieferantenapp',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.red,
),
home: Nav(),
onGenerateRoute: route.controller,
//initialRoute: route.navPage,
);
}
}
nav.dart
import 'package:flutter/material.dart';
import 'package:lieferantenapp/components/MyAppBar.dart';
import 'package:lieferantenapp/components/MyThreeLineText.dart';
//import 'package:lieferantenapp/components/myBottomNavigationBar.dart';
import 'package:lieferantenapp/components/myCard.dart';
import 'package:lieferantenapp/pages/bestellungen.dart';
import 'package:lieferantenapp/pages/packliste.dart';
import 'package:lieferantenapp/pages/tour.dart';
import 'package:lieferantenapp/pages/home.dart';
class Nav extends StatefulWidget {
#override
_NavState createState() => _NavState();
}
class _NavState extends State<Nav> {
int _selectedIndex = 0;
List<Widget> _widgetOptions = <Widget>[
Home(),
Tour(),
Bestellungen(),
Packliste(),
];
void _onItemTap(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
//Custom App Bar without any passed data
appBar: myAppBar(context, null, null),
backgroundColor: Colors.white,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
type: BottomNavigationBarType.fixed,
fixedColor: Colors.red,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(
icon: Icon(Icons.local_shipping),
label: 'Tour',
),
BottomNavigationBarItem(
icon: Icon(Icons.inventory_2),
label: 'Packliste',
),
BottomNavigationBarItem(
icon: Icon(Icons.receipt_long),
label: 'Bestellungen',
),
],
onTap: _onItemTap,
),
body: _widgetOptions.elementAt(_selectedIndex),
);
}
}
home.dart:
import 'package:flutter/material.dart';
import 'package:lieferantenapp/components/MyThreeLineText.dart';
import 'package:lieferantenapp/components/myCard.dart';
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
//BlackBoard Message
//TODO get BlackBoard Message from Server
//myThreeLineText(Context, TextTheme, OVerline, Title, Body)
myThreeLineText(
context,
Theme.of(context).textTheme,
'12.07.2021',
'Servus und Mahlzeit!',
"Herzlich Willkommen in der neuen Mahlzeit LieferApp mit " +
"optimierter Routenplanung via Google Maps.",
),
Expanded(
child: GridView.count(
shrinkWrap: true,
primary: false,
padding: const EdgeInsets.all(18),
crossAxisSpacing: 15,
mainAxisSpacing: 15,
crossAxisCount: 2,
children: <Widget>[
myCard(
context,
Icons.map_outlined,
'Touren',
Theme.of(context).textTheme,
Color(0xff119052),
),
myCard(
context,
Icons.local_shipping_outlined,
'Touren',
Theme.of(context).textTheme,
Color(0xffFF9444),
),
myCard(
context,
Icons.inventory_2_outlined,
'Touren',
Theme.of(context).textTheme,
Color(0xff84000F),
),
myCard(
context,
Icons.receipt_long_outlined,
'Touren',
Theme.of(context).textTheme,
Color(0xffCB5E5E),
),
myCard(
context,
Icons.bookmark_border_outlined,
'Touren',
Theme.of(context).textTheme,
Color(0xffCC3021),
),
myCard(
context,
Icons.settings_outlined,
'Touren',
Theme.of(context).textTheme,
Color(0xff57BB61),
),
],
),
),
],
);
}
}
myCard.dart:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:lieferantenapp/route/route.dart' as route;
Widget myCard(BuildContext context, IconData icon, String text, TextTheme theme,
Color cColor) {
return GestureDetector(
//TODO implement ROUTE ontap start Cards
onTap: () => {},
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
color: Colors.white,
size: 30,
),
SizedBox(height: 25),
Text(
text,
style: theme.bodyText1.apply(color: Colors.white),
),
],
),
color: cColor,
),
);
}
I am also open for Tips and Tricks to improve my code.
EDIT - new main.dart:
import 'package:flutter/material.dart';
import 'package:lieferantenapp/components/MyAppBar.dart';
import 'pages/nav.dart';
import 'route/route.dart' as route;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Mahlzeit Lieferantenapp',
theme: ThemeData(
primarySwatch: Colors.red,
),
//home: Nav(),
//onGenerateRoute: route.controller,
builder: (context, _) => Scaffold(
appBar: myAppBar(context, null, null),
body: Navigator(onGenerateRoute: route.controller),
),
//initialRoute: route.navPage,
);
}
}
The app bar is part of the Scaffold which will appear only on the 4 widgets In _widgetOptions. If you don't have that Scaffold on your page there is no app bar.
Wrapping your Navigator with your Scaffold (i.e. Scaffold on TOP of the Navigator) should do the trick.
Note that when you're 'returning' a Widget from a build function it becomes a child. When you are using Navigator to push a Widget it becomes a sibling and will be on top of the current screen. You can visualize this clearly in the dev tools.
EDIT: Since you're using the default MaterialApp's Navigator this won't work. You will need to create your own Navigator for this.
i.e: Remove home parameter from MaterialApp. Use builder parameter instead. And provide a Navigator widget to the builder (wrapped by your Scaffold)
Something like this:
MaterialApp(builder: (context, _) => Scaffold( ....., body: Navigator( onGenerateRoutes:...)))

AppWidget home not changing when state changes

Using Riverpod + StateNotifier but I think with other providers there is the same issue.
I have an authentication StateNotifier class and StateNotifierProvider and have wrapped the MaterialApp widget into a Riverpod Consumer to rebuild the complete app/widget tree when user is no longer authenticated.
As soon as I navigate with pushReplacementNamed to a third page and update the state of the authenticationStateNotifierProvider, I can see the build method of the consumer wrapping the App is triggered and the state is updated (print(state)) but the home page and widget tree is not rebuilt.
Sample app with 3 screen with the issue:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/all.dart';
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final state = watch(authenticationNotifier.state);
print(state);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: state is Unauthenticated ? LoginScreen() : HomeScreen(),
onGenerateRoute: (RouteSettings settings) {
if (settings.name == '/second')
return MaterialPageRoute(builder: (_) => SecondScreen());
else
return MaterialPageRoute(builder: (_) => HomeScreen());
},
);
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('HomeScreen'),
),
body: Column(
children: [
MaterialButton(
child: Text('Logout'),
onPressed: () => context.read(authenticationNotifier).toggle(),
),
MaterialButton(
child: Text('Second'),
onPressed: () => Navigator.pushReplacementNamed(
context,
'/second',
),
),
],
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SecondScreen'),
),
body: MaterialButton(
child: Text('Logout'),
onPressed: () => context.read(authenticationNotifier).toggle(),
),
);
}
}
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('LoginScreen'),
),
body: MaterialButton(
child: Text('Login'),
onPressed: () => context.read(authenticationNotifier).toggle(),
),
);
}
}
// Controller.
final authenticationNotifier =
StateNotifierProvider((ref) => AuthenticationNotifier());
class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
AuthenticationNotifier() : super(Unauthenticated());
void toggle() {
state = state is Unauthenticated ? Authenticated() : Unauthenticated();
}
}
// State.
abstract class AuthenticationState {}
class Authenticated extends AuthenticationState {}
class Unauthenticated extends AuthenticationState {}
If you test the app you will see state management works between login and home page as expected with the code, but as soon as you navigate to the second screen form the home page and press the logout button, state is changed on the App widged but widget tree is not updated.
The problem is in your onGenerateRoute you are always redirecting to the HomeScreen in your else case. The home property on MaterialApp is only called when the app first opens. So, to truly fix your problem (I saw this commit in your repo, which is a workaround that won't work if your user's session were invalidated externally), you should add something like the following:
home: state is Unauthenticated ? LoginScreen() : HomeScreen(),
onGenerateRoute: (RouteSettings settings) {
if (settings.name == '/second')
return MaterialPageRoute(builder: (_) => SecondScreen());
else
return MaterialPageRoute(builder: (_) => state is Unauthenticated ? LoginScreen() : HomeScreen());
},
I think that should work for you.

How to display navigation bar and only show on specific pages in Flutter?

I am working on an app. The app has a splash screen, then a tour screen is displayed (if this is the first time a user is running the app). Finally once the user exits the tour screen they are directed to the main app page. The main page should display a navigation bar (but not the splash or tour screen).
I have all the code below:
How do I display the Navigation Bar?
How do I restrict which pages can display it?
Thanks
main.dart
import 'package:flutter/material.dart';
import 'package:fyw/pages/navigation_bar.dart';
import 'package:fyw/pages/tour.dart';
import 'package:fyw/pages/splash.dart';
import 'package:fyw/pages/login.dart';
import 'package:fyw/pages/home.dart';
var routes = <String, WidgetBuilder>{
"/tourpage": (BuildContext context) => Tour(),
"/loginpage": (BuildContext context) => LoginPage(),
"/homepage": (BuildContext context) => HomePage(),
};
void main() => runApp(new MaterialApp(
theme:
ThemeData(primaryColor: Colors.red, accentColor: Colors.yellowAccent),
debugShowCheckedModeBanner: false,
home: Splash(),
routes: routes
));
navigation_bar.dart
import 'package:flutter/material.dart';
import 'package:fyw/pages/home.dart';
import 'package:fyw/pages/login.dart';
class NavigationBarController extends StatefulWidget {
#override
_NavigationBarControllerState createState() =>
_NavigationBarControllerState();
}
class _NavigationBarControllerState
extends State<NavigationBarController> {
final List<Widget> pages = [
HomePage(),
LoginPage(),
];
final PageStorageBucket bucket = PageStorageBucket();
int _selectedIndex = 0;
Widget _bottomNavigationBar(int selectedIndex) => BottomNavigationBar(
onTap: (int index) => setState(() => _selectedIndex = index),
currentIndex: selectedIndex,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.add), title: Text('home')),
BottomNavigationBarItem(
icon: Icon(Icons.list), title: Text('login Page')),
],
);
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: _bottomNavigationBar(_selectedIndex),
body: PageStorage(
child: pages[_selectedIndex],
bucket: bucket,
),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: NavigationBarController(context),
);
}
}