Encaplsulating a widget for use in another dart file - android

What I'm trying to achieve:
Have a BottomNavigationBar widget in its own class in its own dart file called navigationBar.dart
Have a main.dart file that has a Scaffold widget that calls this class to create the BottomNavigationBar widget
Then in the main.dart file I want to be able to set the BottomNavigationBar from navigationBar.dart and I want to be able to change the body of the Scaffold widget in the main.dart file depending on which index is selected in the BottomNavigationBar widget (check the comment in the main.dart file in the body property for a better explanation)
Here is my code below so far:
navigationBar.dart
import 'package:flutter/material.dart';
import '../home.dart';
class NavigationBar extends StatefulWidget {
const NavigationBar({Key? key}) : super(key: key);
#override
State<NavigationBar> createState() => _NavigationBar();
}
class _NavigationBar extends State<NavigationBar> {
int selectedIndex = 2;
void _onItemTapped(int index) {
setState(() {
selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
backgroundColor: Colors.red,
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
backgroundColor: Colors.green,
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
backgroundColor: Colors.purple,
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
backgroundColor: Colors.pink,
),
],
currentIndex: selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
);
}
main.dart
import 'package:flutter/material.dart';
import 'components/navigationBar.dart';
void main() {
runApp(const MaterialApp(home: App()));
}
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
#override
State<App> createState() => _App();
}
class _App extends State<App> {
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 0: Home',
),
Text(
'Index 1: Business',
),
Text(
'Index 2: School',
),
Text(
'Index 3: Settings',
),
];
#override
Widget build(BuildContext context) {
const navBar = navigationBar()
return Scaffold(
appBar: AppBar(
title: const Text('Test App',
style: TextStyle(
color: Colors.white,
fontFamily: 'LogoFont',
fontSize: 30.0,
letterSpacing: 1.5)),
centerTitle: true,
backgroundColor: Colors.lightBlue[500],
elevation: 0.0,
),
backgroundColor: Colors.lightBlue[800],
body: //something like this: _widgetOptions.elementAt(navbar.selectedIndex)
),
bottomNavigationBar: navBar);
}
}
Any ideas on how I could create what I need in the bullet points? Any help would be great, thanks

I think there is no way with stateful widget but you can do this by provider, like example below.
Provider:
class MainViewProvider with ChangeNotifier ,
DiagnosticableTreeMixin{
int activeItem = 2;
changeActiveItem(int activeElement){
activeItem = activeElement;
notifyListeners();
}
}
BottomNavBar Widget:
class BotNavWidget extends StatelessWidget {
const BotNavWidget({Key? key}) : super(key: key);
get context => null;
#override
Widget build(BuildContext context) {
final watch = context.watch<ColorsProvider>();
return Container(
padding: EdgeInsets.symmetric(
horizontal: getWidth(16), vertical: getHeight(10)),
child: Container(
height: SizeConfig.height! * .1,
width: SizeConfig.width!,
decoration: BoxDecoration(
color: watch.colors[1],
borderRadius: BorderRadius.circular(getWidth(20))),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
buildIcon(watch.bottomIcons[0], context, 0),
buildIcon(watch.bottomIcons[1], context, 1),
buildIcon(watch.bottomIcons[2], context, 2),
buildIcon(watch.bottomIcons[3], context, 3),
buildIcon(watch.bottomIcons[4], context, 4),
],
),
),
);
}
buildIcon(ColorFiltered icon, BuildContext context, int i) {
final read = context.read<MainViewProvider>();
final watch = context.watch<MainViewProvider>();
final watchColors = context.watch<ColorsProvider>().colors;
return InkWell(
onTap: () async{
read.changeActiveItem(i);
},
child: Container(
padding: EdgeInsets.all(getWidth(15)),
height: SizeConfig.height! * .07,
width: SizeConfig.height! * .07,
decoration: BoxDecoration(
color: watch.activeItem == i ? watchColors[2] : Colors.transparent,
borderRadius: BorderRadius.circular(getWidth(20))),
child: SizedBox(
height: getHeight(24),
width: getHeight(24),
child: icon
),
),
);
}
}
P.S: You can use custom BottomNavigationBar Widget instead of making it manually

Related

How can I create inner navigation in showModalBottomSheet?

In my app I am trying to implement Badoo-like sort/filter showBottomModalSheet feature. I managed to create 2 separate pages, which I can navigate back and forth. However, the problem I'm facing is the second page in showBottomModalSheet. Back button works fine until I try to touch outside of the modal, which takes back to the first page. Instead it should close modal.
User navigates to sort users modal, which shows the 1st page in showBottomModalSheet When user taps "Show gender" it navigates to the second page (the one with different genders). When back button is pressed it navigates to 1st screen until it closes modal completely. Touching outside of the modal also closes the modal
The best stackoverflow answer that I tried:
https://stackoverflow.com/questions/63602999/how-can-i-do-navigator-push-in-a-modal-bottom-sheet-only-not-the-parent-page/63603685#63603685
I also tried using modal_bottom_sheet package, but had no luck.
https://pub.dev/packages/modal_bottom_sheet/example
Most of my code behind showBottomModalSheet:
class Page1 extends StatefulWidget {
const Page1({
Key? key
}) : super(key: key);
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
int _currentView = 0;
late List<Widget> pages;
#override
void initState() {
pages = [
page1(),
page2(),
];
super.initState();
}
#override
Widget build(BuildContext context) {
print("LOG build _currentView ${_currentView}");
return pages[_currentView];
}
Widget page1() {
return WillPopScope(
onWillPop: () async {
return true;
},
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(60), topLeft: Radius.circular(60))),
height: 400,
width: double.maxFinite,
child: Center(
child: Column(
children: [
Text("First page"),
ElevatedButton(
onPressed: () {
setState(() {
_currentView = 1;
print("LOG page1 _currentView ${_currentView}");
});
},
child: Text("tap to navigate to 2nd page"),
),
],
)),
));
}
Widget page2() {
return WillPopScope(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(60), topLeft: Radius.circular(60))),
height: 400,
width: double.maxFinite,
child: Center(
child: InkWell(
onTap: () {
setState(() {
_currentView = 0;
print("LOG page2 _currentView ${_currentView}");
});
},
child: Text("tap to navigate to 1st screen"),
),
),
),
onWillPop: () async {
print("LOG currentView jot $_currentView");
if (_currentView == 0) {
return true;
}
setState(() {
_currentView = 0;
});
return false;
});
}
}
Solution 1
Use the standard DraggableScrollableSheet or a 3rd-party widget to do it, there are a bunch of them. Here are some from https://pub.dev/
awesome_select
backdrop_modal_route
bottom_sheet_expandable_bar
bottom_sheet
cupertino_modal_sheet
modal_bottom_sheet
just_bottom_sheet
sheet
Solution 2
Anyway, if you'd like to do it manually I'd do it with Navigator.push/Navigator.pop instead with PageRouteBuilder with barrierDismissible=true.
It would like the following. Check out the live demo on DartPad.
Here's the code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void _show() async {
await Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
barrierDismissible: true,
pageBuilder: (_, __, ___) => const Page1(),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: _show,
tooltip: 'Settings',
child: const Icon(Icons.settings),
),
);
}
}
class Page1 extends StatefulWidget {
const Page1({Key? key}) : super(key: key);
#override
State<Page1> createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
Widget build(BuildContext context) {
return Stack(
children: [
Align(
alignment: Alignment.bottomCenter,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
topRight: Radius.circular(60), topLeft: Radius.circular(60)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: const Offset(0, 3), // changes position of shadow
),
],
),
height: 400,
width: double.maxFinite,
child: Center(
child: Material(
type: MaterialType.transparency,
child: Column(
children: [
const Text("First page"),
ElevatedButton(
onPressed: () async {
final backButton =
await Navigator.of(context).push<bool?>(
PageRouteBuilder(
opaque: false,
barrierDismissible: true,
pageBuilder: (_, __, ___) => const Page2(),
),
);
if (backButton == null || backButton == false) {
if (mounted) Navigator.of(context).pop();
}
},
child: const Text("tap to navigate to 2nd page"),
),
],
),
),
),
),
),
],
);
}
}
class Page2 extends StatelessWidget {
const Page2({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(60), topLeft: Radius.circular(60)),
),
height: 400,
width: double.maxFinite,
child: Center(
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () => Navigator.of(context).pop(true),
child: const Text("tap to navigate to 1st screen"),
),
),
),
),
);
}
}

Duplicate GlobalKey detected in widget tree - The key [LabeledGlobalKey<ScaffoldMessengerState>#ab7de] was used by multiple widgets

I am creating TabBar using Getx but getting the error Duplicate GlobalKey detected in the widget tree. So whenever I am going to the second Tab app doesn't show any content. How I solve the issue whenever I am using stateful widget it works but whenever trying Getx to create the TabBar using the stateless widget.
TabBar Class:
class Page2 extends StatelessWidget {
const Page2({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final GetxTab getxTab = Get.put(GetxTab());
return MaterialApp(
home: Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: getxTab.tabController,
tabs: getxTab.appTabs,
),
),
body: TabBarView(controller: getxTab.tabController, children: [
PageTabs1(),
GetxExample(),
])),
);
}
}
class GetxTab extends GetxController with SingleGetTickerProviderMixin {
late TabController tabController;
final List<Tab> appTabs = <Tab>[
Tab(
icon: Icon(
Icons.share,
),
text: ("Bottom Sheet")),
Tab(
icon: Icon(
Icons.share,
),
text: ("Getx")),
];
#override
void onInit() {
// TODO: implement onInit
super.onInit();
tabController = TabController(length: appTabs.length, vsync: this);
}
#override
void onClose() {
// TODO: implement onClose
super.onClose();
tabController.dispose();
}
}
First Page:
Updated: Problem solved I just figure out I make a mistake adding GetMaterialApp, Scaffold
both of my Parent and child class. Which conflicts one with another.
So I just remove the child GetMaterialApp( home: Scaffold(
class PageNav3 extends StatelessWidget {
const PageNav3({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: Text(
"Nav1",
style: TextStyle(color: Colors.red),
),
),
);
}
}
Second Page:
This page causes the Issue
class GetxExample extends StatelessWidget {
GetxExample({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
bool value = true;
return GetMaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: GestureDetector(
child: Container(
width: double.infinity,
height: 45,
child: My_Button(
ButtonText: "Change",
Backcolors: Colors.black,
FontColors: Colors.white,
padBot: 5,
padTop: 5,
padRight: 5,
padLeft: 5),
),
onTap: () {
value = !value;
Get.bottomSheet(
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
color: Colors.blueGrey,
),
child: Wrap(
children: [
AddListTittle(
Tittle: "Camera",
des: "Add Photo by clicking Camera",
iconss: Icons.camera,
Index: 0,
reqIndex: ImageSource.camera,
),
AddListTittle(
Tittle: "Gallery",
des: "Add Photo from Gallery",
iconss: Icons.storage,
Index: 1,
reqIndex: ImageSource.gallery,
),
],
),
),
);
},
),
),
),
);
}
}
If you are using the scaffold keys to display snackbar, remove them and use the overlay support package, it offers a simpler implementation

How to open this type of alert dialog in flutter

I wanted to show dialog in my application. How can i achieve this using flutter
You can use a PopupMenuButton (https://api.flutter.dev/flutter/material/PopupMenuButton-class.html) to achieve this in flutter.
See example code below:
PopupMenuButton<int>(
itemBuilder: (context) => [
const PopupMenuItem(
value: 1,
child: Center(
child: Icon(
Icons.download_outlined,
size: 30.0,
),
),
),
const PopupMenuItem(
value: 2,
child: Center(
child: Icon(
Icons.link,
size: 30.0,
),
),
),
const PopupMenuItem(
value: 2,
child: Center(
child: Icon(
Icons.share,
size: 30.0,
),
),
),
],
icon: const Icon(
Icons.more_horiz,
size: 40.0,
),
offset: const Offset(150, -150),
);
The above example popups a list of Icons when the PopupMenuButton is pressed.
You can adapt this to your use-case above.
Finally I found a Solution thanks enfinity. Here how i solve the problem.
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHomePage(),
);
}
}
/// An arbitrary widget that lives in a popup menu
class PopupMenuWidget<T> extends PopupMenuEntry<T> {
const PopupMenuWidget({ Key key, this.height, this.child }) : super(key: key);
#override
final Widget child;
#override
final double height;
#override
bool get enabled => false;
#override
_PopupMenuWidgetState createState() => new _PopupMenuWidgetState();
}
class _PopupMenuWidgetState extends State<PopupMenuWidget> {
#override
Widget build(BuildContext context) => widget.child;
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
actions: <Widget>[
new PopupMenuButton<String>(
onSelected: (String value) {
print("You selected $value");
},
itemBuilder: (BuildContext context) {
return [
new PopupMenuWidget(
height: 40.0,
child: new Row(
children: [
IconButton(
icon: Icon(
Icons.remove,
color: Colors.green,
),
onPressed: () {
print("Remove");
}),
Text("1"),
IconButton(
icon: Icon(
Icons.add,
color: Colors.green,
),
onPressed: () {
print("Add");
}),
],
),
),
];
}
),
],
),
);
}
}

Black-Screen with the FlatButton in the AppBar

My App contains basically 2 parts -> Appbar (with 1 Button) and BottomNavigationBar (with some buttons that works properly). The problem came when I pressed the Appbar button (goes to a black screen instead of show the "manual_page.dart")
this is the content of the 2 files (the home_page.dart and manual_page.dart):
home_page.dart
import 'package:flutter/material.dart';
import 'package:opening_a_pdf/manual_page.dart';
import 'package:opening_a_pdf/first_page.dart';
import 'package:opening_a_pdf/second_page.dart';
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _selectedPage = 0;
List<Widget> pageList = List<Widget>();
#override
void initState() {
pageList.add(FirstPage());
pageList.add(SecondPage());
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFFAFAFA),
appBar: AppBar(
backgroundColor: Colors.black,
title: const Text('Aplicación en Desarrollo'),
actions: <Widget>[
FlatButton(
textColor: Colors.white,
child: Text(
'MANUAL',
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
),
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Voice()),
);
}
)
],
),
body: IndexedStack(
index: _selectedPage,
children: pageList,
),
bottomNavigationBar: BottomNavigationBar(
// type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
backgroundColor: Colors.black,
icon: Icon(Icons.compare_arrows),
title: Text('Conectividad'),
),
BottomNavigationBarItem(
backgroundColor: Colors.black,
icon: Icon(Icons.blur_on),
title: Text('Captura Datos'),
),
BottomNavigationBarItem(
backgroundColor: Colors.black,
icon: Icon(Icons.graphic_eq),
title: Text('Voz'),
),
BottomNavigationBarItem(
backgroundColor: Colors.black,
icon: Icon(Icons.list),
title: Text('Comandos'),
),
BottomNavigationBarItem(
backgroundColor: Colors.black,
icon: Icon(Icons.settings),
title: Text('Ajustes'),
),
],
currentIndex: _selectedPage,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void _onItemTapped(int index) {
setState(() {
_selectedPage = index;
});
}
}
manual_page.dart
import 'package:flutter/material.dart';
// ignore: camel_case_types
class Voice extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sección de Órdenes por Voz"),
),
body: Stack(
fit: StackFit.expand,
children: <Widget>[
Positioned(
bottom: 0,
width: MediaQuery.of(context).size.width,
child: Center(
child: MaterialButton(
onPressed: () {},
color: Colors.red,
),
),
)
],
),
);
}
}
Try to initial the height of container in the second screen before Stack
There are no errors in the code. Works correctly. Maybe the fault is in the main () or in the emulator.
Code in main:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
I executed your code and found no problem with it:
But you can put empty Container() as the child of MaterialButton().
Corrected code:
MaterialButton(
onPressed: () {},
color: Colors.red,
child:Container(),
),

Is it possible to get a bottomNavBar without icons / with text only

I want to get a bottom navigation bar, but the Tabs should be text-only. The problem is, that icon is a required property of BottomNavigationBarItem().
Edit: I got it working using a tab bar as bottom nav bar, but #Fernando Rocha 's solution seems to work less tricky and works better. To sum it up, simply add "size: 0" to each icon (you will still need an icon).
I used size 0 at icon size and it worked
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 0: Home',
style: optionStyle,
),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home, size: 0),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business, size: 0),
title: Text('Business'),
),
BottomNavigationBarItem(
icon: Icon(Icons.school, size: 0),
title: Text('School'),
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
You can do
BottomNavigationBarItem(
icon: Icon(null),
title: Text('Just Text'),
)
to achieve this.
With this approach there will still be an empty space where the Icon is "supposed" to go. With #Fernando Rocha 's approach it looks like the text is centered.
I used tabs instead :
static const List<Tab> _tabs = [
Tab(text: "A"),
Tab(text: "AA"),
Tab(text: "AAA")
];
return WillPopScope(
child: DefaultTabController(
length: _tabs.length,
child: Scaffold(
bottomNavigationBar: Container(
// color: Color(0xFF3F5AA6),
margin: const EdgeInsets.only(bottom: 11),
child: TabBar(
// labelColor: Colors.white,
// unselectedLabelColor: Colors.white60,
// indicatorSize: TabBarIndicatorSize.tab,
indicatorPadding: const EdgeInsets.symmetric(vertical: 7, horizontal: 23),
indicatorColor: Colors.white,
onTap: (int index) {
setState(() {
_selectedIndex = index;
});
},
tabs: _tabs,
),
),
body: Center(
child: _pages[_selectedIndex]
),
),
),
onWillPop: () async {
return Navigator.canPop(context);
}
);

Categories

Resources