Change pages of my navigation bar pressing a button - flutter

I have a NavigationBar with 3 tabs and i want to change the current tab or destination when i push on a button.
The Navigation Bar on the class Home:
body:IndexedStack(
index: currentPageIndex,
children: <Widget>[
const ShopPage(),
QRScanner(showTab2: () {
setState(() {
setState(() {
currentPageIndex = 2;
});
});
}),
const ShopListPage(),
],
),
bottomNavigationBar: NavigationBar(
elevation: 2,
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
selectedIndex: currentPageIndex,
onDestinationSelected: (int index) {
setState(() => currentPageIndex = index);
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.store_outlined),
selectedIcon: Icon(Icons.store),
label: 'Tienda',
),
NavigationDestination(
icon: Icon(Icons.qr_code_scanner_outlined),
selectedIcon: Icon(Icons.qr_code_scanner),
label: 'Escaner',
),
NavigationDestination(
icon: Icon(Icons.shopping_cart_outlined),
selectedIcon: Icon(Icons.shopping_cart),
label: 'Carrito',
),
],
),
The function with the show dialog on the QRScanner page
void onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
});
controller.scannedDataStream.listen((scanData) {
setState(() {
result = scanData;
});
controller.pauseCamera();
Stream<List<Products>> readProducts() => FirebaseFirestore.instance
.collection('products')
.where('id', isEqualTo: '${result!.code}')
.snapshots()
.map((snapshot) => snapshot.docs
.map((doc) => Products.fromJson(doc.data()))
.toList());
Future<bool> onWillPop() async {
return false;
}
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) => WillPopScope(
onWillPop: () async => false,
child: WillPopScope(
onWillPop: onWillPop,
child: Dialog(
insetPadding: const EdgeInsets.all(8),
child: StreamBuilder<List<Products>>(
stream: readProducts(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Algo ha ocurrido! ${snapshot.error}');
} else if (snapshot.hasData) {
final products = snapshot.data!;
return Container(
margin: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: products
.map((p) => BuildQRCards( // This is the class which contain the button that should change tab on pressed
products: p, controller: controller,))
.toList()),
);
} else {
return const Center(child: CircularProgressIndicator());
}
}),
),
),
),
);
});
}
The BuildQRCard Class:
class _BuildQRCardsState extends State<BuildQRCards> {
#override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.antiAlias,
child: SizedBox(
width: 300,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Image(image: NetworkImage(widget.products.imageUrl)),
ListTile(
title: Text(widget.products.name),
subtitle: Text('Precio: ${widget.products.price}\$'),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 8, 8),
child: ElevatedButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(
const Color(0xFFFFFFFF),
),
backgroundColor: MaterialStateProperty.all<Color>(
const Color(0xFF6750A4),
),
),
onPressed: () {
Navigator.pop(context);
widget.controller!.resumeCamera();
},
child: const Text(
'Cancelar',
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 8, 8),
child: ElevatedButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(
const Color(0xFFFFFFFF),
),
backgroundColor: MaterialStateProperty.all<Color>(
const Color(0xFF6750A4),
),
),
onPressed: () => showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => AlertDialog(
content: Text(
'¿Desea añadir el producto ${widget.products.name} a la lista de compras?',
textAlign: TextAlign.center,
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Cancelar')),
TextButton(
onPressed: () {
passID(widget.products.id);
widget.controller!.resumeCamera();
},
child: const Text('Añadir'),
),
],
),
),
child: const Text(
'Añadir al Carro',
),
),
),
],
),
],
),
),
);
}
}

new answer
This has two approaches
Using a function and using result from navigator.pop
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(home: NavigationExample());
}
}
class NavigationExample extends StatefulWidget {
const NavigationExample({super.key});
#override
State<NavigationExample> createState() => _NavigationExampleState();
}
class _NavigationExampleState extends State<NavigationExample> {
int currentPageIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: NavigationBar(
onDestinationSelected: (int index) {
setState(() {
currentPageIndex = index;
});
},
selectedIndex: currentPageIndex,
destinations: const <Widget>[
NavigationDestination(
icon: Icon(Icons.explore),
label: 'Explore',
),
NavigationDestination(
icon: Icon(Icons.commute),
label: 'Commute',
),
NavigationDestination(
selectedIcon: Icon(Icons.bookmark),
icon: Icon(Icons.bookmark_border),
label: 'Saved',
),
],
),
body: <Widget>[
MyPage1(
showTab2: () {
// how to use a function when they are in the same tab list
setState(() {
setState(() {
currentPageIndex = 2;
});
});
},
),
Container(
color: Colors.green,
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Page 2'),
TextButton(
onPressed: () async {
// how to use the result
final int? result =
await Navigator.push(context, MaterialPageRoute(
builder: (context) {
return MyNewPage();
},
));
if (result != null) {
setState(() {
currentPageIndex = result;
});
}
},
child: Text("new page"))
],
),
),
Container(
color: Colors.blue,
alignment: Alignment.center,
child: const Text('Page 3'),
),
][currentPageIndex],
);
}
}
class MyPage1 extends StatelessWidget {
final Function showTab2;
const MyPage1({Key? key, required this.showTab2}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Page 1'),
TextButton(
onPressed: () {
showTab2();
},
child: Text("Show tab 3"))
],
),
);
}
}
class MyNewPage extends StatelessWidget {
const MyNewPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: TextButton(
onPressed: () => showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => AlertDialog(
content: Text(
'¿Desea añadir el producto ${'widget.products.name'} a la lista de compras?',
textAlign: TextAlign.center,
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context);
Navigator.pop(context);
},
child: const Text('Cancelar')),
TextButton(
onPressed: () {
// passID(widget.products.id);
// widget.controller!.resumeCamera();
Navigator.pop(context);
Navigator.pop(context, 0);
},
child: const Text('Añadir'),
),
],
),
),
child: Text("Show dialog"),
),
)),
);
}
}
/// END OF NEW ANSWER
//old answer
This line is what changes the tabs
setState(() {
setState(() => this.index = index);
});
Hence if you want to change the tab through a different button just add something like this
setState(() {
setState(() => this.index = newTabIndex);
});
where newTabIndex is the index of the tap you want to
in your case, you might have something like this
TextButton(
onPressed: () {
passID(products.id);
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
BuildListCards(
products: products)));
controller!.resumeCamera();
setState(() {
setState(() => this.index = 1); // I am assuming 1 is the index of the tab you want to show
});
},
child: const Text('Añadir')),

Related

Flutter|Updating a ListView

everyone!
I have a HomeScreen, with this code:
return SafeArea(
child: Scaffold(
body: Stack(
children: [
Padding(
padding: const EdgeInsets.only(left: 8, right: 8),
child: Column(children: [
ActiveTaskInfo(
task: tasks.first,
),
const TextWidget(),
Expanded(child: TasksList()),
const SizedBox(height: 80),
]),
),
BottomBarClass(),
],
),
),
);
TasksList() - ListView.
BottomBarClass() - It is a container with a button inside.
If you return the code itself to the main HomeScreen file, everything works, but if you put it in a separate class and when you add a new item to the list (through the button) nothing happens, but if you press Hot Reload, then the new item in the list is displayed.
Code BottomBarClass():
Positioned(
bottom: 0, left: 0,
child: ClipRRect(
borderRadius: const BorderRadius.only(topRight: Radius.circular(30), topLeft: Radius.circular(30)),
child: Container(
height: 80, width: MediaQuery.of(context).size.width,
color: const Color(0xff070417),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Icon(Icons.watch_later, color: Colors.white.withOpacity(0.4)),
IconButton(icon: Icon(Icons.add, color: Colors.white.withOpacity(0.4),), iconSize: 32, onPressed: () async {
bool result = await Navigator.push(context, MaterialPageRoute(builder: (context) {
return TaskAdding();
}));
if (result == true) {
setState(() {
});
}
}),
Icon(Icons.pie_chart_rounded, color: Colors.white.withOpacity(0.4), size: 24,),
],),
),
));
Вот пример GIF: https://gifyu.com/image/Spp1O
TaskAdding():
import 'package:flutter/material.dart';
import '../expansions/task_tags_decorations.dart';
import '../expansions/tasks_data.dart';
class TaskAdding extends StatefulWidget {
const TaskAdding({Key? key}) : super(key: key);
#override
State<TaskAdding> createState() => _TaskAddingState();
}
class _TaskAddingState extends State<TaskAdding> {
late String _addField;
late Widget _selectedValue;
late bool _active;
late int _selectedIndex;
#override
void initState() {
_addField = 'Empty';
_active = false;
_selectedValue = tasks[0].icon;
_selectedIndex = 0;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Add'), backgroundColor: Colors.pink),
body: Column(children: [
Text('Add Task', style: TextStyle(color: Colors.white, fontSize: 24),),
TextField(onChanged: (String value) {
_addField = value;
}),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
DropdownButton<Widget>(
value: _selectedValue,
onChanged: (newValue) {
setState(() {
_selectedValue = newValue!;
});
},
items: dropdownItems,
),
ElevatedButton(
onPressed: () {
setState(() {
tasks.addAll({
TaskData(
taskName: _addField,
tagOne: _active
? tasks[0].tagOne
: tasks[1].tagOne,
tagTwo: tagTwoContainer[_selectedIndex],
icon: _selectedValue,
taskTime: '00:32:10',
)
});
decorations.addAll({
TaskTagsDecorations(
iconColor: const Color(0xff7012CF))
});
});
Navigator.of(context).pop(true);
},
child: const Text('Add')),
],
),
Center(
child: ListTile(
title: _active
? Center(
child: tasks[0].tagOne,
)
: Center(child: tasks[1].tagOne),
selected: _active,
onTap: () {
setState(() {
_active = !_active;
});
},
),
),
SizedBox(
height: 52,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: tagTwoContainer.length,
itemBuilder: (context, index) {
var tagTwoList = tasks[index].tagTwo;
return SizedBox(
height: MediaQuery.of(context).size.height, width: 160,
child: ListTile(
visualDensity: VisualDensity.compact,
selected: index == _selectedIndex,
selectedTileColor: Colors.indigo.withOpacity(0.6),
title: Align(
alignment: Alignment.topCenter,
child: tagTwoList),
onTap: () {
setState(() {
_selectedIndex = index;
});
},
),
);
}),
),
],),
);
}
List<DropdownMenuItem<Widget>> get dropdownItems {
List<DropdownMenuItem<Widget>> menuItems = [
DropdownMenuItem(
child: const Icon(Icons.free_breakfast),
value: iconCircle[0],
),
DropdownMenuItem(
child: const Icon(Icons.grade_outlined), value: iconCircle[1]),
DropdownMenuItem(child: const Icon(Icons.gamepad), value: iconCircle[2]),
DropdownMenuItem(
child: const Icon(Icons.face_rounded), value: iconCircle[3]),
];
return menuItems;
}
}
You question is not clear. Try to add TasksList()
My solution:
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return HomeScreen();
}));

How to route from notification in flutter app?

I am using awesome_notification package for the notifications.
Actually, I have a custom bottom navigation bar and I am using screen[selectedIndex] to change the content of the body inside the scaffold. I guess that is what causing all the problem. Please suggest me a way out.
Whenever I am trying to navigate from my notification I am getting this error.
The code:
// ignore_for_file: use_key_in_widget_constructors
import 'package:awesome_notifications/awesome_notifications.dart';
import 'package:flutter/material.dart';
import 'package:notiminder/pages/done.dart';
import 'package:notiminder/pages/history.dart';
import 'package:notiminder/pages/reminder.dart';
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
void initState() {
super.initState();
AwesomeNotifications().isNotificationAllowed().then((isAllowed) {
if (!isAllowed) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text(
'Allow Notifications',
),
content: const Text(
'Notiminder app would like to send you notifications',
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text("Don't Allow"),
),
TextButton(
onPressed: () => AwesomeNotifications()
.requestPermissionToSendNotifications()
.then(
(_) => Navigator.pop(context),
),
child: const Text(
'Allow',
style: TextStyle(
fontStyle: FontStyle.italic,
color: Colors.teal,
),
),
),
],
),
);
}
});
AwesomeNotifications().createdStream.listen((notification) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Reminder created for you'),
),
);
});
AwesomeNotifications().actionStream.listen((event) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => HomePage(),
),
(route) => route.settings.name == screen[1].toString(),
// (route) => route.isFirst,
);
});
}
int selectedIndex = 0;
final screen = [
ReminderPage(),
DonePage(),
HistoryPage(),
];
#override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return GestureDetector(
onTap: () {
final FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
child: SafeArea(
child: Scaffold(
// resizeToAvoidBottomInset: false,
appBar: AppBar(
title: const Text('Notiminder'),
centerTitle: true,
),
body: screen[selectedIndex],
bottomNavigationBar: Row(
children: [
SizedBox(
width: size.width,
height: 80,
child: Stack(
children: [
CustomPaint(
size: Size(size.width, 80),
painter: BNBCustomPainter(),
),
Center(
heightFactor: 0.6,
child: FloatingActionButton(
onPressed: () {
setState(() {
selectedIndex = 0;
});
},
child: const Icon(Icons.notifications, size: 30),
),
),
SizedBox(
width: size.width,
height: 80,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
buttomBarItems(
Icons.done_outline_rounded,
1,
),
Container(
width: size.width * 0.20,
),
buttomBarItems(
Icons.history_rounded,
2,
),
],
),
),
],
),
),
],
),
),
),
);
}
Specifically the notification route:
AwesomeNotifications().actionStream.listen((event) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => HomePage(),
),
(route) => route.settings.name == screen[1].toString(),
// (route) => route.isFirst,
);
});
}

I want to use flutter to create a list that can be sorted

I'm working on a list in flutter that can be reordered.
However, I am getting an error.
I thought showDialog and builder were the cause of the error, so I turned off showDialog and tried again, but the error still occurred.
How to solve this problem or
How do I create a List using ReorderableListView.builder and showDialog?
I do not understand English, so some words and sentences may be wrong.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() {
// 最初に表示するWidget
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
// 右上に表示される"debug"ラベルを消す
debugShowCheckedModeBanner: false,
// アプリ名
title: 'My Todo App',
theme: ThemeData(
// テーマカラー
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
// リスト一覧画面を表示
home: TodoListPage(),
);
}
}
// リスト一覧画面用Widget
class TodoListPage extends StatefulWidget {
#override
_TodoListPageState createState() => _TodoListPageState();
}
class _TodoListPageState extends State<TodoListPage> {
// Todoリストのデータ
List<String> todoList = [];
void reorderData(int oldindex, int newindex) {
setState(() {
if (newindex > oldindex) {
newindex -= 1;
}
final items = todoList.removeAt(oldindex);
todoList.insert(newindex, items);
});
}
void sorting() {
setState(() {
todoList.sort();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
// AppBarを表示し、タイトルも設定
appBar: AppBar(
title: Text('List list'),
),
// データを元にListViewを作成
body: ReorderableListView.builder(
itemCount: todoList.length,
itemBuilder: (context, index) {
return Column(
children: <Widget>[
for (final items in todoList)
GestureDetector(
onTap: () async {
// ダイアログを表示------------------------------------
showDialog(
context: context,
builder: (BuildContext context) {
return Column(
children: <Widget>[
AlertDialog(
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Column(
children: <Widget>[
TextButton.icon(
icon: const Icon(
Icons.add,
color: Colors.black,
),
label: const Text('Edit'),
onPressed: () {
sorting();
},
onLongPress: () async {
var morenewText =
await Navigator.of(context)
.push(
MaterialPageRoute(
builder: (context) =>
TodoAddPage(
todoList[index]),
),
);
setState(() {
todoList[index] = morenewText;
});
},
),
ElevatedButton(
child: const Text('Delete'),
onPressed: () {
setState(() {});
todoList.removeAt(index);
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
primary: Colors.blue,
),
),
ElevatedButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
],
),
),
),
],
);
},
);
},
key: ValueKey(items),
child: ListTile(
title: Text(todoList[index]),
),
),
],
);
},
onReorder: reorderData,
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
// "push"で新規画面に遷移
// リスト追加画面から渡される値を受け取る
var newListText = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
// 遷移先の画面としてリスト追加画面を指定
return TodoAddPage(null);
}),
);
if (newListText != null) {
// キャンセルした場合は newListText が null となるので注意
setState(() {
// リスト追加
todoList.add(newListText);
});
}
},
child: Icon(Icons.add),
),
);
}
}
class TodoAddPage extends StatefulWidget {
final oldnama;
TodoAddPage(this.oldnama);
#override
_TodoAddPageState createState() => _TodoAddPageState();
}
class _TodoAddPageState extends State<TodoAddPage> {
var newname = '';
#override
Widget build(BuildContext context) {
if (widget.oldnama != null) {
newname = widget.oldnama;
}
return Scaffold(
appBar: AppBar(
title: Text('Add list'),
),
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// 入力されたテキストを表示
const SizedBox(height: 8),
// テキスト入力
TextFormField(
//テキスト入力の初期値を決める
initialValue: newname,
// 入力されたテキストの値を受け取る(valueが入力されたテキスト)
onChanged: (String value) {
// データが変更したことを知らせる(画面を更新する)
// データを変更
newname = value;
},
),
const SizedBox(height: 8),
Container(
// 横幅いっぱいに広げる
width: double.infinity,
// リスト追加ボタン
child: ElevatedButton(
onPressed: () {
// "pop"で前の画面に戻る
// "pop"の引数から前の画面にデータを渡す
Navigator.of(context).pop(newname);
},
child: Text('Add list', style: TextStyle(color: Colors.white)),
),
),
const SizedBox(height: 8),
Container(
// 横幅いっぱいに広げる
width: double.infinity,
// キャンセルボタン
child: TextButton(
// ボタンをクリックした時の処理
onPressed: () {
// "pop"で前の画面に戻る
Navigator.of(context).pop();
},
child: Text('Cancel'),
),
),
],
),
),
);
}
}
You're using itemBuilder: wrong. Update it using this...
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(todoList[index]),
onTap: () {
// ダイアログを表示------------------------------------
showDialog(
context: context,
builder: (BuildContext context) {
return Column(
children: <Widget>[
AlertDialog(
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Column(
children: <Widget>[
TextButton.icon(
icon: const Icon(
Icons.add,
color: Colors.black,
),
label: const Text('Edit'),
onPressed: () {
sorting();
},
onLongPress: () async {
var morenewText =
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
TodoAddPage(todoList[index]),
),
);
setState(() {
todoList[index] = morenewText;
});
},
),
ElevatedButton(
child: const Text('Delete'),
onPressed: () {
setState(() {});
todoList.removeAt(index);
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
primary: Colors.blue,
),
),
ElevatedButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
],
),
),
),
],
);
},
);
},
title: Text(todoList[index]),
);
},

The getter 'todoText' isn't defined for the class 'String'

I am getting the following error in my code.
The getter 'todoText' isn't defined for the class 'String'.
Try importing the library that defines 'todoText', correcting the name to the name of an existing getter, or defining a getter or field named 'todoText'.dart(undefined_getter)
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
void main() => runApp(new ToDoApp());
class ToDoApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'To Do Application',
theme: new ThemeData(
primarySwatch: Colors.teal,
),
home: new ToDoList(),
);
}
}
class ListItem{
bool _todoCheck;
ListItem(this._todoCheck);
}
class _strikeThrough extends StatelessWidget{
final String todoText;
final bool todoCheck;
_strikeThrough(this.todoText, this.todoCheck) : super();
Widget _widget(){
if(todoCheck){
return Text(
todoText,
style: TextStyle(
decoration: TextDecoration.lineThrough,
fontStyle: FontStyle.italic,
fontSize: 22.0,
color: Colors.red[200],
),
);
}
else{
return Text(
todoText,
style: TextStyle(
fontSize: 22.0
),
);
}
}
#override
Widget build(BuildContext context){
return _widget();
}
}
class ToDoList extends StatefulWidget {
#override
createState() => new _TodoListState();
}
class _TodoListState extends State<ToDoList> {
var textController = TextEditingController();
var popUpTextController = TextEditingController();
List<String> _todoItems = [];
List<ListItem> WidgetList =[];
void _addToDoItem(String task){
if(task.length > 0){
setState(()=> _todoItems.add(task));
}
}
void _removeToDoItem(int index){
setState(() => _todoItems.removeAt(index));
}
void _promptRemoveToDoItem(int index){
showDialog(context: context,
builder: (BuildContext context){
return new AlertDialog(
title: new Text('Mark "${_todoItems[index]}" as Completed?'+' This will remove this Item'),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () => Navigator.of(context).pop()
),
new FlatButton(
child: new Text('MARK AS DONE'),
onPressed: () {
_removeToDoItem(index);
Navigator.of(context).pop();
Fluttertoast.showToast(
msg: 'ToDo Task Deleted!'
);
}
),
]
);
}
);
}
Widget _buildToDoList(BuildContext context){
return Column(
children: <Widget>[
new Flexible(
child: new ListView.builder(
itemBuilder: (context,index){
return _buildToDoItem(_todoItems[index], index);
},
),
),
new Expanded(
child: ReorderableListView(
children: <Widget>[
for(final widget in _todoItems)
for(final widget1 in WidgetList)
GestureDetector(
key: Key(widget.todoText),
child: Dismissible(
key: Key(widget.todoText),
child: CheckboxListTile(
value: widget1._todoCheck,
title: _strikeThrough(widget.todoText,widget1._todoCheck),
onChanged: (checkValue){
setState(() {
if(!checkValue){
widget1._todoCheck = false;
}
else{
widget1._todoCheck = true;
}
});
}),
background: Container(
child: Icon(Icons.delete),
alignment: Alignment.centerRight,
color: Colors.orange[300],
),
confirmDismiss: (dismissDirection){
return showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context){
return AlertDialog(
title: Text("Delete Todo?"),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: (){
Navigator.of(context).pop(true);
},
),
FlatButton(
child: Text("Cancel"),
onPressed: (){
Navigator.of(context).pop(true);
},),
],
);
});
},
direction: DismissDirection.endToStart,
movementDuration: const Duration(milliseconds: 200),
onDismissed: (dismissedDirection){
WidgetList.remove(widget);
Fluttertoast.showToast(
msg: "Todo Deleted!"
);
},
),
onDoubleTap: (){
popUpTextController.text = widget.todoText;
showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context){
return AlertDialog(
title: Text("Edit ToDo"),
content: TextFormField(
controller: popUpTextController,
),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: (){
setState(() {
widget.todoText=popUpTextController.text;
});
Navigator.of(context).pop(true);
},
),
FlatButton(
child: Text("Cancel"),
onPressed: (){
Navigator.of(context).pop(false);
},
)
],
);
}
);
},
)
],
onReorder: (oldIndex, newIndex){
setState(() {
if(oldIndex < newIndex){
newIndex -= 1;
}
var replaceWiget = WidgetList.removeAt(oldIndex);
WidgetList.insert(newIndex, replaceWiget);
});
},)
)
],
);
}
Widget _buildToDoItem(String todoText, int index){
return new ListTile(
title: new Text(todoText),
onLongPress: () => _promptRemoveToDoItem(index),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('To Do List')
),
body:_buildToDoList(context),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: new FloatingActionButton(
focusElevation: 7,
onPressed:_pushAddToDoScreen,
tooltip: 'Add Task',
child: new Icon(Icons.add)
),
drawer: Drawer(
child: ListView(
padding: const EdgeInsets.all(0),
children: <Widget>[
DrawerHeader(
child: Text('To Do App Drawer Header'),
decoration: BoxDecoration(
color: Colors.teal,
)
),
ListTile(
title: Text('Add Items'),
onTap: _pushAddToDoScreen,
),
ListTile(
title: Text('Remove Items'),
subtitle: Text('Long Press on any item to remove it'),
onTap: (){
Navigator.pop(context);
},
),
ListTile(
title: Text('Check Status of Items'),
subtitle: Text('If the item is in List, then it is Pending, else long press to mark it done'),
onTap: (){
Navigator.pop(context);
},
),
],
)
),
bottomNavigationBar: BottomAppBar(
shape: CircularNotchedRectangle(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(Icons.settings),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.more_vert),
onPressed: () {},
)
]
)
)
);
}
void _pushAddToDoScreen(){
Navigator.of(context).push(
new MaterialPageRoute(
builder: (context){
return new Scaffold(
appBar: new AppBar(
title: new Text('Add a new task')
),
body: new TextField(
autofocus: true,
onSubmitted: (val){
_addToDoItem(val);
Navigator.pop(context);
},
decoration: new InputDecoration(
hintText: 'Enter Tasks....',
contentPadding: const EdgeInsets.all(16.0)
),
)
);
}
)
);
}
}

Flutter: BottomNavigationBar rebuilds Page on change of tab

I have a problem with my BottomNavigationBar in Flutter. I want to keep my page alive if I change the tabs.
here my implementation
BottomNavigation
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomeState();
}
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
List<Widget> _children;
final Key keyOne = PageStorageKey("IndexTabWidget");
#override
void initState() {
_children = [
IndexTabWidget(key: keyOne),
PlaceholderWidget(Colors.green),
NewsListWidget(),
ShopsTabWidget(),
PlaceholderWidget(Colors.blue),
];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(MyApp.appName),
textTheme: Theme.of(context).textTheme.apply(
bodyColor: Colors.black,
displayColor: Colors.blue,
),
),
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
key: IHGApp.globalKey,
fixedColor: Colors.green,
type: BottomNavigationBarType.fixed,
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(Icons.message),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(Icons.perm_contact_calendar),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Container(height: 0.0),
),
],
),
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
Column buildButtonColumn(IconData icon) {
Color color = Theme.of(context).primaryColor;
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color),
],
);
}
}
This is my index page (first tab):
class IndexTabWidget extends StatefulWidget {
IndexTabWidget({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() {
return new IndexTabState();
}
}
class IndexTabState extends State<IndexTabWidget>
with AutomaticKeepAliveClientMixin {
List<News> news = List();
FirestoreNewsRepo newsFirestore = FirestoreNewsRepo();
#override
Widget build(BuildContext context) {
return Material(
color: Colors.white,
child: new Container(
child: new SingleChildScrollView(
child: new ConstrainedBox(
constraints: new BoxConstraints(),
child: new Column(
children: <Widget>[
HeaderWidget(
CachedNetworkImageProvider(
'https://static1.fashionbeans.com/wp-content/uploads/2018/04/50-barbershop-top-savill.jpg',
),
"",
),
AboutUsWidget(),
Padding(
padding: const EdgeInsets.all(16.0),
child: SectionTitleWidget(title: StringStorage.salonsTitle),
),
StreamBuilder(
stream: newsFirestore.observeNews(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
} else {
news = snapshot.data;
return Column(
children: <Widget>[
ShopItemWidget(
AssetImage('assets/images/picture.png'),
news[0].title,
news[0],
),
ShopItemWidget(
AssetImage('assets/images/picture1.png'),
news[1].title,
news[1],
)
],
);
}
},
),
Padding(
padding: const EdgeInsets.only(
left: 16.0, right: 16.0, bottom: 16.0),
child: SectionTitleWidget(title: StringStorage.galleryTitle),
),
GalleryCategoryCarouselWidget(),
],
),
),
),
),
);
}
#override
bool get wantKeepAlive => true;
}
So if I switch from my index tab to any other tab and back to the index tab, the index tab will always rebuild. I debugged it and saw that the build function is always being called on the tab switch.
Could you guys help me out with this issue?
Thank you a lot
Albo
None of the previous answers worked out for me.
The solution to keep the pages alive when switching the tabs is wrapping your Pages in an IndexedStack.
class Tabbar extends StatefulWidget {
Tabbar({this.screens});
static const Tag = "Tabbar";
final List<Widget> screens;
#override
State<StatefulWidget> createState() {
return _TabbarState();
}
}
class _TabbarState extends State<Tabbar> {
int _currentIndex = 0;
Widget currentScreen;
#override
Widget build(BuildContext context) {
var _l10n = PackedLocalizations.of(context);
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: widget.screens,
),
bottomNavigationBar: BottomNavigationBar(
fixedColor: Colors.black,
type: BottomNavigationBarType.fixed,
onTap: onTabTapped,
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.format_list_bulleted),
title: new Text(_l10n.tripsTitle),
),
BottomNavigationBarItem(
icon: new Icon(Icons.settings),
title: new Text(_l10n.settingsTitle),
)
],
),
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
You need to wrap every root page (the first page you see when you press a bottom navigation item) with a navigator and put them in a Stack.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final int _pageCount = 2;
int _pageIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: _body(),
bottomNavigationBar: _bottomNavigationBar(),
);
}
Widget _body() {
return Stack(
children: List<Widget>.generate(_pageCount, (int index) {
return IgnorePointer(
ignoring: index != _pageIndex,
child: Opacity(
opacity: _pageIndex == index ? 1.0 : 0.0,
child: Navigator(
onGenerateRoute: (RouteSettings settings) {
return new MaterialPageRoute(
builder: (_) => _page(index),
settings: settings,
);
},
),
),
);
}),
);
}
Widget _page(int index) {
switch (index) {
case 0:
return Page1();
case 1:
return Page2();
}
throw "Invalid index $index";
}
BottomNavigationBar _bottomNavigationBar() {
final theme = Theme.of(context);
return new BottomNavigationBar(
fixedColor: theme.accentColor,
currentIndex: _pageIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.list),
title: Text("Page 1"),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle),
title: Text("Page 2"),
),
],
onTap: (int index) {
setState(() {
_pageIndex = index;
});
},
);
}
}
The pages will be rebuild but you should separate your business logic from you UI anyway. I prefer to use the BLoC pattern but you can also use Redux, ScopedModel or InhertedWidget.
Just use an IndexedStack
IndexedStack(
index: selectedIndex,
children: <Widget> [
ProfileScreen(),
MapScreen(),
FriendsScreen()
],
)
I'm not sure but CupertinoTabBar would help.
If you don't want it, this video would be great url.
import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => new _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final List<dynamic> pages = [
new Page1(),
new Page2(),
new Page3(),
new Page4(),
];
int currentIndex = 0;
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () async {
await Future<bool>.value(true);
},
child: new CupertinoTabScaffold(
tabBar: new CupertinoTabBar(
iconSize: 35.0,
onTap: (index) {
setState(() => currentIndex = index);
},
activeColor: currentIndex == 0 ? Colors.white : Colors.black,
inactiveColor: currentIndex == 0 ? Colors.green : Colors.grey,
backgroundColor: currentIndex == 0 ? Colors.black : Colors.white,
currentIndex: currentIndex,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.looks_one),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_two),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_3),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_4),
title: Text(''),
),
],
),
tabBuilder: (BuildContext context, int index) {
return new DefaultTextStyle(
style: const TextStyle(
fontFamily: '.SF UI Text',
fontSize: 17.0,
color: CupertinoColors.black,
),
child: new CupertinoTabView(
routes: <String, WidgetBuilder>{
'/Page1': (BuildContext context) => new Page1(),
'/Page2': (BuildContext context) => new Page2(),
'/Page3': (BuildContext context) => new Page3(),
'/Page4': (BuildContext context) => new Page4(),
},
builder: (BuildContext context) {
return pages[currentIndex];
},
),
);
},
),
);
}
}
class Page1 extends StatefulWidget {
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
String title;
#override
void initState() {
title = 'Page1';
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
leading: new IconButton(
icon: new Icon(Icons.text_fields),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Page13()));
},
)),
body: new Center(
child: new Text(title),
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Page2'),
leading: new IconButton(
icon: new Icon(Icons.airline_seat_flat_angled),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Page12()));
},
)),
body: new Center(
child: Column(
children: <Widget>[
CupertinoSlider(
value: 25.0,
min: 0.0,
max: 100.0,
onChanged: (double value) {
print(value);
}
),
],
),
),
);
}
}
class Page3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Page3'),
),
body: new Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new RaisedButton(
child: new Text('Cupertino'),
textColor: Colors.white,
color: Colors.red,
onPressed: () {
List<int> list = List.generate(10, (int i) => i + 1);
list.shuffle();
var subList = (list.sublist(0, 5));
print(subList);
subList.forEach((li) => list.remove(li));
print(list);
}
),
new SizedBox(height: 30.0),
new RaisedButton(
child: new Text('Android'),
textColor: Colors.white,
color: Colors.lightBlue,
onPressed: () {
var mes = 'message';
var messa = 'メッセージ';
var input = 'You have a new message';
if (input.contains(messa) || input.contains(mes)) {
print('object');
} else {
print('none');
}
}
),
],
),
),
);
}
}
class Page4 extends StatelessWidget {
static List<int> ints = [1, 2, 3, 4, 5];
static _abc() {
print(ints.last);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Page4'),
),
body: new Center(
child: new RaisedButton(
child: new Text('Static', style: new TextStyle(color: Colors.white)),
color: Colors.lightBlue,
onPressed: _abc,
)),
);
}
}
class Page12 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Page12'),
actions: <Widget>[
new FlatButton(
child: new Text('GO'),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Page13()));
},
)
],
),
body: new Center(
child: new RaisedButton(
child: new Text('Swiper', style: new TextStyle(color: Colors.white)),
color: Colors.redAccent,
onPressed: () {},
)),
);
}
}
class Page13 extends StatefulWidget {
#override
_Page13State createState() => _Page13State();
}
class _Page13State extends State<Page13> with SingleTickerProviderStateMixin {
final List<String> _productLists = Platform.isAndroid
? [
'android.test.purchased',
'point_1000',
'5000_point',
'android.test.canceled',
]
: ['com.cooni.point1000', 'com.cooni.point5000'];
String _platformVersion = 'Unknown';
List<IAPItem> _items = [];
List<PurchasedItem> _purchases = [];
#override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
String platformVersion;
try {
platformVersion = await FlutterInappPurchase.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
var result = await FlutterInappPurchase.initConnection;
print('result: $result');
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
// refresh items for android
String msg = await FlutterInappPurchase.consumeAllItems;
print('consumeAllItems: $msg');
}
Future<Null> _buyProduct(IAPItem item) async {
try {
PurchasedItem purchased = await FlutterInappPurchase.buyProduct(item.productId);
print('purchased: ${purchased.toString()}');
} catch (error) {
print('$error');
}
}
Future<Null> _getProduct() async {
List<IAPItem> items = await FlutterInappPurchase.getProducts(_productLists);
print(items);
for (var item in items) {
print('${item.toString()}');
this._items.add(item);
}
setState(() {
this._items = items;
this._purchases = [];
});
}
Future<Null> _getPurchases() async {
List<PurchasedItem> items = await FlutterInappPurchase.getAvailablePurchases();
for (var item in items) {
print('${item.toString()}');
this._purchases.add(item);
}
setState(() {
this._items = [];
this._purchases = items;
});
}
Future<Null> _getPurchaseHistory() async {
List<PurchasedItem> items = await FlutterInappPurchase.getPurchaseHistory();
for (var item in items) {
print('${item.toString()}');
this._purchases.add(item);
}
setState(() {
this._items = [];
this._purchases = items;
});
}
List<Widget> _renderInApps() {
List<Widget> widgets = this
._items
.map((item) => Container(
margin: EdgeInsets.symmetric(vertical: 10.0),
child: Container(
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 5.0),
child: Text(
item.toString(),
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
),
),
),
FlatButton(
color: Colors.orange,
onPressed: () {
print("---------- Buy Item Button Pressed");
this._buyProduct(item);
},
child: Row(
children: <Widget>[
Expanded(
child: Container(
height: 48.0,
alignment: Alignment(-1.0, 0.0),
child: Text('Buy Item'),
),
),
],
),
),
],
),
),
))
.toList();
return widgets;
}
List<Widget> _renderPurchases() {
List<Widget> widgets = this
._purchases
.map((item) => Container(
margin: EdgeInsets.symmetric(vertical: 10.0),
child: Container(
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 5.0),
child: Text(
item.toString(),
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
),
),
)
],
),
),
))
.toList();
return widgets;
}
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width-20;
double buttonWidth=(screenWidth/3)-20;
return new Scaffold(
appBar: new AppBar(),
body: Container(
padding: EdgeInsets.all(10.0),
child: ListView(
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
'Running on: $_platformVersion\n',
style: TextStyle(fontSize: 18.0),
),
),
Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.amber,
padding: EdgeInsets.all(0.0),
onPressed: () async {
print("---------- Connect Billing Button Pressed");
await FlutterInappPurchase.initConnection;
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'Connect Billing',
style: TextStyle(
fontSize: 16.0,
),
),
),
),
),
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.amber,
padding: EdgeInsets.all(0.0),
onPressed: () async {
print("---------- End Connection Button Pressed");
await FlutterInappPurchase.endConnection;
setState(() {
this._items = [];
this._purchases = [];
});
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'End Connection',
style: TextStyle(
fontSize: 16.0,
),
),
),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.green,
padding: EdgeInsets.all(0.0),
onPressed: () {
print("---------- Get Items Button Pressed");
this._getProduct();
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'Get Items',
style: TextStyle(
fontSize: 16.0,
),
),
),
)),
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.green,
padding: EdgeInsets.all(0.0),
onPressed: () {
print(
"---------- Get Purchases Button Pressed");
this._getPurchases();
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'Get Purchases',
style: TextStyle(
fontSize: 16.0,
),
),
),
)),
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.green,
padding: EdgeInsets.all(0.0),
onPressed: () {
print(
"---------- Get Purchase History Button Pressed");
this._getPurchaseHistory();
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'Get Purchase History',
style: TextStyle(
fontSize: 16.0,
),
),
),
)),
]),
],
),
Column(
children: this._renderInApps(),
),
Column(
children: this._renderPurchases(),
),
],
),
],
),
),
);
}
}
Use IndexedStack widget:
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: IndexedStack(
index: _currentIndex,
children: const [
HomePage(),
SettingsPage(),
],
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (int index) => setState(() => _currentIndex = index),
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
],
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('build home');
return Center(child: Text('Home'));
}
}
class SettingsPage extends StatelessWidget {
const SettingsPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('build settings');
return Center(child: Text('Settings'));
}
}
Make sure the IndexedStack children widget list is constant. This will prevent the widgets from rebuilding when setState() is called.
IndexedStack(
index: _currentIndex,
children: const [
HomeWidget(),
SettingsWidget(),
],
),
The problem with IndexedStack is that all the widgets will be built at the same time when IndexedStack is initialized. For small widgets (like the example above), it won't be a problem. But for big widgets, you may see some performance issues.
Consider using the lazy_load_indexed_stack package. According to the package:
[LazyLoadIndexedStack widget] builds the required widget only when it is needed, and returns the pre-built widget when it is needed again
Again, make sure the LazyLoadIndexedStack children widgets are constant, otherwise they will keep rebuilding when setState is called.
If, you just need to remember the scroll position inside a list, the best option is to simply use a PageStoreKey object for the key property:
#override
Widget build(BuildContext context) {
return Container(
child: ListView.builder(
key: PageStorageKey<String>('some-list-key'),
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () => _onElementTapped(index),
child: makeCard(items[index])
);
},
),
);
}
According to https://docs.flutter.io/flutter/widgets/PageStorageKey-class.html, this should work on ANY scrollable widget.
if i use the IndexedStack in the body it is loading only the main screen content not every other screen which is present in the bottom nativation bar.
Using IndexedStack with bloc pattern solved everthing.