I am trying to build simple TabBar but getting an error
missing the concrete implementation of 'State.build' and the declaration 'build isn't referenced' . I have created the controller and trying to change to each tab when pressed on individuals.
I have created there tab and provided different function to them.
By clicking the Tab it will not change. I donot know where I am getting wrong?
Thank you in advance.
import 'package:flutter/material.dart';
import 'homepage.dart';
import 'secondpage.dart';
import 'thirdpage.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const appTitle = 'Drawer Demo';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: appTitle,
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
late TabController controller;
#override
void initState() {
super.initState();
controller = TabController(length: 3, vsync: this);
controller.addListener(() {
setState(() {});
});
}
#override
void dispose() {
super.initState();
controller.dispose();
super.dispose();
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
leading: Icon(Icons.menu),
title: Text('Tab ${controller.index + 1}'),
centerTitle: true,
backgroundColor: Colors.purple,
elevation: 20,
//titleSpacing: 0,
bottom: TabBar(
controller: controller,
tabs: const [
Tab(
text: 'Home',
icon: Icon(Icons.home),
),
Tab(
text: 'Page1',
icon: Icon(Icons.star),
),
Tab(
text: 'Page2',
icon: Icon(Icons.person),
),
],
),
),
body: TabBarView(
controller: controller,
children: const [
HomePage(),
SecondPage(),
ThirdPage(),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add, size: 32),
onPressed: () {
controller.animateTo(0);
}
),
);
}
}
You should declare _MyHomePageState class inside
build widget
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
But you did inside void dispose() {} Method.
Related
I am having this issue github link. What happens is if a widget uses TickerProviderStateMixin then it gets rebuilt when a page navigation occurs. I have a very complex page and rebuilding the whole page causes a UI jank on page navigation. If I do not rebuild then everything is fine no janks. Is there a workaround for this? It seems to me that this is some sort of an internal flutter bug or unexpected behaviour?
Example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: PageA(title: 'Flutter Demo Home Page'),
);
}
}
class PageA extends StatefulWidget {
PageA({Key key, this.title}) : super(key: key);
final String title;
#override
_PageAState createState() => _PageAState();
}
class _PageAState extends State<PageA>
with SingleTickerProviderStateMixin {
TabController tabController;
#override
void initState() {
super.initState();
tabController = TabController(length: 2, vsync: this);
}
void toPageB() {
//tabController.animateTo(1);
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return PageB();
}));
}
#override
Widget build(BuildContext context) {
print("Page A");
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: TabBar(
tabs: [
Text(
"Tab A",
style: Theme.of(context).textTheme.bodyText1,
),
Text(
"Tab B",
style: Theme.of(context).textTheme.bodyText1,
)
],
controller: tabController,
),
),
floatingActionButton: FloatingActionButton(
onPressed: toPageB,
child: Icon(Icons.add),
),
);
}
}
class PageB extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Page B");
return Scaffold(
appBar: AppBar(
title: Text("Page A"),
),
body: Container(
child: Center(
child: Text("Page A"),
),
));
}
}
#override
// ignore: must_call_super
void didChangeDependencies() {}
just add the code to prevent the rebuild, I dont know the side effect, but this walk around works for my app.
This is the solution I used before.
Change your Page A like
class PageA extends StatefulWidget {
PageA({Key key, this.title}) : super(key: key);
final String title;
#override
_PageAState createState() => _PageAState();
}
class _PageAState extends State<PageA> {
TabController tabController;
// #override
// void initState() {
// super.initState();
// tabController = TabController(length: 2, vsync: this);
// }
void toPageB() {
tabController.animateTo(1);
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return PageB();
}));
}
#override
Widget build(BuildContext context) {
print("Page A");
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: CustomTabBar(
tabs: [
Text(
"Tab A",
style: Theme.of(context).textTheme.bodyText1,
),
Text(
"Tab B",
style: Theme.of(context).textTheme.bodyText1,
)
],
controller: (controller) {
tabController = controller;
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: toPageB,
child: Icon(Icons.add),
),
);
}
}
And add a new class CustomTabBar
class CustomTabBar extends StatefulWidget {
const CustomTabBar({
this.controller,
this.tabs,
Key? key,
}) : super(key: key);
final Function(TabController)? controller;
final List<Widget>? tabs;
#override
_CustomTabBarState createState() => _CustomTabBarState();
}
class _CustomTabBarState extends State<CustomTabBar>
with SingleTickerProviderStateMixin {
late TabController tabController;
#override
void initState() {
super.initState();
tabController =
TabController(length: widget.tabs?.length ?? 0, vsync: this);
if (widget.controller != null) {
widget.controller!(tabController);
}
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return TabBar(
tabs: widget.tabs ?? [],
controller: tabController,
);
}
}
It should fix the issue that Page A rebuild
How is it possible to add new page in pageview widget while I am swiping ?
I tried to setstate after animation and adding new page in list but it doesn't works.
Try this snippet:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
List<String> _list = ['test', 'test', 'test'];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TabBar Demo'),
),
body: PageView.builder(
onPageChanged: (index) {
_list.add('test');
setState(() {});
},
itemCount: _list.length,
itemBuilder: (context, index) {
return Center(
child: Container(
child: Text(
_list[index],
),
),
);
},
),
);
}
}
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
PageController _pageController = PageController(
initialPage: 0,
);
int currentIndex = 0;
Widget childWidget = ChildWidget(
number: AvailableNumber.First,
);
#override
void dispose() {
_pageController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
selectedItemColor: Theme.of(context).primaryColor,
unselectedItemColor: Colors.grey[500],
currentIndex: currentIndex,
onTap: (value) {
currentIndex = value;
_pageController.animateToPage(
value,
duration: Duration(milliseconds: 200),
curve: Curves.linear,
);
setState(() {});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("First"),
),
BottomNavigationBarItem(
icon: Icon(Icons.trending_up),
title: Text("Second"),
),
BottomNavigationBarItem(
icon: Icon(Icons.dashboard),
title: Text("Third"),
),
BottomNavigationBarItem(
icon: Icon(Icons.dashboard),
title: Text("Third"),
),
],
),
body: PageView(
controller: _pageController,
onPageChanged: (page) {
setState(() {
currentIndex = page;
});
},
children: <Widget>[
Text('1'),
Text(2'),
Text('3'),
],
),
);
}
}
video example
I need to show a hint/tooltip for the userto indicate the user can get his current location by pressing the button. Have included the Tooltip in the code but only when the user does a long press of the button the tooltip is appearing, i want the tooltip to appear when the screen is initialized.
Code:
GlobalKey _toolTipKey = GlobalKey();
GestureDetector(
onTap: () {
final dynamic tooltip = _toolTipKey.currentState;
tooltip.ensureTooltipVisible();
},
child: Tooltip(
key: _toolTipKey,
message: 'Get current Location',
child: CircleAvatar(
radius: 30,
child: IconButton(
onPressed: getLocation,
icon: Icon(
Icons.my_location,
color: Colors.white,
),
),
),
),
)
I recently had to implement the same thing and I after lot of trying I managed to get it working.
You can use stateful widget and call the function to show tooltip in its initState. Now I got the same error as another person.
The method ensureTooltipVisible was called on null.
To solve this, I had to call
await Future.delayed(Duration(milliseconds: 10));
before ensureTooltipVisible() function.
#override
void initState() {
super.initState();
showTooltipIfOnboadingComplete();
}
and the function to show and close tooltip after certain amount of time,
Future showAndCloseTooltip() async {
await Future.delayed(Duration(milliseconds: 10));
tooltipkey.currentState.ensureTooltipVisible();
await Future.delayed(Duration(seconds: 4));
tooltipkey.currentState.deactivate();
}
you will also have to set you Tooltip widget trigger mode as TooltipTriggerMode.manual,
Here is complete 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 FloatingSupportButton()
);
}
}
class FloatingSupportButton extends StatefulWidget {
const FloatingSupportButton({Key? key}) : super(key: key);
#override
State<FloatingSupportButton> createState() => _FloatingSupportButtonState();
}
class _FloatingSupportButtonState extends State<FloatingSupportButton> {
// final GlobalKey<TooltipState> tooltipkey = GlobalKey<TooltipState>();
final tooltipkey = GlobalKey<State<Tooltip>>();
#override
void initState() {
super.initState();
showAndCloseTooltip();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Align(
alignment: Alignment.center,
child: Tooltip(
message: "Hello",
triggerMode: TooltipTriggerMode.manual,
key: tooltipkey,
preferBelow: false,
child: FloatingActionButton(
child: const Icon(Icons.add),
shape: const CircleBorder(
side: BorderSide(
color: Colors.white,
),
),
backgroundColor: const Color(0xFFc60c0c),
onPressed: () {
showAndCloseTooltip();
},
),
),
),
);
}
Future showAndCloseTooltip() async {
await Future.delayed(const Duration(milliseconds: 10));
// tooltipkey.currentState.ensureTooltipVisible();
final dynamic tooltip = tooltipkey.currentState;
tooltip?.ensureTooltipVisible();
await Future.delayed(const Duration(seconds: 4));
// tooltipkey.currentState.deactivate();
tooltip?.deactivate();
}
}
Have a great day everyone, hope this was helpful !!
You should use Statefulwidget and in initState write below code
import 'package:flutter/scheduler.dart';
#override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) {
// Flutter get callback here when screen initialized.
final dynamic tooltip = _toolTipKey.currentState;
tooltip.ensureTooltipVisible();
});
}
Here the Full Source code When you run the app it directly shows "Get current Location" tooltip
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
MyHomePage({Key? key, required this.title}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
GlobalKey _toolTipKey = GlobalKey();
#override
void initState() {
super.initState();
SchedulerBinding.instance!.addPostFrameCallback((_) {
// Flutter get callback here when screen initialized.
final dynamic tooltip = _toolTipKey.currentState;
tooltip.ensureTooltipVisible();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Tooltip(
key: _toolTipKey,
message: 'Get current Location',
child: CircleAvatar(
radius: 30,
child: IconButton(
onPressed: () {},
icon: Icon(
Icons.my_location,
color: Colors.white,
),
),
),
),
],
),
),
);
}
}
basically I have a swiping screen with elements, where user is able to swipe in left or right direction. When the user is swiping, im calling some functions. Im using GestureDetector for gesture recognitions and PageView.Custom for my items. Probably ListView.Custom does also work, but it doesn't fix my issue I have.
I need a PageController, because I have to control the navigation programatically. And I think the PageController maybe is the reason behind my issue that my functions are called multiple times. How to fix it? Does somebody know why setstate is called that often and what to do to prevent it?
Im providing you a fully working example (minified version) with a print on the swiping right actions, where you can see that its beeing called multiple times.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.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',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentIndex = 0;
#override
Widget build(BuildContext context) {
// Page selector for tab list
void _selectPage(int index) {
print('page index: $index');
setState(() {
currentIndex = index;
});
}
// Routes list for tab navigation Android
final List<Widget> _pages = [
ScreenA(),
ScreenB(func: _selectPage),
];
return Scaffold(
appBar: AppBar(),
body: _pages[currentIndex],
bottomNavigationBar: SafeArea(
child: BottomNavigationBar(
onTap: _selectPage,
iconSize: 22,
currentIndex: currentIndex,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.description),
label: 'ScreenA',
),
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.ac_unit_outlined),
label: 'ScreenB'),
],
),
),
);
}
}
class ScreenA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Text('HOME'),
);
}
}
class ScreenB extends StatefulWidget {
Function func;
ScreenB({Key key, #required this.func})
: super(key: key);
#override
_ScreenBState createState() => _ScreenBState();
}
class _ScreenBState extends State<ScreenB> {
_ScreenBState();
var _controller = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.access_alarm_sharp),
onPressed: () async {
widget.func(0);
},
),
],
),
body: PageView.custom(
dragStartBehavior: DragStartBehavior.start,
controller: _controller,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
childrenDelegate: SliverChildBuilderDelegate((ctx, pageIndex) =>
GestureDetector(
onPanUpdate: (details) async {
if (details.delta.dx < 0) {
_controller.nextPage(
duration: Duration(milliseconds: 200),
curve: Curves.easeInOut);
print('function called');
}
},
child: Center(
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: Text('hi')))))),
);
}
}
Thanks in advance!
The problem is that you are using the onPanUpdate method, which is triggered every time a user drags their finger either right or left. You should use the onPanEnd method, which is only triggered when the user's finger is off the screen after dragging either left or right. The function below will work fine.
onPanEnd: (details) async { if (details.velocity.pixelsPerSecond.dx < 0) { _controller.nextPage( duration: Duration(milliseconds: 200), curve: Curves.easeInOut); print('function called'); } }
Please write this function out of widget build function
// Page selector for tab list
void _selectPage(int index) {
print('page index: $index');
setState(() {
currentIndex = index;
});
}
like this
import 'package:flutter/gestures.dart';
import 'package:flutter/material.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',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentIndex = 0;
// Page selector for tab list
void _selectPage(int index) {
print('page index: $index');
setState(() {
currentIndex = index;
});
}
// Routes list for tab navigation Android
final List<Widget> _pages = [
ScreenA(),
ScreenB(func: _selectPage),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: _pages[currentIndex],
bottomNavigationBar: SafeArea(
child: BottomNavigationBar(
onTap: _selectPage,
iconSize: 22,
currentIndex: currentIndex,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.description),
label: 'ScreenA',
),
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.ac_unit_outlined),
label: 'ScreenB'),
],
),
),
);
}
}
class ScreenA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Text('HOME'),
);
}
}
class ScreenB extends StatefulWidget {
Function func;
ScreenB({Key key, #required this.func})
: super(key: key);
#override
_ScreenBState createState() => _ScreenBState();
}
class _ScreenBState extends State<ScreenB> {
_ScreenBState();
var _controller = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.access_alarm_sharp),
onPressed: () async {
widget.func(0);
},
),
],
),
body: PageView.custom(
dragStartBehavior: DragStartBehavior.start,
controller: _controller,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
childrenDelegate: SliverChildBuilderDelegate((ctx, pageIndex) =>
GestureDetector(
onPanUpdate: (details) async {
if (details.delta.dx < 0) {
_controller.nextPage(
duration: Duration(milliseconds: 200),
curve: Curves.easeInOut);
print('function called');
}
},
child: Center(
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: Text('hi')))))),
);
}
}
When i invoke the loadingDelete method upon deleting a post where the Navigator.push.. takes place, i am directed to the Profile page but with my bottom navigation bar empty (appearing empty where icons(content) are blank).
I keep on encountering this problem when i either upload or delete a post..I tried replacing scaffold with MaterialApp but did't work...
This is where my loadingDelete method resides:
class PostStateless extends StatelessWidget {
final Post post2;
PostStateless(this.post2);
#override
Widget build(BuildContext context) {
print("REACHED HERE BEG "+post2.toString());
return new Scaffold(
resizeToAvoidBottomInset: false,
body:PostPage(post2),
);
}
}
class PostPage extends StatefulWidget {
final Post post2;
PostPage(this.post2);
#override
PostPageState createState() => new PostPageState(post2);
}
class PostPageState extends State<PostPage> with TickerProviderStateMixin {
...
..
loadingDelete()
{
if(!loadingDeletePost)
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("lib/assets/BackToEarth.jpg"),
fit: BoxFit.cover,
),
),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min, //centered things bil nos
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(Colors.black),
)
]),
/* add child content here */
));
else {
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) =>
Profile()), (Route<dynamic> route) => false);
//Navigator.push alone redirects me to the profile page with blank nav bar plus arrow back
visible in my app bar(Worse).
}
}
....
}
This is my Profile page structure:
class Profile extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ProfilePage(),
);
}
}
class ProfilePage extends StatefulWidget {
#override
_ProfilePageState createState() => new _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
...
...
}
This is the structure of my global bottom nav bar which resides in my mainn dart file under MaterialApp Widget:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Instagram',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
ManageUserModel user;
class _MyHomePageState extends State<MyHomePage> {
ApiService apiService = ApiService();
List<Widget> pages = [
HomePage(),
SearchPage(),
Post(),
NotificationsPage(),
ProfilePage()
];
saveVariable(ManageUserModel user) async {
// save variable
SharedPreferences sharedUser = await SharedPreferences.getInstance();
String userSt=toJson(user);
print("USERST: "+userSt);
sharedUser.setString('user', userSt);
}
#override
void initState() {
apiService.getUsers("beeso").then((result) {
setState(() {
user = result;
print("USERRRR" +user.toString());
saveVariable(user);
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 5,
initialIndex: 0,
child: Scaffold(
body: TabBarView(
children: pages,
),
bottomNavigationBar:
Container(
margin: EdgeInsets.only(bottom: 20),
child: new TabBar(
tabs: [
Tab(
icon: Icon(Icons.home),
),
Tab(
icon: Icon(Icons.search),
),
Tab(
icon: Icon(Icons.add),
),
Tab(
icon: Icon(Icons.favorite),
),
Tab(
icon: Icon(Icons.perm_identity),
),
],
unselectedLabelColor: Colors.black,
labelColor: Colors.blue,
indicatorColor: Colors.transparent,
),
),
),
);
}
}
Any help is appreciated!!
Change the navigation to the following code:
Navigator.of(context).popUntil((route) => route.isFirst);