I would like to: when user tapped drawer menu item, change the pageview's index which is located at main screen.
I tried to change index from another file but I couldn't
drawer menu code
InkWell(
onTap: () {
debugPrint("Tapped");
HomeApp().openMyGloves();
},
)
openMyGloves()
class HomeApp extends StatefulWidget {
const HomeApp({Key? key}) : super(key: key);
#override
State<HomeApp> createState() => _HomeAppState();
void openMyGloves() {
_HomeAppState()._openMyGloves();
}
}
class _HomeAppState extends State<HomeApp> {
class _HomeAppState extends State<HomeApp> {
int simdikiIndex = 1;
late List<Widget> tumSayfalar;
late Blog blogSayfa;
late MyGloves gloveSayfa;
late HomePage homeSayfa;
late final controller;
#override
void initState() {
blogSayfa = const Blog();
gloveSayfa = const MyGloves();
homeSayfa = const HomePage();
tumSayfalar = [blogSayfa, homeSayfa, gloveSayfa];
controller = PageController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
drawer: const DrawerMenu(),
bottomNavigationBar: bottomNav(),
body: PageView(
/// [PageView.scrollDirection] defaults to [Axis.horizontal].
/// Use [Axis.vertical] to scroll vertically.
controller: controller,
children: <Widget>[blogSayfa, homeSayfa, gloveSayfa],
onPageChanged: (page) {
setState(() {
simdikiIndex = page;
});
}));
}
BottomNavigationBar bottomNav() {
return BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Image.asset(
"assets/images/info.png",
scale: 2,
),
label: "HAKKIMIZDA",
),
BottomNavigationBarItem(
icon: Image.asset(
"assets/images/home.png",
scale: 2,
),
label: "ANA SAYFA",
),
BottomNavigationBarItem(
icon: Image.asset(
"assets/images/gloves.png",
scale: 2,
),
label: "ELDİVENLERİM",
),
],
onTap: (index) {
setState(() {
simdikiIndex = index;
controller.jumpToPage(index);
});
},
currentIndex: simdikiIndex,
);
}
void _openMyGloves() {
controller.jumpToPage(2);
}
}
}
note: I got
Late Initialization Error
for controller.for controller.for controller.for controller.for controller.for controller.for controller.for controller.for controller.for controller.for controller.for controller.
HomeApp().openMyGloves();
Here you are calling openMyGloves() from a new instance of the HomeApp which is not the one that exists in the widget tree
to solve this you have to access the same HomeApp which is built in the widget tree, this will be done by these steps:
1- Make _HomeAppState not private by removing the underscore _
2- define a global key with the HomeAppState in the parent widget of the HomeApp and pass it to HomeApp widget
static final GlobalKey<HomeAppState> homeAppKey = GlobalKey();
then
child: HomeApp(key: homeAppKey),
now you can call openMyGloves() using this key like this
ParentWidget.homeAppKey.currentState?.openMyGloves();
ParentWidget is the parent class of HomeApp in which you define the key and pass it to HomeApp
It'll be way easier if you pass the page view value you want to access to the parent widget, in your case HomeApp(), the code should look like this:
class DrawerMenu extends StatefulWidget {
Function pageViewIndex;
DrawerMenu({required this.pageViewIndex});
#override
_DrawerMenuState createState() => _DrawerMenuState();
}
class _DrawerMenuState extends State<DrawerMenu> {
#override
Widget build(BuildContext context) {
return DrawerMenuItem(
child: Text(item),
onPressed: () {
widget.pageViewIndex(x);
// Where x equals the value you want to pass to HomeApp()
}
);
}
}
Once you did that, you can now read that value from HomeApp() by using a function like this:
// This function goes to HomeApp()
void function(pageViewIndex) {
setState(() {
simdikiIndex = pageViewIndex;
});
}
A different approach would be working with Provider, but if you're not familiarized with it the approach I just gave you should do the trick
Related
I have implemented following BottomNavigation
class AppMenu extends StatefulWidget {
const AppMenu({Key? key}) : super(key: key);
#override
State<AppMenu> createState() => _AppMenuState();
}
class _AppMenuState extends State<AppMenu> {
int current = 0;
final List<String> titles = [
"Home 1",
"Home 2"
];
final List<Widget> views = [
const HomeView1(),
const HomeView2(),
];
final List<String> icons = [
"icon_1",
"icon_2",
];
final List<String> barTitles = ["Home1", "Home2"];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: HomeAppBar(
title: titles[current],
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: (index) {
setState(() {
current = index;
});
},
selectedItemColor: const Color(0xff6B6B6B),
showUnselectedLabels: true,
showSelectedLabels: true,
unselectedItemColor: const Color(0xff6B6B6B),
selectedLabelStyle: const TextStyle(fontSize: 12),
unselectedLabelStyle: const TextStyle(fontSize: 12),
items: views.map((e) {
final itemIndex = views.indexOf(e);
return BottomNavigationBarItem(
icon: Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Image.asset(
"assets/images/${icons[itemIndex]}${itemIndex == current ? "" : "_disabled"}.png",
width: 25,
),
),
label: barTitles[itemIndex],
);
}).toList()),
body: Column(
children: [
Expanded(child: views[current]),
],
),
);
}
}
Now it works perfect when I click on home1 and home2 bottom menu and it shows respected widget and load all the content which I have wrote on initState of home1 and home2 but now assume that I am on home1 and if I click again home1 then it is not calling initState again.
I want to call initState or specific function if user click on that menu even if it is selected.
Is there any way to do it?
You can create a initialize or initXXX function to initialize something in initState or somewhere. If parent widget call setState(), then child widget will call didUpdateWidget().
void initialize() {
// do something
}
Call initialize() in initState().
void initState() {
super.initState();
initialize();
}
Call initialize() in didUpdateWidget() of page(child widget).
#override
void didUpdateWidget(covariant PageTest oldWidget) {
super.didUpdateWidget(oldWidget);
initialize();
}
To handle the case in a simple way. You can add your method in onTap of BottomNavigationBar and then pass your data down to the widget tree.
It's only a demonstration to handle your case, you can adjust it with your own liking
For example
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: (index) {
if(current == index){
foo = yourMethodHere();
}
setState(() {
current = index;
});
},
Pass the variable in the tree
List<Widget> get views => [
HomeView1(foo),
HomeView2(foo),
];
My app allows people to post text and switch between different pages on a navbar. On the users page, there is a button, when clicked, will show an overlay so the user can create a post. The overlay includes a back button that calls a function to close the overlay. I want to keep the navbar available at the bottom so user can back out of the post that way if they want to.
The problem is, when the user uses the navbar, the overlay does not close because the close overlay function is on the user page and the navbar page does not have access to it.
How do I give another class on another dart file access to a method or function? If you are able to answer, can you please use my code instead of another example to help me follow better? Thank you.
User Page File #1
class UserPage extends StatefulWidget {
#override
_UserPageState createState() => _UserPageState();
}
class _UserPageState extends State<UserPage> {
OverlayEntry? entry;
#override
Widget build(BuildContext context) {
return Scaffold(
ElevatedButton(
child: const Text('New Post'),
onPressed: showOverlay,
),
),
}
void showOverlay() {
(...)
}
void closeOverlay() {
entry?.remove();
entry = null;
}
}
Nav Bar File #2 (Need help with "OnTap")
class Nav extends StatefulWidget {
const Nav({Key? key}) : super(key: key);
#override
_NavState createState() => _NavState();
}
class _NavState extends State<Nav> {
int currentTab = 1; // makes the home page the default when loading up the app
#override
void initState() {
super.initState();
}
List<Widget> tabs = <Widget>[
const Other(),
const Home(),
const UserPage(),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: tabs.elementAt(currentTab),
),
// BOTTOM NAVIGATION BAR
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentTab,
onTap: (value) {
setState(() => currentTab = value);
const _UserPageState().closeOverlay(); //HERE IS WHERE I NEED HELP WITH THE CODE
},
items: const [
BottomNavigationBarItem(
label: 'Other',
),
BottomNavigationBarItem(
label: 'Home',
),
BottomNavigationBarItem(
label: 'User Page',
),
],
),
);
}
}
You can try to make your _UserPageState public by removing - from it, and then call it UserPageState().closeOverlay();
I have a StatefulWidget class 'FirstClass' which extends a State '_FirstClassState'. From a separate State class, called '_SecondClassState', I'm trying to access the value of a variable (called 'counter' in '_FirstClassState').
I've found a solution, but I'm not sure if this is the best solution to the problem. I've tried looking at other similar questions:
How to access Stateful widget variable inside State class outside the build method? (Declaring the variable in the StatefulWidget class and using 'widget.' in the State class does not allow me to get the value of 'counter' from '_SecondClassState')
I've also seen other sites that recommend using a GlobalKey. However, even though this might be a solution, I didn't explore further into this as I found other sites which recommend minimising the use of GlobalKeys
second_class.dart:
import 'package:brew_crew/question/first_class.dart';
import 'package:flutter/material.dart';
class SecondClass extends StatefulWidget {
#override
_SecondClassState createState() => _SecondClassState();
}
class _SecondClassState extends State<SecondClass> {
final FirstClass firstClass = FirstClass();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("MyApp"),
centerTitle: true,
),
body: Column(
children: <Widget>[
firstClass, //Displays the button to increase 'counter'
FlatButton(
child: Text("Submit"),
onPressed: () {
print(firstClass
.getCounter()); //I need to get the value of 'counter' here to use elsewhere
}),
],
),
);
}
}
first_class.dart
import 'package:flutter/material.dart';
class FirstClass extends StatefulWidget {
final _FirstClassState firstClassState = _FirstClassState();
#override
_FirstClassState createState() => firstClassState;
int getCounter() {
return firstClassState.counter;
}
}
class _FirstClassState extends State<FirstClass> {
int counter = 0;
//Increases counter by 1
void _increaseCounter() {
setState(() {
counter++;
});
}
#override
Widget build(BuildContext context) {
//A button which increases counter when clicked
return FlatButton.icon(
icon: Icon(Icons.add),
label: Text("Counter: $counter"),
onPressed: () {
_increaseCounter();
},
);
}
}
As you can see, within 'FirstClass', I initialise a new instance of '_FirstClassState'. Using this in the 'getCounter()' function, I can get the 'counter' value.
Is there a better way (e.g. best practice, fewer lines of code, an easier to understand method, etc.) than this to achieve what I'm trying to do?
The use of GlobalKey is definitely the recommended approach if absolutely you have to access the state of a widget from outside. However, in this case, you shouldn't use either approach.
_SecondClassState should contain the counter, and you should pass it, along with the increaseCounter function, as parameters to FirstClass. If you want to increase the number, just call that function.
Something along these lines:
class SecondClass extends StatefulWidget {
#override
_SecondClassState createState() => _SecondClassState();
}
class _SecondClassState extends State<SecondClass> {
int counter = 0;
//Increases counter by 1
void _increaseCounter() {
setState(() {
counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("MyApp"),
centerTitle: true,
),
body: Column(
children: <Widget>[
FirstClass(counter: counter, increaseCounter: _increaseCounter), //Displays the button to increase 'counter'
FlatButton(
child: Text("Submit"),
onPressed: () {
print(counter.toString()); //I need to get the value of 'counter' here to use elsewhere
}),
],
),
);
}
}
class FirstClass extends StatefulWidget {
final int counter;
final Function increaseCounter;
FirstClass({this.counter, this.increaseCounter});
final _FirstClassState firstClassState = _FirstClassState();
#override
_FirstClassState createState() => firstClassState;
}
class _FirstClassState extends State<FirstClass> {
#override
Widget build(BuildContext context) {
//A button which increases counter when clicked
return FlatButton.icon(
icon: Icon(Icons.add),
label: Text("Counter: ${widget.counter}"),
onPressed: () {
widget.increaseCounter();
},
);
}
}
A simple, but very complicated question: What’s the best way to add a tap to focus functionality for the Flutter camera?
I’ve searched the entire World Wide Web about elegant solutions, but I found nothing.
Do you have an idea?
I might be late but you can try adv_camera package.
Here is a simple example:
import 'package:adv_camera/adv_camera.dart';
import 'package:flutter/material.dart';
class CameraApp extends StatefulWidget {
final String id;
const CameraApp({Key? key, required this.id}) : super(key: key);
#override
_CameraAppState createState() => _CameraAppState();
}
class _CameraAppState extends State<CameraApp> {
List<String> pictureSizes = <String>[];
String? imagePath;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AdvCamera Example'),
),
body: SafeArea(
child: AdvCamera(
initialCameraType: CameraType.rear,
onCameraCreated: _onCameraCreated,
onImageCaptured: (String path) {
if (this.mounted)
setState(() {
imagePath = path;
});
},
cameraPreviewRatio: CameraPreviewRatio.r16_9,
focusRectColor: Colors.purple,
focusRectSize: 200,
),
),
floatingActionButton: FloatingActionButton(
heroTag: "capture",
child: Icon(Icons.camera),
onPressed: () {
cameraController!.captureImage();
},
),
);
}
AdvCameraController? cameraController;
_onCameraCreated(AdvCameraController controller) {
this.cameraController = controller;
this.cameraController!.getPictureSizes().then((pictureSizes) {
setState(() {
this.pictureSizes = pictureSizes ?? <String>[];
});
});
}
}
I want to load pages from a List and when the user taps on an item from the drawer he can go to that page (if it's already opened) otherwise the Widget will load in the selected page.
But I can't find if that widget is already exists in the List if(myList.contains(Widget1())) => print('it exist'); One guy told me to override hashCode and operator==
class Widget6 extends StatelessWidget {
final String title = 'Widget6';
final Icon icon = Icon(Icons.assessment);
#override
Widget build(BuildContext context) {
return Center(
child: icon,
);
}
#override
bool operator ==(dynamic other) {
final Widget6 typedOther = other;
return title == typedOther.title && icon == typedOther.icon;
}
#override
int get hashCode => hashValues(title, icon);
}
if I do that I can't use any child widget to those widgets. Getting exception like: type 'Center' is not a subtype of type 'Widget6'. I copied this from flutter gallery I didn't find good documentation/guide. Sorry, I am a beginner.
Complete code below
class _MyHomePageState extends State<MyHomePage> {
List pageList = [
Widget1(),
Widget2(),
Widget3(),
Widget4(),
];
PageController _pageController;
int _selectedIndex = 0;
#override
void initState() {
_pageController = PageController(
initialPage: _selectedIndex,
);
super.initState();
}
void navigatePage(Widget widget) {
// problem is here
if (pageList.contains(widget)) {
_pageController.animateToPage(pageList.indexOf(widget, 0),
duration: Duration(milliseconds: 300), curve: Curves.ease);
}
else {
setState(() {
pageList.removeAt(_pageController.page.toInt());
pageList.insert(_pageController.page.toInt(), widget);
});
_pageController.animateToPage(_pageController.page.toInt(),
duration: Duration(milliseconds: 300), curve: Curves.ease);
}
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text('Widget1'),
onTap: () => navigatePage(
Widget1(),
),
),
ListTile(
title: Text('Widget2'),
onTap: () => navigatePage(
Widget2(),
),
),
ListTile(
title: Text('Widget3'),
onTap: () => navigatePage(
Widget3(),
),
),
ListTile(
title: Text('Widget4'),
onTap: () => navigatePage(
Widget4(),
),
),
ListTile(
title: Text('Widget5'),
onTap: () => navigatePage(
Widget5(),
),
),
ListTile(
title: Text('Widget6'),
onTap: () => navigatePage(
Widget6(),
),
),
],
),
),
appBar: AppBar(
title: Text(widget.title),
),
body: PageView.builder(
onPageChanged: (newPage) {
setState(() {
this._selectedIndex = newPage;
});
},
controller: _pageController,
itemBuilder: (context, index) {
return Container(
child: pageList[index],
);
},
itemCount: pageList.length,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) => setState(() {
_selectedIndex = index;
_pageController.animateToPage(index,
duration: Duration(milliseconds: 300), curve: Curves.ease);
}),
items: pageList.map((page) {
return BottomNavigationBarItem(
backgroundColor: Colors.deepOrangeAccent,
icon: page.icon,
title: Text(page.title));
}).toList(),
),
);
}
}
Here List of dummy Widgets
class Widget1 extends StatelessWidget {
final String title = 'Widget1';
final Icon icon = Icon(Icons.school);
#override
Widget build(BuildContext context) {
return Center(
child: icon,
);
}
}
class Widget2 extends StatelessWidget {
// only title and icon are changed
}
class Widget3 extends StatelessWidget {
// only title and icon are changed
}
class Widget4 extends StatelessWidget {
// only title and icon are changed
}
class Widget5 extends StatelessWidget {
// only title and icon are changed
}
class Widget6 extends StatelessWidget {
// only title and icon are changed
}
Okay, I found the solution. And it has to do with operator== overriding
I missed this line if (runtimeType != other.runtimeType) return false;
The whole code stays the same.
#override
// ignore: hash_and_equals
bool operator ==(dynamic other) {
if (runtimeType != other.runtimeType) return false;
final Widget6 typedOther = other;
return title == typedOther.title;
}
#Ahmed Sorry for the late reply, I decided to put it in an answer rather than a comment.
One solution is yours, overriding == but I was thinking of using Key and then instead of using contains method, using something like:
if(myList.indexWhere((Widget widget)=> widget.key==_key) != -1)...
Suggestion
You can store icon and title as a map or a module instead of making 6 different Widget.
You can create another file, saying module.dart like this:
class Module {
final String title;
final Icon icon;
Module(this.title, this.icon);
#override
int get hashCode => hashValues(title.hashCode, icon.hashCode);
#override
bool operator ==(other) {
if (!identical(this, other)) {
return false;
}
return other is Module &&
this.title.compareTo(other.title) == 0 &&
this.icon == other.icon;
}
}
Then create another file that builds the page, saying mywidget.dart, like this:
class MyWidget extends StatelessWidget {
final Module module;
MyWidget({Key key,#required this.module}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: module.icon,
);
}
}
Then on each ListTile's onTap, Navigate like this:
...
ListTile(
title: Text('Widget1'),
onTap: () => navigatePage(
MyWidget(module: Module('Widget1', Icon(Icons.school)),)
),
),
...
So instead of storing Widgets, you store a Type(Here Module) that you declared.
You can also use the list's map to build each ListTile of the ListView for each Module, instead of doing it one by one. (if each item on the drawer are similar), Something like this:
List<Module> myTabs = [
Module('Widget1', Icon(Icons.school)),
Module('Widget2', Icon(Icons.home)),
];
...
Drawer(
child: ListView(
children:myTabs.map((Module module)=> ListTile(
title:Text( module.title),
onTap: navigatePage(MyWidget(module: module,)),
)).toList(),
) ,
);
...