Flutter Tabbar NoSuchMethodError: NoSuchMethodError - dart

I am building an app using flutter that has a TabBar that is used to filter a listview by categories. However, when the TabBar is initiated it throws the following error:
flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following NoSuchMethodError was thrown building _TabStyle(animation: kAlwaysDismissedAnimation,
flutter: dirty, state: _AnimatedState#71498):
flutter: The method 'withAlpha' was called on null.
flutter: Receiver: null
flutter: Tried calling: withAlpha(178).....
Code was working fine initially. But the simulator now no longer renders the tapBar at all. Instead the simulator has a error stating that the bottom overflowed by 99870 pixels
class LocationListWidget extends StatefulWidget {
final List<Location> listOfLocations;
#override
State<StatefulWidget> createState() => new LocationListView();
LocationListWidget(this.listOfLocations);
}
class LocationListView extends State<LocationListWidget>
with SingleTickerProviderStateMixin {
TabController _controller;
static const List<TopButtons> typeList = const <TopButtons>[
const TopButtons(title: "Places", icon: Icons.directions_walk),
const TopButtons(title: "Shop", icon: Icons.shop),
const TopButtons(title: "Vineyards", icon: Icons.local_drink),
const TopButtons(title: "Dining", icon: Icons.local_dining),
const TopButtons(title: "Cafes", icon: Icons.local_cafe),
const TopButtons(
title: "Stay",
icon: Icons.home,
)
];
List<Location> listOfLocations;
List<Location> fliteredlistOfLocations;
#override
void initState() {
super.initState();
listOfLocations = this.widget.listOfLocations;
fliteredlistOfLocations = new List();
_controller = new TabController(length: 5, vsync: this, initialIndex: 1);
_controller.addListener(updateList);
updateList();
}
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Location"),
bottom:
TabBar(controller: _controller, isScrollable: true, tabs: <Tab>[
Tab(text: typeList[0].title, icon: Icon(typeList[0].icon)),
Tab(text: typeList[1].title, icon: Icon(typeList[1].icon)),
Tab(text: typeList[2].title, icon: Icon(typeList[2].icon)),
Tab(text: typeList[3].title, icon: Icon(typeList[3].icon)),
Tab(text: typeList[4].title, icon: Icon(typeList[4].icon)),
]),
),
body: SafeArea(
child: ListView.builder(
itemExtent: 100.0,
padding: const EdgeInsets.all(10.0),
itemCount: fliteredlistOfLocations.length,
itemBuilder: (BuildContext ctxt, int index) =>
buildBody(ctxt, index))));
}

I ran into a very similar issue. Here is how I fixed it:
I've added unselectedLabelColor: Colors.red, property in the TabBar widget. This stopped the error and tabs were working as expected.
In my case I've traced the issue back to ThemeData. I've implemented a custom theme for my app and this was the cause for this error. I've commented my custom theme and everything worked perfectly without any errors and without the need to add unselectedLabelColor.
Here is my final code:
import 'package:flutter/material.dart';
class AgendaScreen extends StatefulWidget {
#override
_AgendaScreenState createState() => _AgendaScreenState();
}
class _AgendaScreenState extends State
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
}
#override
Widget build(BuildContext context) => Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Theme.of(context).primaryColor,
title: Text('Agenda', style: Theme.of(context).textTheme.title),
),
body: Column(
children: [
Container(
child: TabBar(
controller: _tabController,
indicatorColor: Colors.red,
unselectedLabelColor: Colors.red,
tabs: [
Tab(
child: Text(
'Audi 01',
style: TextStyle(color: Colors.black),
),
),
Tab(
child: Text(
'Audi 02',
style: TextStyle(color: Colors.black),
),
),
],
),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
ListView(
children: [
Row(
children: [],
)
],
),
Icon(Icons.directions_transit)
],
),
),
],
),
);
}

Related

Unhandled Exception: Bad state: No element

My build was working fine until I added an image to the launch screen, when I started getting a PhaseScript error. I had to completely delete the ios folder, recreate it, and then make the necessary manual changes again. Now when I run the build on a simulator through android studio, I get this error once the app starts:
[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: Bad state: No element
#0 List.first (dart:core-patch/growable_array.dart:339:5)
#1 main (package:globe_flutter/main.dart:19:25)
<asynchronous suspension>
Nothing shows up on the screen. I use a tabBar in the main.dart and a tabBarView to show the different screens. Here is the main.dart:
var firstCamera;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();
// Get a specific camera from the list of available cameras.
firstCamera = cameras.first;
await Firebase.initializeApp();
runApp(MyApp());
}
// void main() async {
// WidgetsFlutterBinding.ensureInitialized();
// await Firebase.initializeApp();
// runApp(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: 'GLOBE',
debugShowCheckedModeBanner: false,
home: const MyHome(),
routes: {
LoginScreen.id: (context) => LoginScreen(),
SignupScreen.id: (context) => SignupScreen(),
HomeScreen.id: (context) => HomeScreen()
},
);
}
}
class MyHome extends StatefulWidget {
const MyHome({Key? key}) : super(key: key);
#override
_MyHomeState createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> with SingleTickerProviderStateMixin {
late TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(length: 5, vsync: this);
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: TabBarView(
children: [
HomeScreen(),
HomeScreen(),
TakePictureScreen(camera: firstCamera),
HomeScreen(),
ProfileScreen(username: "cjmofficial")
],
controller: _tabController
),
extendBody: true,
bottomNavigationBar: Container(
color: Colors.transparent,
padding: EdgeInsets.symmetric(vertical: 40, horizontal: 40),
child: ClipRRect(
clipBehavior: Clip.hardEdge,
borderRadius: BorderRadius.circular(50.0),
child: Container(
height: 55,
color: Colors.grey[200],
child: TabBar(
labelColor: Colors.teal[200],
unselectedLabelColor: Colors.blueGrey,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(color: Colors.teal),
insets: EdgeInsets.fromLTRB(50, 0, 50, 40)
),
indicatorColor: Colors.teal,
tabs: [
Tab(icon: Icon(Icons.home_outlined)),
Tab(icon: Icon(Icons.explore_outlined)),
Tab(icon: Icon(Icons.camera_alt_outlined)),
Tab(icon: Icon(Icons.movie_outlined)),
Tab(icon: Icon(Icons.person_outline))
],
controller: _tabController),
),
),
),
);
}
}
and the first screen that is supposed to show in the TabBarView:
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
static const String id = "home_screen";
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Color _likeButtonColor = Colors.black;
Widget _buildPost(String username, String imageUrl, String caption, String pfpUrl) {
return Container(
color: Colors.white,
child: Column(
children: [
GestureDetector(
onTap: (){},
child: Container(
height: 50,
color: Colors.deepOrangeAccent[100],
child: Row(
children: [
SizedBox(width: 5),
CircleAvatar(
child: ClipOval(
child: Image.network(
pfpUrl,
height: 40,
width: 40,
),
),
),
SizedBox(width: 10),
Icon(Icons.more_vert, size: 20),
SizedBox(width: 10),
Text(username, style: TextStyle(fontSize: 15))
],
),
),
),
Stack(
children: [
Image.asset("images/post_background.jpg"),
Padding(
padding: const EdgeInsets.all(20.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image.network(imageUrl, fit: BoxFit.cover)),
),
],
),
Container(
height: 100,
child: Column(
children: [
const SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: () {
setState(() {
HapticFeedback.lightImpact();
});
},
icon: Icon(Icons.thumb_up_alt_outlined, size: 30)),
Text("l", style: TextStyle(fontSize: 30)),
IconButton(
onPressed: () {
setState(() {
HapticFeedback.lightImpact();
GallerySaver.saveImage(imageUrl);
});
},
icon: Icon(Icons.ios_share, size: 30))
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(caption, style: const TextStyle(fontSize: 15))
],
)
],
),
)
],
),
);
}
List<Post> listPosts = [];
fetchPosts() async {
final userRef = FirebaseFirestore.instance.collection('users');
final QuerySnapshot result = await userRef.get();
result.docs.forEach((res) async {
print(res.id);
QuerySnapshot posts = await userRef.doc(res.id).collection("posts").get();
posts.docs.forEach((res) {
listPosts.add(Post.fromJson(res.data() as Map<String, dynamic>));
});
// Other method
// listPosts = posts.docs.map((doc) => Post.fromJson(doc.data() as Map<String, dynamic>)).toList();
setState(() {});
});
setState(() {
listPosts.sort((a, b) => a.postedDate.compareTo(b.postedDate));
});
}
pfpUrl(String username) async {
// Get pfpUrl
String downloadURL = await FirebaseStorage.instance
.ref(username + "/profile_picture.png")
.getDownloadURL();
return downloadURL;
}
#override
void initState() {
fetchPosts();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(45.0),
child: AppBar(
leading:
Icon(Icons.monetization_on_outlined, color: Colors.blueGrey),
centerTitle: true,
title: Text("GLOBE", style: TextStyle(color: Colors.blueGrey)),
actions: [
Icon(Icons.search, color: Colors.blueGrey),
SizedBox(width: 10)
],
backgroundColor: Colors.tealAccent[100]),
),
body: ListView.builder(
itemCount: listPosts.length,
itemBuilder: (BuildContext context, int index) {
// We retrieve the post at index « index »
final post = listPosts[index];
// Get name from id
var parts = post.id.split('_');
var username = parts[0].trim();
// Get pfpUrl
String pfpUrlString = "https://play-lh.googleusercontent.com/IeNJWoKYx1waOhfWF6TiuSiWBLfqLb18lmZYXSgsH1fvb8v1IYiZr5aYWe0Gxu-pVZX3";
return _buildPost(username, post.postUrlString, post.caption, pfpUrlString);
}),
);
}
}
I'm new to flutter and all I can understand about this is that something cannot load. I also ran a build with verbose but it didn't give anymore information. If someone can help me understand the issue, that would be great.
Ios simulators do not have cameras so on line 19, when firstCamera = cameras.first is called, there is no first camera.

Is it possible to animate the transition between the tabs of a CupertinoTabScaffold?

I'm using the sample below (taken from the CupertinoTabScaffold documentation page).
There is a "slide" transition when pushing a new route inside the tab, but when I click on a tabbar item, the content is brutally replaced. How can I have a transition when switching between tabs?
I would like implement something like that: https://github.com/Interactive-Studio/TransitionableTab
CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.home),
title: Text("Tab 0"),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.news),
title: Text("Tab 1"),
),
],
),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
builder: (BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Page 1 of tab $index'),
),
child: Center(
child: CupertinoButton(
child: const Text('Next page'),
onPressed: () {
Navigator.of(context).push(
CupertinoPageRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Page 2 of tab $index'),
),
child: Center(
child: CupertinoButton(
child: const Text('Back'),
onPressed: () { Navigator.of(context).pop(); },
),
),
);
},
),
);
},
),
),
);
},
);
},
)
May be this package will be help you page_transition: ^2.0.5
https://pub.dev/packages/page_transition
Try this -
It has info on how to animate tabBarView but should be similar to CupertinoTabView
class MyApp2 extends StatefulWidget {
#override
_MyApp2State createState() => _MyApp2State();
}
class _MyApp2State extends State<MyApp2> with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
_tabController = TabController(length: 4, vsync: this);
super.initState();
}
_tabBarView() {
return AnimatedBuilder(
animation: _tabController.animation,
builder: (BuildContext context, snapshot) {
return Transform.rotate(
angle: _tabController.animation.value * pi,
child: [
Container(
color: Colors.blue,
),
Container(
color: Colors.orange,
),
Container(
color: Colors.lightGreen,
),
Container(
color: Colors.red,
),
][_tabController.animation.value.round()],
);
},
);
}
_bottomTabBar() {
return TabBar(
controller: _tabController,
labelColor: Colors.black,
tabs: [
Tab(
icon: new Icon(Icons.home),
),
Tab(
icon: new Icon(Icons.public),
),
Tab(
icon: new Icon(Icons.group),
),
Tab(
icon: new Icon(Icons.person),
)
],
);
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(title: const Text('Bottom App Bar')),
body: _tabBarView(),
bottomNavigationBar: _bottomTabBar(),
),
);
}
}
The above code is taken from the following link-
Flutter - Change the animation of TabBarView

OpenFlutter/flutter_oktoast shows on main page only?

I am using this library to show custom toast in my app. I have multiple pages in my app. The problem is, toast appears on the main page even when I call showToastWidget(...) from any other pages.
Main Page
#override
Widget build(BuildContext context) {
return OKToast(
child: Scaffold(
backgroundColor: Theme.of(context).accentColor,
body: Center(
child: SizedBox(
height: 50,
width: 50,
child: Image(image: AssetImage('assets/ic_logo.png')),
),
),
),
);
}
Page 2
#override
Widget build(BuildContext context) {
return OKToast(
child: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Reset Password'),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: FlatButton(
onPressed: () {
showToastWidget(
Text('Hello. I am Toast!!!'),
duration: Duration(seconds: 2),
);
},
child: Text('Show'),
),
),
),
),
);
}
When I call showToastWidget(...) from this page, it appears on Main Page
EDIT 1
I get this exception when I pass context to showToastWidget()
I/flutter (24327): The following NoSuchMethodError was thrown while handling a gesture:
I/flutter (24327): The getter 'position' was called on null.
I/flutter (24327): Receiver: null
I/flutter (24327): Tried calling: position
I/flutter (24327):
I/flutter (24327): When the exception was thrown, this was the stack:
I/flutter (24327): #0 Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:50:5)
I/flutter (24327): #1 showToastWidget (package:oktoast/src/toast.dart:210:40)
Looks like the OKToast library does not support multiple OKToast widgets in the same app. You will have to wrap your entire app in an OKToast widget, full sample:
import 'package:flutter/material.dart';
import 'package:oktoast/oktoast.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return OKToast(
child: MaterialApp(
home: MainPage(),
),
);
}
}
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).accentColor,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
FlatButton(
child: Text("Show"),
onPressed: () {
showToast(
"Main Page toast",
duration: Duration(seconds: 2),
);
},
),
SizedBox(height: 12.0),
FlatButton(
child: Text("Go to next page"),
onPressed: () => _goToNextPage(context),
),
],
),
),
);
}
void _goToNextPage(BuildContext context) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(),
),
);
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("Second Page"),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: FlatButton(
onPressed: () {
showToast(
"Second Page toast",
duration: Duration(seconds: 2),
);
},
child: Text('Show'),
),
),
),
);
}
}
I am the author of OKToast, I am very glad that you use this library.
This tip of Toast is based on the design of toast in android on the mobile side.
The inspiration for OKToast itself comes from toast, so it is inevitable that it has received an impact, not adapting to the individual page-level Toast.
If you need a page-level OKToast component, you might need to do this:
import 'package:flutter/material.dart';
import 'package:oktoast/oktoast.dart';
import 'package:simple_widget/simple_widget.dart';
class CategoryPage extends StatefulWidget {
#override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
#override
Widget build(BuildContext context) {
return OKToast(
child: SimpleScaffold(
title: "CategoryPage",
actions: <Widget>[
Builder(
builder: (ctx) => IconButton(
icon: Icon(Icons.trip_origin),
onPressed: () {
showToast("page toast", context: ctx); // use context to show toast in the page-level OKToast.
},
),
),
],
child: Container(),
),
);
}
}

Using tabbar with animated list results in duplicate global key error

I am trying to implement Flutter's Tab Bar with 3 tabs and an AnimatedList inside those tabs. I want to use the same list and filter the list according to each tab (past tasks, today's tasks, and future tasks), however during my implementation of the tab bar together with the animatedlist I am getting an error regarding a duplicate global key in the widget tree. https://pastebin.com/iAW6DH9m . What would be the best way to deal with this error? Thank you for any help.
edit: I tried using this method to fix this. Multiple widgets used the same GlobalKey while it did fix my error I was then unable to access "currentstate" method on the key to be able to add more items to the list. I then tried a similar method using using GlobalKey and it resulted in a similar error of duplicate global keys.
This is my tab bar implementation
import 'package:flutter/material.dart';
import 'search_widget.dart';
import 'animatedlist_widget.dart';
class Dashboard extends StatefulWidget {
#override
_DashboardState createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
centerTitle: true,
actions: <Widget>[
new IconButton(icon: new Icon(Icons.grid_on), onPressed: null)
],
title: new Text('Dashboard'),
elevation: 0,
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
_onFabPress(context);
},
child: new Icon(Icons.add)),
body: Scaffold(
appBar: new SearchWidget(
onPressed: () => print('implement search'),
icon: Icons.search,
title: 'Search',
preferredSize: Size.fromHeight(50.0),
),
body: DefaultTabController(
length: 3,
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: Container(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: new TabBar(
unselectedLabelColor: Colors.black45,
labelColor: Colors.white,
indicator: CustomTabIndicator(),
tabs: <Widget>[
new Tab(text: "Past"),
new Tab(text: "Today"),
new Tab(text: "Future")
]),
),
),
),
body: new TabBarView(
children: <Widget>[
AnimatedTaskList(),
AnimatedTaskList(),
AnimatedTaskList()
],
)
),
),
),
);
}
void _onFabPress(context) {
AnimatedTaskList().addUser();
}
/*showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return Container(
child: new Wrap(children: <Widget>[
new TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter Task Title')),
new TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter Task Details',
)),
]));
});
}*/
}
class CustomTabIndicator extends Decoration {
#override
BoxPainter createBoxPainter([onChanged]) {
// TODO: implement createBoxPainter
return new _CustomPainter(this, onChanged);
}
}
class _CustomPainter extends BoxPainter {
final CustomTabIndicator decoration;
_CustomPainter(this.decoration, VoidCallback onChanged)
: assert(decoration != null),
super(onChanged);
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
// TODO: implement paint
assert(configuration != null);
assert(configuration.size != null);
final indicatorHeight = 30.0;
final Rect rect = Offset(
offset.dx, (configuration.size.height / 2) - indicatorHeight / 2) &
Size(configuration.size.width, indicatorHeight);
final Paint paint = Paint();
paint.color = Colors.blueAccent;
paint.style = PaintingStyle.fill;
canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(30)), paint);
}
}
This is my animatedlist class:
import 'package:flutter/material.dart';
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
class AnimatedTaskList extends StatefulWidget {
void addUser() {
int index = listData.length;
listData.add(
TaskModel(
taskTitle: "Grocery Shopping",
taskDetails: "Costco",
),
);
_listKey.currentState
.insertItem(index, duration: Duration(milliseconds: 500));
}
#override
_AnimatedTaskListState createState() => _AnimatedTaskListState();
}
class _AnimatedTaskListState extends State<AnimatedTaskList> {
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: SafeArea(
child: AnimatedList(
key: _listKey,
initialItemCount: listData.length,
itemBuilder:
(BuildContext context, int index, Animation animation) {
return Card(
child: FadeTransition(
opacity: animation,
child: ListTile(
title: Text(listData[index].taskTitle),
subtitle: Text(listData[index].taskDetails),
onLongPress: () {
//todo delete user
},
)));
})),
);
}
}
class TaskModel {
TaskModel({this.taskTitle, this.taskDetails});
String taskTitle;
String taskDetails;
}
List<TaskModel> listData = [
TaskModel(
taskTitle: "Linear Algebra",
taskDetails: "Chapter 4",
),
TaskModel(
taskTitle: "Physics",
taskDetails: "Chapter 9",
),
TaskModel(
taskTitle: "Software Construction",
taskDetails: "Architecture",
),
];
I fixed my issue by moving
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
into my _AnimatedTaskListState class, and adding a constructor and private key to my AnimatedTaskList class
final GlobalKey<AnimatedListState> _key;
AnimatedTaskList(this._key);
#override
_AnimatedTaskListState createState() => _AnimatedTaskListState(_key);
then in my tab bar implementation I changed it to reflect my new constructor
AnimatedTaskList(GlobalKey<AnimatedListState>(debugLabel: "key 1"));
AnimatedTaskList(GlobalKey<AnimatedListState>(debugLabel: "key 2"));
AnimatedTaskList(GlobalKey<AnimatedListState>(debugLabel: "key 3"));

Flutter: Changing the current tab in tab bar view using a button

I am creating an app that contains a tab bar on its homepage. I want to be able to navigate to one of the tabs using my FloatingActionButton. In addition, I want to keep the default methods of navigating to that tab, i.e. by swiping on screen or by clicking the tab.
I also want to know how to link that tab to some other button.
Here is a screenshot of my homepage.
You need to get the TabBar controller and call its animateTo() method from the button onPressed() handle.
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 MyTabbedPage(),
);
}
}
class MyTabbedPage extends StatefulWidget {
const MyTabbedPage({Key key}) : super(key: key);
#override
_MyTabbedPageState createState() => new _MyTabbedPageState();
}
class _MyTabbedPageState extends State<MyTabbedPage> with SingleTickerProviderStateMixin {
final List<Tab> myTabs = <Tab>[
new Tab(text: 'LEFT'),
new Tab(text: 'RIGHT'),
];
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = new TabController(vsync: this, length: myTabs.length);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Tab demo"),
bottom: new TabBar(
controller: _tabController,
tabs: myTabs,
),
),
body: new TabBarView(
controller: _tabController,
children: myTabs.map((Tab tab) {
return new Center(child: new Text(tab.text));
}).toList(),
),
floatingActionButton: new FloatingActionButton(
onPressed: () => _tabController.animateTo((_tabController.index + 1) % 2), // Switch tabs
child: new Icon(Icons.swap_horiz),
),
);
}
}
If you use a GlobalKey for the MyTabbedPageState you can get the controller from any place, so you can call the animateTo() from any button.
class MyApp extends StatelessWidget {
static final _myTabbedPageKey = new GlobalKey<_MyTabbedPageState>();
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyTabbedPage(
key: _myTabbedPageKey,
),
);
}
}
You could call it from anywhere doing:
MyApp._myTabbedPageKey.currentState._tabController.animateTo(...);
I am super late, but hopefully someone benefits from this. just add this line to your onPressed of your button and make sure to change the index number to your preferred index:
DefaultTabController.of(context).animateTo(1);
You can use TabController:
TabController _controller = TabController(
vsync: this,
length: 3,
initialIndex: 0,
);
_controller.animateTo(_currentTabIndex);
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _controller,
tabs: [
...
],
),
),
body: TabBarView(
controller: _controller,
children: [
...
],
),
);
And than, setState to update screen:
int _currentTabIndex = 0;
setState(() {
_currentTabIndex = 1;
});
chemamolin's answer above is correct, but for additional clarification/tip, if you want to call your tabcontroller "from anywhere", also make sure the tabcontroller is not a private property of the class by removing the underscore, otherwise the distant class will not be able to see the tabcontroller with the example provided even when using the GlobalKey.
In other words, change
TabController _tabController;
to:
TabController tabController;
and change
MyApp._myTabbedPageKey.currentState._tabController.animateTo(...);
to:
MyApp._myTabbedPageKey.currentState.tabController.animateTo(...);
and everywhere else you reference tabcontroller.
If you want to jump to a specific page, you can use
PageController.jumpToPage(int)
However if you need animation, you'd use
PageController.animateToPage(page, duration: duration, curve: curve)
Simple example demonstrating it.
// create a PageController
final _controller = PageController();
bool _shouldAnimate = true; // whether we animate or jump
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () {
if (_shouldAnimate) {
// animates to page1 with animation
_controller.animateToPage(1, duration: Duration(seconds: 1), curve: Curves.easeOut);
} else {
// jump to page1 without animation
_controller.jumpToPage(1);
}
},
),
body: PageView(
controller: _controller, // assign it to PageView
children: <Widget>[
FlutterLogo(colors: Colors.orange), // page0
FlutterLogo(colors: Colors.green), // page1
FlutterLogo(colors: Colors.red), // page2
],
),
);
}
DefaultTabController(
length: 4,
initialIndex: 0,
child: TabBar(
tabs: [
Tab(
child: Text(
"People",
style: TextStyle(
color: Colors.black,
),
),
),
Tab(
child: Text(
"Events",
style: TextStyle(
color: Colors.black,
),
),
),
Tab(
child: Text(
"Places",
style: TextStyle(
color: Colors.black,
),
),
),
Tab(
child: Text(
"HashTags",
style: TextStyle(
color: Colors.black,
),
),
),
],
),
)
i was trying to solve similar issue but passing methods or controllers down the widget tree wasn't a clean option for me. i had requirement to go back to tabbed page from other non-tabbed routes (back to specific tabs).
following solution worked for me
Inside tabbed page: read route arguments
#override
Widget build(BuildContext context) {
final String? tabId = Get.arguments;
_selectedTabIndex = tabId !=null? int.parse(tabId): 0;
return Scaffold(
....
body: _pages[_selectedPageIndex]['page'] as Widget,
bottomNavigationBar: BottomNavigationBar(
onTap: _selectPage,
....);
}
Now the calling page
onSubmit:() { // or some other event
// do something here
Get.offAndToNamed(Routes.homeTabs,
arguments: TabIndex.specialTab.index.toString());
//Routes is a const & TabIndex is enum defined somewhere
}
A solution with TabController + Streams
Pass a stream into the state object. Pass the new tab index through the stream for the state to update itself. Here's how I'm doing it.
import 'package:flutter/material.dart';
class TabsWidget extends StatefulWidget {
const TabsWidget({Key? key, this.tabs = const [], this.changeReceiver}) : super(key: key);
final List<Tab> tabs;
// To change the tab from outside, pass in the tab index through a stream
final Stream<int>? changeReceiver;
#override
State<TabsWidget> createState() => _TabsWidgetState();
}
class _TabsWidgetState extends State<TabsWidget> with SingleTickerProviderStateMixin {
int _index = 0;
late TabController _tabController;
#override
void initState() {
_tabController = TabController(length: widget.tabs.length, vsync: this, initialIndex: _index);
// Listen to tab index changes from external sources via this stream
widget.changeReceiver?.listen((int newIndex) {
setState(() {
_index = newIndex;
_tabController.animateTo(newIndex);
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
if (widget.tabs.isEmpty) return const SizedBox.shrink(); // If no tabs, show nothing
return TabBar(tabs: widget.tabs, controller: _tabController, );
}
}
// Sample usage - main
import 'dart:async';
import 'package:flutter/material.dart';
import 'tabs_widget.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final StreamController<int> tabChangeNotifier = StreamController();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tab Change Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('Tab Change Demo'),
),
body: SingleChildScrollView(child: Column(
children: [
const SizedBox(height: 30,),
Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
ElevatedButton(onPressed: () => tabChangeNotifier.add(0), child: const Text('Go Orange')),
ElevatedButton(onPressed: () => tabChangeNotifier.add(1), child: const Text('Go Red')),
ElevatedButton(onPressed: () => tabChangeNotifier.add(2), child: const Text('Go Green')),
],),
const SizedBox(height: 30,),
TabsWidget(changeReceiver: tabChangeNotifier.stream, tabs: const [
Tab(icon: Icon(Icons.circle, color: Colors.orange,),),
Tab(icon: Icon(Icons.circle, color: Colors.red,),),
Tab(icon: Icon(Icons.circle, color: Colors.green,),),
],),
],
),), // This trailing comma makes auto-formatting nicer for build methods.
),
);
}
#override
void dispose() {
tabChangeNotifier.close();
super.dispose();
}
}
This is how the above sample looks.
Use DefaultTabController instead of a local TabController, high enough in your widget tree, and then you'll have access to it from anywhere in that sub tree.
Widget build(BuildContext context) {
return DefaultTabController(
initialIndex: initialIndex,
length: tabs.length,
child: SizedBox( // From here down you have access to the tab controller
width: double.infinity,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SomeWidget(), // Has access to the controller
TabBar(
controller: DefaultTabController.of(context),
tabs:
tabs.map((tab) => Tab(child: Text(tab.title, style: const TextStyle(color: Colors.black)))).toList(),
),
Expanded(
child: TabBarView(
controller: DefaultTabController.of(context),
children: tabs.map((tab) => tab.widget).toList(),
),
),
],
),
),
);
}
In any point in that tree, you can access the tab controller with DefaultTabController.of(context) and change the tab, like so:
DefaultTabController.of(context)?.animateTo(0);
class Tab bar
class TabBarScreen extends StatefulWidget {
TabBarScreen({Key key}) : super(key: key);
#override
_TabBarScreenState createState() => _TabBarScreenState();
}
final List<Tab> tabs = <Tab>[
Tab(text: 'Page1'),
Tab(text: 'Page2'),
];
class _TabBarScreenState extends State<TabBarScreen> with SingleTickerProviderStateMixin {
TabController tabController;
#override
void initState() {
super.initState();
tabController = new TabController(vsync: this, length: tabs.length);
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
appBar: AppBar(
backgroundColor: Theme.of(context).primaryColor,
centerTitle: true,
shape: Border(bottom: BorderSide(color: Colors.white)),
title: Text("Tab Bar",),
bottom: TabBar(
controller: tabController,
tabs: tabs,
indicatorWeight: 5,
indicatorColor: Colors.white,
labelColor: Colors.white,
),
),
body: TabBarView(
controller: tabController,
children: [
PageOneScreen(controller: tabController),
PageTwoScreen(controller: tabController),
],
),
),
);
}
}
class PageOne
class PageOneScreen extends StatefulWidget {
#override
_PageOneScreenState createState() => _PageOneScreenState();
PageOneScreen({controller}) {
tabController = controller;
}
}
TabController tabController;
class _PageOneScreenState extends State<PageOneScreen> {
#override
Widget build(BuildContext context) {
return Column(
children: [
RaisedButton(
onPressed: () {
tabController.animateTo(1); // number : index page
},
child: Text(
"Go To Page 2",
),
),
],
);
}
}

Resources