Changing screens when navigating in bottomnavigation bar error - android

Hi Im new to flutter so please bear with me for asking too much nooby question. So I am currently developing an app, the first screen will be a Login/register screen then after a login or registration is directed, the actual main app screen is displayed I also set this screen as stateless and set a body to my HomePage.dart now on my HomePage.dart which is a stateful widget, which contains the navigation bar but for some reason, Im getting an error in
final List<Widget> _children [
NavHome()
];
saying that the children is initialized. And Im confused since I followed the tutorial from medium exactly BUT with just a custom main screen (which appears after the main.dart)
the code for the actual main app screen is below:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'home.dart';
class MainScreen extends StatelessWidget {
const MainScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: HomePage(),
);
}
}
the code for Home.dart is below which says the children variable isnt initialized
import 'package:flutter/material.dart';
import 'package:vmembershipofficial/screens/nav_home.dart';
class HomePage extends StatefulWidget {
static final String id = 'homepage';
HomePage({Key key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _currentTab = 2;
final List<Widget> _children [
NavHome()
];
void onTabTapped(int index) {
setState(() {
_currentTab = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _children[_currentTab],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
type: BottomNavigationBarType.fixed,
currentIndex: _currentTab, //makes a new variable called current Tab
items: [
BottomNavigationBarItem(
icon: Icon(Icons.search, size: 30.0),
title: Text('Search', style: TextStyle(fontSize: 12.0),),
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite),
title: Text('Favorites', style: TextStyle(fontSize: 12.0),),
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home', style: TextStyle(fontSize: 12.0),),
),
BottomNavigationBarItem(
icon: Icon(Icons.message),
title: Text('Messages', style: TextStyle(fontSize: 12.0),),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle),
title: Text('Account', style: TextStyle(fontSize: 12.0),),
),
],
),
);
}
}
NOTE: I just want the bottom navigation bar to change to the NavHome, or NavProfile when I tapped on different tabs. I just cant seem to find a way why the _children variable isnt initialized.

You're almost there!
What went wrong
final List<Widget> _children [ // Missing = sign
NavHome()
];
What you can do
Convert the code snippet above to:
final List<Widget> _children = [ // Add = sign here
NavHome(),
NavProfile(),
// Add more screens here
];
I created a simple app for you, which mocks your use case that you need to switch between NavHome and NavProfile.
main.dart
import 'package:flutter/material.dart';
void main() => runApp(ExampleApp());
class ExampleApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Example App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
static final String id = 'homepage';
HomePage({Key key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _currentTab = 0;
final List<Widget> _children = [
RedPage(),
BluePage(),
];
void onTabTapped(int index) {
setState(() {
_currentTab = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _children[_currentTab],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
type: BottomNavigationBarType.fixed,
currentIndex: _currentTab,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.favorite),
title: Text(
'Red',
style: TextStyle(fontSize: 12.0),
),
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text(
'Blue',
style: TextStyle(fontSize: 12.0),
),
),
],
),
);
}
}
class RedPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Container(
color: Colors.red,
),
);
}
}
class BluePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Container(
color: Colors.blue,
),
);
}
}
I have also noticed that you have 5 widgets in your bottom navigation bar, feel free to add placeholders so you won't get RangeError exception whenever you tap on tabs without the counterpart screen/s.
Hope this helps.

Related

Flutter change GNav tab from Button

I have 4 pages in a GNav() Bottom-Navigation-Bar and I would like to navigate to different page via click of a button.
I am aware that there might be similar questions on stackoverflow already, however I was unable to implement any of them successfully which is why I am posting here as my "last effort".
First off, my Class RootPage() which is where I am defining my GNav bar and the pages linked to each tab.
import 'package:anitan/pages/menu_bar_pages/profile_page.dart';
import 'package:anitan/pages/menu_bar_pages/search_page.dart';
import 'package:anitan/pages/menu_bar_pages/settings_pages/settings_page.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:google_nav_bar/google_nav_bar.dart';
import 'home_page.dart';
class RootPage extends StatefulWidget {
RootPage({super.key});
#override
State<RootPage> createState() => _RootPageState();
}
class _RootPageState extends State<RootPage>{
int currentPage = 0;
List<Widget> pages = const [
HomePage(),
ProfilePage(),
SearchPage(),
SettingsPage()
];
final user = FirebaseAuth.instance.currentUser!;
#override
Widget build(BuildContext context) {
return Scaffold(
body: pages[currentPage],
bottomNavigationBar: Container(
color: Colors.green,
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 15.0, vertical: 15),
child: GNav(
backgroundColor: Colors.green,
color: Colors.white,
activeColor: Colors.white,
tabBackgroundColor: Colors.green.shade800,
gap: 8,
onTabChange: (index) => setState(() => currentPage = index),
selectedIndex: currentPage,
padding: const EdgeInsets.all(16),
tabs: const [
GButton(icon: Icons.home, text: "Home"),
GButton(icon: Icons.person, text: "My Page"),
GButton(icon: Icons.search, text: "Browse"),
GButton(icon: Icons.settings, text: "Settings"),
],
),
),
),
);
}
}
Next up, the HomePage which has a button. When clicked, I want to change the selected tab of my GNav bar. Pushing the page etc. will lead to the Navigation Bar disappearing which is why this isn't an option.
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key,});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text("Home Page"),
automaticallyImplyLeading: false,
leading: IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: const Icon(Icons.arrow_back_ios),
),
actions: [
IconButton(
onPressed: () {
debugPrint("appBar Button");
},
icon: const Icon(Icons.info_outline),
),
],
),
body: SafeArea(
child: ElevatedButton(onPressed: (() => print("Change Page Here")), child: Text("Change-Page")),
),
);
}
}
I tried a few things like implementing a StreamController or using a GlobalKey. But I would like to the GNav Bar instead of using a Tab controller because of its design.
In theory, what I am trying to do seems simple:
I would like to currentPage to a new index and call setState() to show the changes.
Can anyone help me understand how I can access change the index and update the selected page in my RootPage?
I have spent 2 days looking into various solutions but I can't get any of them to work.
Many thanks!
Edit:
Please find the code of my main.dart below.
import 'package:anitan/pages/menu_bar_pages/root_page.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
//import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.green),
home: RootPage(),
);
}
}
HomePage is already a part of RootPage which is needed to be on the same route.You can use callback method to handle click-event/changing page.
class HomePage extends StatefulWidget {
final VoidCallback onTap;
const HomePage({
super.key,
required this.onTap,
});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child:
ElevatedButton(onPressed: widget.onTap, child: Text("Change-Page")),
),
);
}
}
Now you will get a callback method while creating HomePage
int currentPage = 0;
List<Widget> pages = [
HomePage(onTap:(){
// page
} ),
];
Given the reply from Yeasin Sheikh I implemented a little workaround which seems to be working fine:
First, as recommended by Yeasin, I added a VoidCallback function onTap to my HomePage class:
class HomePage extends StatefulWidget {
final VoidCallback onTap;
const HomePage({super.key, required this.onTap});
#override
State<HomePage> createState() => _HomePageState();
}
Then I called this onTap function clicking the button:
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child:
ElevatedButton(onPressed: widget.onTap(), child: Text("Change-Page")),
),
);
}
}
In my rootPage class, I then created my HomePage.
When the onTap function of that page is being called, I am calling a method to refresh the page (setState) and changing the currentPage index as part of it.
This required me to initialize the List of my pages using "late".
class _RootPageState extends State<RootPage>{
int currentPage = 0;
late List<Widget> pages = [
HomePage(onTap:(){
_changePage(2);
} ),
const ProfilePage(),
const SearchPage(),
const SettingsPage()
];
_changePage(int id){
setState(() {
currentPage = id;
});
}
To keep everything centralized, I switched to using the same method to update the index in the onTabChange part of the GNav bar as well.
onTabChange: (index) => setState(() => _changePage(index)),
Clicking the button now switches the page.
The bottom navigation bar (GNav) will stay on the screen and can be used as usual.
Thank you for your support in figuring this out!

The relevant error-causing widget was Scaffold when the exception was thrown, this was the stack

class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
final user = FirebaseAuth.instance.currentUser!;
int index = 0;
final pages = [
Home(),
Add(),
Setting(),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: pages[index],
bottomNavigationBar: NavigationBarTheme(
data: NavigationBarThemeData(
indicatorColor: Colors.purpleAccent.withOpacity(0.5),
labelTextStyle: MaterialStateProperty.all(const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
))),
child: NavigationBar(
backgroundColor: Colors.white,
animationDuration: const Duration(seconds: 1),
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
height: 70,
selectedIndex: index,
onDestinationSelected: (index) {
setState(() {
this.index = index;
});
},
destinations: const [
NavigationDestination(
selectedIcon: Icon(Icons.home),
icon: Icon(Icons.home_outlined),
label: '主頁',
),
NavigationDestination(
selectedIcon: Icon(Icons.add),
icon: Icon(Icons.add_outlined),
label: '加入',
),
NavigationDestination(
selectedIcon: Icon(Icons.settings),
icon: Icon(Icons.settings_outlined),
label: '設定',
),
],
),
),
);
}
}
The following StackOverflowError was thrown building _BodyBuilder:
Stack Overflow
The relevant error-causing widget was Scaffold
lib/Pages/home.dart:26
When the exception was thrown, this was the stack
As I am new in Flutter, can anyone tell me why am I getting this error?
Thanks.
The main issue is you are calling Home inside the pages, similar like recursive thing,
final pages = [
Text(""), // use different widget instead of Home
Add(),
Setting(),
];

Flutter ReorderableDragStartListener issue detecting gesture on mobile device (but works fine in web browser)

I want to have reorderable list in flutter with custom drag handle (that works immediately, without long press first).
To achieve that I did:
buildDefaultDragHandles: false,
and I used ReorderableDragStartListener.
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(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<int> _items = List<int>.generate(50, (int index) => index);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ReorderableListView(
buildDefaultDragHandles: false,
children: <Widget>[
for (int index = 0; index < _items.length; index++)
Container(
key: Key('$index'),
color: _items[index].isOdd ? Colors.blue[100] : Colors.red[100],
child: Row(
children: <Widget>[
Container(
width: 64,
height: 64,
padding: const EdgeInsets.all(8),
child: ReorderableDragStartListener(
index: index,
child: Card(
color: Colors.green,
elevation: 2,
),
),
),
Text('Item ${_items[index]}'),
],
),
),
],
onReorder: (int oldIndex, int newIndex) {
print('oldIndex $oldIndex, newIndex $newIndex');
},
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
On desktop (e.g. when run in Edge) it works as expected, drag handle is clicked (mouse down) and dragged up or down to change order.
The problem is on mobile device. When I tap down, and I move finger up or down - the scroll is performed. When however I tap down, and move finger little left or right, and then up/down -> then reordering happens. (tested in android emulator and real android device).
Question is - why on mobile I need to do this little annoying additional left/right move before chaining order? How to fix it?
How it works on desktop (Edge):
How it work on Android (bug!):
I solved it using custom ReorderableDragStartListener, when I set tap delay to 1ms. Since this approach does not require moving finger left/right before dragging, and 1ms is low time, it works like a charm.
code:
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
class CustomReorderableDelayedDragStartListener extends ReorderableDragStartListener {
final Duration delay;
const CustomReorderableDelayedDragStartListener({
this.delay = kLongPressTimeout,
Key? key,
required Widget child,
required int index,
bool enabled = true,
}) : super(key: key, child: child, index: index, enabled: enabled);
#override
MultiDragGestureRecognizer createRecognizer() {
return DelayedMultiDragGestureRecognizer(delay: delay, debugOwner: this);
}
}
usage:
CustomReorderableDelayedDragStartListener(
delay: const Duration(milliseconds: 1), // or any other duration that fits you
index: widget.index, // passed from parent
child: Container(
child: const Icon( // or any other graphical element
Icons.drag_handle
),
),
)
Try this
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<int> _items = List<int>.generate(20, (int index) => index);
#override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
return ReorderableListView(
buildDefaultDragHandles: false,
children: <Widget>[
for (int index = 0; index < _items.length; index++)
Container(
key: Key('$index'),
color: _items[index].isOdd ? oddItemColor : evenItemColor,
child: Row(
children: <Widget>[
Container(
width: 64,
height: 64,
padding: const EdgeInsets.all(8),
child: ReorderableDragStartListener(
index: index,
child: Card(
color: colorScheme.primary,
elevation: 2,
),
),
),
Text('Item ${_items[index]}'),
],
),
),
],
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final int item = _items.removeAt(oldIndex);
_items.insert(newIndex, item);
});
},
);
}
}

how to use bottomNavigationBar while using ListView.builder

I want to use bottomNavigationBar with listview, I tried each one of them separately and they work fine, but when I use them together, the bottomNavigationBar doesn't work, you can press the icons but nothing happens.
note: I'm using an older version of dart to avoid null-safety which is dumb, but the reason is the book I read is from 2019 so I couldn't follow without using an older version.
note 2: I'm very new to programming.
main.dart
//#dart=2.9
import 'package:flutter/material.dart';
import 'package:ch8_bottom_navigation/pages/home.dart';
void main() => runApp(Myapp());
class Myapp extends StatelessWidget {
//this widget is the root of the app
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Starter Template',
theme: ThemeData (
primarySwatch: Colors.blue,
platform: TargetPlatform.iOS,
),
home: Home(),
);
}
}
home.dart
//#dart=2.9
import 'package:flutter/material.dart';
import 'discover.dart';
import 'home2.dart';
import 'account.dart';
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
List _listPages = [];
Widget _currentPage;
#override
void initState() {
super.initState();
_listPages
..add(Home2())
..add(Discover())
..add(Account());
_currentPage = Discover();
}
void _changePage(int selectedIndex) {
setState(() {
_currentIndex = selectedIndex;
_currentPage = _listPages[selectedIndex];
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView.builder(
itemCount:20 ,
itemBuilder: (BuildContext context , int index) {
if (index >= 0 && index <= 0) {
return Home2 (index:index);
}
else return null;
},
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.live_tv),
label: ('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.explore_outlined),
label: ('Discover'),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_box_outlined),
label: ('Account'),
),
],
onTap: (selectedIndex) => _changePage(selectedIndex),
),
);
}
}
home2.dart
//#dart=2.9
import 'package:flutter/material.dart';
class Home2 extends StatelessWidget {
const Home2({Key key , #required this.index}) : super(key: key);
final int index;
#override
Widget build(BuildContext context) {
return Container(
child:Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
color: Colors.white70,
child: ListTile(
leading: Image(
image: AssetImage('assets/images/blackwidow.jpg'),
),
title: Text('Black Widow'),
subtitle: Text('By Disney'),
trailing: Icon(Icons.movie),
selected: true,
onTap: () {
print('Trapped on Row $index');
},
),
)
);
}
}
body: SafeArea(child: _currentPage),
or
body: SafeArea(child: _listPages[selectedIndex]),
Did you try this way?

Is it possible to convert an Android Fragment into Flutter Widget?

I have a Native Android Fragment and I need to use inside a flutter project.
Inside a Android Project when you need to use a fragment something like
supportFragmentManager.beginTransaction()
.replace(R.id.content_fragment, it1,
"name")
.commit()
I would like to embed this fragment along with a BottonNavigationBar (second option for example).
I tried to follow some tutorials as:
https://medium.com/flutter-community/flutter-platformview-how-to-create-flutter-widgets-from-native-views-366e378115b6
https://60devs.com/how-to-add-native-code-to-flutter-app-using-platform-views-android.html
But I wasn`t able to adapt these tutorials for fragment or even activities becase they talk about Views.
Does anyone have any suggestions?
Obs: Just to clarify, I need to use a native screen inside a flutter screen.
But you can use flutter BottomNavigationBar
here is a demo of BottomNavigationBar
and it looks same as Bottomnavigation with fragment in android
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 index = 0;
void currentindex(value) {
setState(() {
this.index = value;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: Icon(
MdiIcons.flower,
color: Colors.white,
),
title: Text(
widget.title,
style: TextStyle(color: Colors.white),
),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(MdiIcons.home),
title: Text("Home"),
),
BottomNavigationBarItem(
icon: Icon(MdiIcons.human),
title: Text("User"),
),
BottomNavigationBarItem(
icon: Icon(MdiIcons.imageAlbum),
title: Text("Theme"),
),
],
onTap: (index) => currentindex(index),
elevation: 19.0,
currentIndex: index,
),
body: Navtabwidget()[this.index],
);
}
List<Widget> Navtabwidget() {
return [
Homewidget(),
Userlistwidget(),
Settingwidget(),
];
}
}
i hope it helps
I used this, in a similar problem:
class DetailsScreen extends StatefulWidget {
#override
DetailsScreenState createState() {
return DetailsScreenState();
}
}
class DetailsScreenState extends State<DetailsScreen>
with SingleTickerProviderStateMixin {
TabController tabController;
#override
void initState() {
tabController = new TabController(length: 4, vsync: this);
super.initState();
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(67.0),
child: AppBar(
elevation: 10.0,
automaticallyImplyLeading: false,
flexibleSpace: Padding(
padding: const EdgeInsets.only(top: 0.0),
child: SafeArea(
child: getTabBar(),
),
),
),
),
body: getTabBarPages());
}
Widget getTabBar() {
return TabBar(controller: tabController, tabs: [
Tab(
text: AppLocalizations.of(context).translate("nav_dieta"),
icon: Icon(MdiIcons.silverwareForkKnife)),
Tab(
text: AppLocalizations.of(context).translate("nav_exercise"),
icon: Icon(MdiIcons.dumbbell)),
Tab(
text: AppLocalizations.of(context).translate("_news"),
icon: Icon(MdiIcons.newspaper)),
Tab(
text: AppLocalizations.of(context).translate("nav_info"),
icon: Icon(MdiIcons.accountDetails)),
]);
}
Widget getTabBarPages() {
return TabBarView(
controller: tabController,
physics: NeverScrollableScrollPhysics(),
children: <Widget>[
MealPlanScreen(),
ExerciseScreen(),
Container(color: Colors.blue),
Container(color: Colors.yellow)
]);
}
}
Where MealPlanScreen and ExerciseScreen are StatefulWidget and those two Containers will be replaced with other classes that contain StatefulWidget.

Categories

Resources