Flutter: Pass value of multiple (single value) ChoiceChip - flutter

The solution here is for single ChoiceChip
multiple ChoiceChip is needed but the index (selected) is always same
Below is the code:
It seems it need another variable to store the desired values, but not sure where to start (maybe use provider?)
class _MyHomePageState extends State<MyHomePage> {
int _selectedIndex = 0;
Widget _buildChips(options) {
List<Widget> chips = [];
for (int i = 0; i < options.length; i++) {
ChoiceChip choiceChip = ChoiceChip(
selected: _selectedIndex == i,
label: Text(options[i], style: const TextStyle(color: Colors.white)),
elevation: 3,
pressElevation: 5,
backgroundColor: Colors.grey[400],
selectedColor: Colors.lightGreen,
onSelected: (bool selected) {
setState(() {
if (selected) {
_selectedIndex = i;
}
});
},
);
chips.add(choiceChip);
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: chips,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Select A value'),
_buildChips(['Regular', 'Hard Sleeper', 'Soft Sleeper']),
const Text('Select B value'),
_buildChips(['Python', 'Flutter']),
ElevatedButton(
child: const Text('Pass selected A and B values to next screen'),
onPressed: () {print(_selectedIndex);},
),
],
),
),
);
}
}

you can use get package and GetXController
https://pub.dev/packages/get
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
Get.put(ChipController());
return GetBuilder(builder: (ChipController controller) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Select A value'),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: controller.lstType
.map((text) => ChoiceChip(
selected: controller.selectedChipAData == text,
label: Text(text,
style: const TextStyle(color: Colors.white)),
elevation: 3,
pressElevation: 5,
backgroundColor: Colors.grey[400],
selectedColor: Colors.lightGreen,
onSelected: (bool selected) {
if (selected) {
controller.setSelectedData(text);
}
},
))
.toList(),
),
const Text('Select B value'),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: controller.lstLanguage
.map((text) => ChoiceChip(
selected: controller.selectedChipBData == text,
label: Text(text,
style: const TextStyle(color: Colors.white)),
elevation: 3,
pressElevation: 5,
backgroundColor: Colors.grey[400],
selectedColor: Colors.lightGreen,
onSelected: (bool selected) {
if (selected) {
controller.setSelectedChipBData(text);
}
},
))
.toList(),
),
ElevatedButton(
child:
const Text('Pass selected A and B values to next screen'),
onPressed: () {
print(controller.selectedChipAData);
print(controller.selectedChipBData);
},
),
],
),
),
);
});
}
}
class ChipController extends GetxController {
String? selectedChipAData;
String? selectedChipBData;
List<String> lstType = [
"Regular",
"Hard Sleeper",
"Soft Sleeper",
];
List<String> lstLanguage = [
"Python",
"Flutter",
];
void setSelectedData(String data) {
selectedChipAData = data;
update();
}
void setSelectedChipBData(String data) {
selectedChipBData = data;
update();
}
}

Related

Getx in not updating value in navigation rails flutter

I am using Obx in the navigation rails and updating the text widget. When I tap on the filter by source widget and select some values it does not update the length/count but when I tap on the filter by country widget the filter by a source shows the count. Same for the widget filter by country. It does not update the value in real-time. This is my rails widget
NavigationRail(
selectedIndex: homeController.rails.value,
onDestinationSelected: (int index) {
homeController.rails.value = index;
},
labelType: NavigationRailLabelType.all,
destinations: <NavigationRailDestination>[
const NavigationRailDestination(
icon: Icon(Icons.list_alt_sharp),
selectedIcon: Icon(Icons.list_alt),
label: Text('Layouts'),
),
const NavigationRailDestination(
icon: Icon(Icons.language_outlined),
selectedIcon: Icon(Icons.language),
label: Text('Filter by language'),
),
NavigationRailDestination(
icon: const Icon(Icons.source_outlined),
selectedIcon: const Icon(Icons.source),
label: Obx(() => Text(
'Filter by source ${homeController.filterSources.value.length}'))),
NavigationRailDestination(
icon: const Icon(FontAwesomeIcons.earthAsia),
selectedIcon:
const FaIcon(FontAwesomeIcons.earthAsia),
label: Obx(
() => Text(
'Filter by country ${homeController.filterCountry.value.length}'),
)),
],
),
This is my get controller
class HomeController extends GetxController {
var filterCountry = [].obs;
var filterSources = [].obs;
void filterCountryFunc(String e) {
if (filterCountry.value.contains(e)) {
filterCountry.value.remove(e);
} else {
filterCountry.value.add(e);
}
update();
}
void filterSourcesFunc(String e) {
if (filterSources.value.contains(e)) {
filterSources.value.remove(e);
} else {
filterSources.value.add(e);
}
update();
}
}
This is my complete dart stateful class
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:newsconnects/constants.dart';
import 'package:newsconnects/controllers/home_controller.dart';
import 'package:newsconnects/controllers/sources_controller.dart';
import 'package:newsconnects/translations/helper.dart';
import 'package:newsconnects/views/widgets/custom_checkBoxTile.dart';
import 'news_view.dart';
class HomeView extends StatefulWidget {
HomeView({Key? key}) : super(key: key);
#override
State<HomeView> createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView>
with SingleTickerProviderStateMixin {
late TabController controller;
late final List<String> _syncChannels = [];
HomeController homeController = Get.put(HomeController());
SourcesController sourcesController = Get.find<SourcesController>();
final List _screens = <Widget>[
const NewsView(),
Center(
child: Text(t.getText('notifications')),
),
const Center(
child: Text('Politics'),
),
];
#override
void initState() {
controller = TabController(length: 3, vsync: this);
super.initState();
print('Home');
_resources();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 75.0,
leadingWidth: 90,
leading: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: () {},
child: const Icon(
Icons.refresh,
size: 30.0,
),
),
InkWell(
onTap: () => _showModelSheet(context),
child: const Icon(
Icons.add_box_sharp,
size: 30.0,
),
),
const Icon(
Icons.search,
size: 30.0,
),
]),
centerTitle: true,
title: const Center(
child: Text('NewsConnect'),
),
actions: [
const Center(
child: Text(
'Options',
style: TextStyle(fontSize: 14),
),
),
IconButton(onPressed: () {}, icon: const Icon(Icons.more_vert))
],
bottom: PreferredSize(
preferredSize: const Size.fromHeight(30.0),
child: TabBar(
controller: controller,
isScrollable: true,
unselectedLabelColor: Colors.white.withOpacity(0.3),
indicatorColor: Colors.white,
tabs: const [
Tab(
child: Text('All News'),
),
Tab(
child: Text('Notifications'),
),
Tab(
child: Text('Politics'),
),
]),
),
),
body: TabBarView(
controller: controller,
children: <Widget>[..._screens],
),
);
}
_showModelSheet(BuildContext context) {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0)),
),
builder: (context) {
return SizedBox(
height: 400.0,
child: Container(
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
topRight: Radius.circular(10.0))),
child: Obx(
() => Column(
children: [
Expanded(
flex: 2,
child: Row(
children: <Widget>[
NavigationRail(
selectedIndex: homeController.rails.value,
onDestinationSelected: (int index) {
homeController.rails.value = index;
},
labelType: NavigationRailLabelType.all,
destinations: <NavigationRailDestination>[
const NavigationRailDestination(
icon: Icon(Icons.list_alt_sharp),
selectedIcon: Icon(Icons.list_alt),
label: Text('Layouts'),
),
const NavigationRailDestination(
icon: Icon(Icons.language_outlined),
selectedIcon: Icon(Icons.language),
label: Text('Filter by language'),
),
NavigationRailDestination(
icon: const Icon(Icons.source_outlined),
selectedIcon: const Icon(Icons.source),
label: Obx(() => Text(
'Filter by source ${homeController.filterSources.value.length}'))),
NavigationRailDestination(
icon: const Icon(FontAwesomeIcons.earthAsia),
selectedIcon:
const FaIcon(FontAwesomeIcons.earthAsia),
label: Obx(
() => Text(
'Filter by country ${homeController.filterCountry.value.length}'),
)),
],
),
const VerticalDivider(thickness: 1, width: 1),
// This is the main content.
Expanded(
child: IndexedStack(
index: homeController.rails.value,
children: [
_layouts(),
_language(),
_sources(),
_country(),
],
))
],
),
),
const Divider(thickness: 1, height: 1),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
TextButton(
onPressed: () {
homeController.filterSources.value.clear();
homeController.filterCountry.value.clear();
},
child: const Text('CLEAR All')),
ElevatedButton(
onPressed: () {}, child: const Text('APPLY'))
],
)
],
),
),
),
);
});
}
Widget _myRadioButton({required String title, required int index}) {
return GetBuilder<HomeController>(
builder: (_) => RadioListTile(
value: homeController.layout[index],
groupValue: homeController.isGrid.value,
onChanged: (newValue) =>
homeController.onClickRadioButton(int.parse(newValue.toString())),
title: Text(title),
activeColor: Consts.appMainColor,
),
);
}
_layouts() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_myRadioButton(
title: "List View",
index: 0,
),
_myRadioButton(
title: "Grid View",
index: 1,
),
],
);
}
_language() {
return Obx(
() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomCheckBoxListTile(
value: homeController.isEnglish.value,
title: const Text('English'),
onTap: (newVal) {
homeController.isEnglish.value = newVal ?? false;
}),
CustomCheckBoxListTile(
value: homeController.isArabic.value,
title: const Text('Arabic'),
onTap: (newVal) {
homeController.isArabic.value = newVal ?? false;
}),
],
),
);
}
_sources() {
return GetBuilder<HomeController>(builder: (_) {
return ListView(
children: _syncChannels
.map((e) => CustomCheckBoxListTile(
value: homeController.filterSources.value.contains(e),
title: Text(e),
onTap: (newVal) {
homeController.filterSourcesFunc(e);
}))
.toList());
});
}
_country() {
return GetBuilder<HomeController>(
builder: (_) => ListView(
children: sourcesController.mapSelectedChannelCheckList.keys
.map((e) => CustomCheckBoxListTile(
value: homeController.filterCountry.value.contains(e),
title: Text(e.toString()),
onTap: (newVal) {
homeController.filterCountryFunc(e);
}))
.toList()));
}
void _resources() {
List.generate(
sourcesController.mapSelectedChannelCheckList.values.length,
(index0) => List.generate(
sourcesController.mapSelectedChannelCheckList.values
.elementAt(index0)
.length,
(index1) => List.generate(
sourcesController.mapSelectedChannelCheckList.values
.elementAt(index0)
.elementAt(index1)
.values
.length,
(index2) => List.generate(
sourcesController.mapSelectedChannelCheckList.values
.elementAt(index0)
.elementAt(index1)
.values
.elementAt(index2)['channels']
.length,
(index3) => _syncChannels.add(sourcesController
.mapSelectedChannelCheckList.values
.elementAt(index0)
.elementAt(index1)
.values
.elementAt(index2)['channels'][index3]
.keys
.toString())))));
}
}

when i typing in flutter textfield it type in backward direction how to solve it?

I have one page of technical skill and I want to add new textfield on onatap and get the value of it and remove that skill on ontap of delete button and this is working now but only problem was that the when i am typing in textfield its typing in backward direction(right to left i want to type left to right.)
import 'package:flutter/material.dart';
class Technical extends StatefulWidget {
const Technical({Key? key}) : super(key: key);
#override
State<Technical> createState() => _TechnicalState();
}
class _TechnicalState extends State<Technical> {
List<String> skill = <String>[];
List<TextEditingController> mycontroller = <TextEditingController>[];
#override
Widget build(BuildContext context) {
double h = MediaQuery.of(context).size.height;
double w = MediaQuery.of(context).size.width;
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
centerTitle: true,
elevation: 0,
backgroundColor: Colors.blue,
title: const Text(
'Technical Skills',
),
),
body: Column(
children: [
const Align(
alignment: Alignment.centerLeft,
child: Text(
'Enter Your Skills',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
),
...skill
.map(
(e) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 100,
child: TextField(
enableInteractiveSelection: true,
controller: TextEditingController(text: e),
onChanged: (String value) {
setState(() {
skill[skill.indexOf(e)] = value;
});
},
),
),
IconButton(
onPressed: () {
setState(() {
skill.remove(e);
mycontroller.clear();
print(e);
});
},
icon: const Icon(Icons.delete))
],
),
)
.toList(),
Center(
child: Text(
'$skill',
style: const TextStyle(fontSize: 30),
),
),
const Spacer(),
OutlinedButton(
onPressed: () {
setState(() {
skill.add("");
});
},
child: const Icon(Icons.add),
),
],
),
);
}
}
The issue is you are creating new controller on every state change, the cursor position is not handling in this.
So the solution will we
controller: TextEditingController.fromValue(
TextEditingValue(
text: e,
selection: TextSelection(
baseOffset: e.length,
extentOffset: e.length,
)),
),
With controller
class _TechnicalState extends State<Technical> {
List<String> skill = <String>[];
List<TextEditingController> mycontroller = <TextEditingController>[];
#override
Widget build(BuildContext context) {
double h = MediaQuery.of(context).size.height;
double w = MediaQuery.of(context).size.width;
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
centerTitle: true,
elevation: 0,
backgroundColor: Colors.blue,
title: const Text(
'Technical Skills',
),
),
body: Column(
children: [
const Align(
alignment: Alignment.centerLeft,
child: Text(
'Enter Your Skills',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
),
for (int i = 0; i < mycontroller.length; i++) row_build(i),
Center(
child: Text(
'$skill',
style: const TextStyle(fontSize: 30),
),
),
const Spacer(),
OutlinedButton(
onPressed: () {
mycontroller.add(TextEditingController());
setState(() {
skill.add("");
});
},
child: const Icon(Icons.add),
),
],
),
);
}
Row row_build(int i) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 100,
child: TextField(
enableInteractiveSelection: true,
controller: mycontroller[i],
onChanged: (String value) {
setState(() {
skill[i] = value;
});
},
),
),
IconButton(
onPressed: () {
setState(() {
skill.remove(skill[i]);
mycontroller.removeAt(i);
});
},
icon: const Icon(Icons.delete))
],
);
}
}
The textfield works fine for me (left to right)
Check your code if the textDirection property is set correctly to TextDirection.ltr instead of TextDirection.rtl
child: TextField(
textDirection: TextDirection.ltr,

content vanish every time I restart the app

I created a page where I can add exercise for my classes and in this page students can create their custom training (it's a gym app), the only problem is that every time the user closes the app all the training is gone.
I leave below my code, I've done some researches and I kind of understood that there is something with SharPreferences but all the tutorial talk about a login page which is not my case.
can you please advise?? I leave my code below, thanks!
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class Custom extends StatefulWidget {
#override
_CustomState createState() => new _CustomState();
}
class _CustomState extends State<Custom> {
int _count = 0;
#override
Widget build(BuildContext context) {
List<Widget> _esercizi = List.generate(_count, (int i) => Esercizi());
bool visible = _count > 0;
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Scaffold(
appBar: AppBar(
title: Text('Scheda personalizzata'),
actions: [
Visibility(
visible: visible,
child: IconButton(
icon: Icon(Icons.remove_rounded),
onPressed: _removeNewEsercizi,
),
),
IconButton(icon: Icon(Icons.add), onPressed: _addNewEsercizi),
],
backgroundColor: Colors.blueGrey,
),
body: LayoutBuilder(builder: (context, constraint) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
height: 700,
child: ListView(
scrollDirection: Axis.vertical,
children: _esercizi,
),
),
//new ContactRow()
],
),
),
);
}),
),
);
}
void _addNewEsercizi() {
setState(() {
_count = _count + 1;
});
}
void _removeNewEsercizi() {
setState(() {
_count = _count - 1;
});
}
}
class Esercizi extends StatefulWidget {
#override
State<StatefulWidget> createState() => _Esercizi();
}
class _Esercizi extends State<Esercizi> {
String dropdownValue = 'Seleziona esercizio';
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Padding(
padding: const EdgeInsets.all(2.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
DropdownButton<String>(
value: dropdownValue,
style: TextStyle(color: Colors.blueGrey),
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: <String>['Seleziona esercizio', 'One', 'Two', 'Three', 'Fo'
'ur', 'Five', 'Six']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
Container(
height: 50,
width: 90,
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
cursorColor: Colors.white,
decoration: InputDecoration(
hintText: 'Time/Reps',
hintStyle: TextStyle(color: Colors.blueGrey),
),
keyboardType: TextInputType.number,
),
),
),
),
],
),
);
}
}

TextEditController clears the text field's text

I am trying to make an application about to do. But there is an issue. I write some text to the text field. But When I add a new text field it clears the text filed's text. And when I delete the TextEditingController it fixes the issue. But then when I try to slide it right or left it clears the text again.
Here is the code
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
void main() => runApp(MaterialApp(
home: Home(),
));
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int today=1;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.lightGreenAccent[100],
appBar: AppBar(
title: Text("Takveam"),
centerTitle: true,
backgroundColor: Colors.lightGreenAccent[700],
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Center(
child: Text(
"$today",
style: TextStyle(
fontSize: 20.0,
color: Colors.lightGreenAccent[700],
letterSpacing: 1,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Todo()),
);
},
icon: Icon(Icons.featured_play_list),
color: Colors.lightGreenAccent[700],
),
IconButton(
onPressed: () {
setState(() {
today-=1;
if(today<1)
today=31;
});
},
icon: Icon(Icons.calendar_today),
color: Colors.lightGreenAccent[700],
),
IconButton(
onPressed: () {
setState(() {
today=31;
});
},
icon: Icon(Icons.access_alarms),
color: Colors.lightGreenAccent[700],
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
today+=1;
if(today>31)
{
today=1;
}
});
},
child: Text("Dıkla"),
backgroundColor: Colors.lightGreenAccent[700],
),
);
}
}
class Todo extends StatefulWidget {
#override
_TodoState createState() => _TodoState();
}
class _TodoState extends State<Todo> {
int i=1;
List<Column> todoos = [
Column(),
];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.lightGreenAccent[100],
appBar: AppBar(
title: Text("To do"),
centerTitle: true,
backgroundColor: Colors.lightGreenAccent[700],
),
body: ListView.builder(
itemCount: i,
itemBuilder: (context,index){
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Slidable(
actionPane: SlidableDrawerActionPane(),
actionExtentRatio: 0.25,
child: Container(
height: 60,
child: Card(
color: Colors.lightGreenAccent[700],
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextField(
controller: new TextEditingController(),
style: TextStyle(
fontSize: 20,
color: Colors.white
),
onChanged: (text) {
print(i);
},
),
],
),
),
),
actions: <Widget>[
IconSlideAction(
color: Colors.lightGreenAccent[200],
icon: Icons.check_circle,
)
],
secondaryActions: <Widget>[
IconSlideAction(
color: Colors.lightGreenAccent[200],
icon: Icons.more_horiz
)
],
),
],
);
}
),
floatingActionButton: FloatingActionButton(
onPressed: (){
setState(() {
i+=1;
});
},
child: Icon(
Icons.add,
),
backgroundColor: Colors.lightGreenAccent[700],
),
);
}
}
You can copy paste run full code below
Each item need a TextEditingController
For demo purpose, I remove background color
Step 1: declare a List<TextEditingController> listTexttCtrl = [TextEditingController()];
Step 2: In FloatingActionButton, add listTexttCtrl.add(TextEditingController());
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
i += 1;
listTexttCtrl.add(TextEditingController());
});
},
Step 3: TextField controller attribute use listTexttCtrl[index]
TextField(
controller: listTexttCtrl[index],
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
void main() => runApp(MaterialApp(
home: Home(),
));
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int today = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.lightGreenAccent[100],
appBar: AppBar(
title: Text("Takveam"),
centerTitle: true,
backgroundColor: Colors.lightGreenAccent[700],
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Center(
child: Text(
"$today",
style: TextStyle(
fontSize: 20.0,
color: Colors.lightGreenAccent[700],
letterSpacing: 1,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Todo()),
);
},
icon: Icon(Icons.featured_play_list),
color: Colors.lightGreenAccent[700],
),
IconButton(
onPressed: () {
setState(() {
today -= 1;
if (today < 1) today = 31;
});
},
icon: Icon(Icons.calendar_today),
color: Colors.lightGreenAccent[700],
),
IconButton(
onPressed: () {
setState(() {
today = 31;
});
},
icon: Icon(Icons.access_alarms),
color: Colors.lightGreenAccent[700],
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
today += 1;
if (today > 31) {
today = 1;
}
});
},
child: Text("Dıkla"),
backgroundColor: Colors.lightGreenAccent[700],
),
);
}
}
class Todo extends StatefulWidget {
#override
_TodoState createState() => _TodoState();
}
class _TodoState extends State<Todo> {
int i = 1;
List<Column> todoos = [
Column(),
];
List<TextEditingController> listTexttCtrl = [TextEditingController()];
#override
Widget build(BuildContext context) {
return Scaffold(
//backgroundColor: Colors.lightGreenAccent[100],
appBar: AppBar(
title: Text("To do"),
centerTitle: true,
backgroundColor: Colors.lightGreenAccent[700],
),
body: ListView.builder(
itemCount: i,
itemBuilder: (context, index) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Slidable(
actionPane: SlidableDrawerActionPane(),
actionExtentRatio: 0.25,
child: Container(
height: 60,
child: Card(
color: Colors.lightGreenAccent[700],
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextField(
controller: listTexttCtrl[index],
style: TextStyle(fontSize: 20, color: Colors.white),
onChanged: (text) {
print(i);
},
),
],
),
),
),
actions: <Widget>[
IconSlideAction(
color: Colors.lightGreenAccent[200],
icon: Icons.check_circle,
)
],
secondaryActions: <Widget>[
IconSlideAction(
color: Colors.lightGreenAccent[200],
icon: Icons.more_horiz)
],
),
],
);
}),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
i += 1;
listTexttCtrl.add(TextEditingController());
});
},
child: Icon(
Icons.add,
),
backgroundColor: Colors.lightGreenAccent[700],
),
);
}
}

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.