How to select only one checkbox from Checkbox Listtile in Flutter - flutter

Here I've one service page where I've displayed all the services, and there I can select multiple services. but I want to select only one service. I've used the checkbox Listitle for the service selection. I want that user can select only one service not multiple services at a time.
Here is code i've tried :
class _AddWalkinServiceScreenState extends State<AddWalkinServiceScreen>
with TickerProviderStateMixin {
List<int> servicesIds = [];
int selected = 0;
Map<String, bool> _selection = {};
List<BspServices.Service> selectedServices = [];
SearchBarController _controller = new SearchBarController();
String _searchText = '';
List<dynamic> finalList = new List();
List<dynamic> searchList = new List();
bool isLoading = false;
AnimationController controller;
Animation<double> animation;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
animation = CurvedAnimation(parent: controller, curve: Curves.easeInQuint);
controller.forward();
}
Widget _renderServices(AddWalkinServiceViewModel awsVm) {
List lovCountryServices = searchList.length != 0 ? searchList : finalList;
if (lovCountryServices == null || lovCountryServices.length == 0) {
return Container(
child: Center(
child: Text("No Services available for this combination"),
),
);
}
// print(lovCountryServices);
return Container(
child: finalList.length < 1
? ListTile(
leading: CircularProgressIndicator(),
)
: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
itemCount: lovCountryServices.length,
itemBuilder: (BuildContext context, int index) {
var item = lovCountryServices[
index]; // should be outside build function
List items = item['services'];
return ExpansionTile(
title: Text(item['name']),
children: List.generate(items.length, (i) {
_selection[items[i]['name']] =
_selection[items[i]['name']] ?? items[i]['isSelected'];
return CheckboxListTile(
title: Text(items[i]['name']),
value: _selection[items[i]['name']] == null
? false
: _selection[items[i]['name']],
onChanged: (val) {
setState(() {
_selection[items[i]['name']] = val;
if (val) {
servicesIds.add(items[i]['id']);
List<BspServices.Service> services =
selectedServices.where((service) {
return service.mainCategory == item['name'];
}).toList();
SubCategory subService = new SubCategory(
id: items[i]['id'],
name: items[i]['name'],
);
List<SubCategory> subCategories = [];
if (services.length < 1) {
subCategories.add(subService);
selectedServices.add(
new BspServices.Service(
mainCategory: item['name'],
mainCategoryId: item['id'],
subCategory: subCategories,
),
);
} else {
print('services in else');
print(services[0].subCategory);
subCategories = services[0].subCategory;
subCategories.add(subService);
}
} else {
servicesIds.removeWhere((service) {
return service == items[i]['id'];
});
List<BspServices.Service> services =
selectedServices.where((service) {
return service.mainCategory == item['name'];
}).toList();
services[0].subCategory.removeWhere((subService) {
return subService.id == items[i]['id'];
});
}
});
print('servicesIds after set state');
print(servicesIds);
},
);
}),
);
},
),
);
}
Widget content(BuildContext context, AddWalkinServiceViewModel awsVm) {
Orientation orientation = MediaQuery.of(context).orientation;
var colorStyles = Theming.colorstyle(context);
final appBar = SearchBar(
controller: _controller,
onQueryChanged: (String query) {
print('Search Query $query');
setState(() {
_searchText = query;
});
_searchFilter();
},
defaultBar: AppBar(
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.pop(context);
// NavigationHelper.navigatetoBack(context);
}),
title: Text('Select Services'),
),
);
return new Scaffold(
backgroundColor: colorStyles['primary'],
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: FadeTransition(
opacity: animation,
child: new Container(
margin: EdgeInsets.only(top: 10),
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(50.0),
topRight: const Radius.circular(50.0),
),
),
child: isLoading ? FadeInUi() : _renderServices(awsVm),
),
),
);
}
#override
Widget build(BuildContext context) {
return new StoreConnector<AppState, AddWalkinServiceViewModel>(
converter: (Store<AppState> store) =>
AddWalkinServiceViewModel.fromStore(store),
onInit: (Store<AppState> store) {
print('store.state.servicesState.servicesByCountry');
print(store
.state.servicesState.servicesByCountry.servicesByCountry[0].name);
Map<String, dynamic> services =
store.state.servicesState.servicesByCountry.toJson();
finalList = services['servicesByCountry'];
print('finalList = $finalList');
},
builder: (BuildContext context, AddWalkinServiceViewModel awsVm) =>
content(context, awsVm),
);
}
}

UPDATED
List<Map> services = [];
List<int> selections = [];
#override
void initState() {
super.initState();
getList();
}
void getList() async {
//get data from internet/api
//for ex. I m using offline data
setState(() {
services = List.generate(
10,
(ind) => {
'name': 'Service Category $ind',
'services': ['Service 1', 'Service 2']
}).toList();
selections = List.generate(10, (ind) => -1).toList();
});
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
color: Colors.white,
child: services.length < 1
? ListTile(
leading: CircularProgressIndicator(), title: Text('Loading...'))
: ListView.builder(
itemCount: services.length,
itemBuilder: (con, ind) {
return ExpansionTile(
title: Text('${services[ind]['name']}',
style: TextStyle(color: Colors.black)),
children:
List.generate(services[ind]['services'].length, (ii) {
return CheckboxListTile(
title: Text('${services[ind]['services'][ii]}',
style: TextStyle(color: Colors.green[900])),
value: selections[ind] == ii,
onChanged: (b) {
setState(() {
selections[ind] = ii;
});
});
}).toList());
}));
}

Related

The state of my object is not stored properly

I want to mark my object as favorite.
I have a list of object <RobotAnimation> which is displayed in a ListView. The class have two fields: title and isFavorite. Marking an object as a favorite works, but there is a problem when it comes to storing that state. When I perform a search of all items, after selecting an item as favorite, my favorite items are not being remembered. It seems like the state is being discarded.
What can I do to fix this problem?
Here's what's going on:
Here's my code:
class RobotAnimation {
String title;
bool isFavorite;
RobotAnimation({required this.title, this.isFavorite = false});
#override
String toString() {
return '{Title: $title, isFavortite: $isFavorite}';
}
}
class Animations extends StatefulWidget {
const Animations({super.key});
#override
State<Animations> createState() => _AnimationsState();
}
class _AnimationsState extends State<Animations> with TickerProviderStateMixin {
late TabController _tabController;
List<RobotAnimation> animations = [];
List<RobotAnimation> favoriteAnimations = [];
List<String> results = store.state.animations;
List<String> defaultFavorites = [];
List<RobotAnimation> getAnimationList(
List<String> animations, List<String> favorites) {
List<RobotAnimation> robotAnimations = [];
for (var animation in animations) {
bool isFav = false;
for (var favorite in favorites) {
if (favorite == animation) {
isFav = true;
}
}
robotAnimations.add(RobotAnimation(title: animation, isFavorite: isFav));
}
return robotAnimations;
}
List<RobotAnimation> filterFavorites() {
List<RobotAnimation> filtered = favoriteAnimations;
animations.where((element) => element.isFavorite == true).toList();
return filtered;
}
void filterSearchResults(String query) {
List<RobotAnimation> searchList =
getAnimationList(results, defaultFavorites);
log('query: $query');
List<RobotAnimation> filteredList = searchList
.where((element) =>
element.title.toLowerCase().contains(query.toLowerCase()))
.toList();
log(searchList.toString());
log(filteredList.toString());
setState(() => animations = filteredList);
}
#override
void initState() {
animations = getAnimationList(results, defaultFavorites);
super.initState();
}
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, _Props>(
converter: (store) => _mapStateToProps(store),
builder: (_, props) {
return Scaffold(
body: TabBarView(
...
children: [
Container(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
TextField(
onChanged: filterSearchResults,
decoration: const InputDecoration(
labelText: 'Search',
hintText: 'Search animation',
prefixIcon: Icon(Icons.search),
),
),
Expanded(
child: ListView.separated(
itemCount: animations.length,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
return ListTile(
onTap: () {
props.socket?.animation(IAnimation(
animation: animations[index].title));
},
title: ExtendedText(
animations[index].title,
maxLines: 1,
overflowWidget: const TextOverflowWidget(
position: TextOverflowPosition.middle,
align: TextOverflowAlign.center,
child: Text(
'...',
overflow: TextOverflow.ellipsis,
),
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {
setState(() {
animations[index].isFavorite
? animations[index].isFavorite = false
: animations[index].isFavorite = true;
});
},
icon: animations[index].isFavorite
? Icon(
Icons.favorite,
color: Colors.red.shade500,
)
: Icon(
Icons.favorite_border,
color: Colors.grey.shade500,
),
),
],
),
);
},
),
)
],
),
),
],
),
);
},
);
}
}
class _Props {
final Connection? socket;
final List<String> animations;
_Props({
required this.socket,
required this.animations,
});
}
_Props _mapStateToProps(Store<AppState> store) {
return _Props(
socket: store.state.socket,
animations: store.state.animations,
);
}
Try this inside your IconButton onPressed-Method:
setState(() {
if (animations[index].isFavorite) {
animations[index].isFavorite = false
defaultFavorites.remove(animations[index].title)
} else {
animations[index].isFavorite = true;
defaultFavorites.add(animations[index].title)
}
});
It seems like you always generate a new list of animations based on the two lists List<String>.

Flutter weird bug with widget function, sometimes in runs 2 times instead of one

This function have futureBuilder inside and returns another function that returns ListView.
Also I do not want the user to download information from the Internet every time they visit the page, so I put the information downloaded from the Internet into a static variable in class, so i make if else. If this static variable length != 0 , it return me listView instead of make server request with futureBuilder
function that works 2 times instead of one is ListView body
And also this bug created after i added purchases to my application, maybe something wrong there. I don't have any ideas why this happend
this is my complete code
class AddCheckList extends StatefulWidget {
const AddCheckList({Key? key}) : super(key: key);
#override
_AddCheckListState createState() => _AddCheckListState();
}
class _AddCheckListState extends State<AddCheckList> {
String xQueryReqestForCheckListNames = "element CheckListList {for \$a in PACK/OBJECT where (\$a/#inUse = 'True') order by \$a/#name return element CheckList {attribute name {\$a/#name}, attribute sPaid {\$a/#isPaid},attribute oid {\$a/#oid} }}";
String serverLink = "http...";
int addCheckListMethod = 0;
// if addCheckListMethod == 0, then data will download from server and checkLists will be added from server xml data
// if addCheckListMethod == 1, then data will download from assets, and checkLists will be added from assets xml data with getXmlData function
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
final String _productID = '1d7ea644f690ffa';
bool _available = true;
List<ProductDetails> _products = [];
List<PurchaseDetails> _purchases = [];
StreamSubscription<List<PurchaseDetails>>? _subscription;
#override
void initState() {
final Stream<List<PurchaseDetails>> purchaseUpdated = _inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
setState(() {
_purchases.addAll(purchaseDetailsList);
_listenToPurchaseUpdated(purchaseDetailsList);
});
}, onDone: () {
_subscription!.cancel();
}, onError: (error) {
_subscription!.cancel();
});
_initialize();
super.initState();
}
#override
void dispose() {
_subscription!.cancel();
super.dispose();
}
void _initialize() async {
_available = await _inAppPurchase.isAvailable();
List<ProductDetails> products = await _getProducts(
productIds: Set<String>.from(
[_productID],
),
);
setState(() {
_products = products;
});
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
switch (purchaseDetails.status) {
case PurchaseStatus.pending:
// _showPendingUI();
break;
case PurchaseStatus.purchased:
case PurchaseStatus.restored:
// bool valid = await _verifyPurchase(purchaseDetails);
// if (!valid) {
// _handleInvalidPurchase(purchaseDetails);
// }
break;
case PurchaseStatus.error:
print(purchaseDetails.error!);
// _handleError(purchaseDetails.error!);
break;
default:
break;
}
if (purchaseDetails.pendingCompletePurchase) {
await _inAppPurchase.completePurchase(purchaseDetails);
}
});
}
Future<List<ProductDetails>> _getProducts({required Set<String> productIds}) async {
ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(productIds);
return response.productDetails;
}
void _subscribe({required ProductDetails product}) {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);
_inAppPurchase.buyNonConsumable(
purchaseParam: purchaseParam,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(padding: EdgeInsets.all(14.0), child: listViweBody(addCheckListMethod, serverLink, xQueryReqestForCheckListNames, _products,_subscribe)),
);
}
}
Widget listView(addCheckListMethod, serverLink, product,_subscribe) {
return ListView.separated(
itemCount: CheckListModel.checkListModelNamesFromServer.length,
itemBuilder: (BuildContext context, int index) {
if (CheckListModel.checkListModelNamesFromServer[index].ispaid == false) {
return InkWell(
onTap: () async {
CheckListModel checkList = CheckListModel('', 0, '', 0, 0, 0, '', [], '');
if (addCheckListMethod == 0) {
String xQueryReqestForCheckList = 'for \$a in PACK/OBJECT where \$a/#name="${CheckListModel.checkListModelNamesFromServer[index].name}" return \$a';
var data = await CheckListModel.getDataFromServer(xQueryReqestForCheckList, serverLink);
CheckListModel.addCheckListFromServer(checkList, data);
} else {
CheckListModel.addCheckList(index, checkList);
}
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Добавить описание"),
content: Container(
decoration: BoxDecoration(border: Border.all(color: Color.fromARGB(Desing.colorFromARGBBtn[0], Desing.colorFromARGBBtn[1], Desing.colorFromARGBBtn[2], Desing.colorFromARGBBtn[3]), width: 1), borderRadius: BorderRadius.circular(10)),
child: TextField(
decoration: new InputDecoration.collapsed(hintText: "Описание", border: InputBorder.none),
maxLines: null,
onChanged: (String value) async {
checkList.description = value;
},
),
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text("Отменить"),
),
TextButton(
onPressed: () async {
await checkList.writeToFile(checkList.toJson());
CheckListModel.checkLists.add(checkList);
Navigator.pushNamed(context, '/beforeMainCheckList', arguments: {'index': CheckListModel.checkLists.length - 1});
},
child: Text('Добавить'))
],
);
});
},
child: Container(
decoration: BoxDecoration(border: Border.all(color: Color.fromARGB(Desing.colorFromARGBBtn[0], Desing.colorFromARGBBtn[1], Desing.colorFromARGBBtn[2], Desing.colorFromARGBBtn[3]), width: 1), borderRadius: BorderRadius.circular(10)),
width: 50,
height: 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"${CheckListModel.checkListModelNamesFromServer[index].name}",
style: TextStyle(fontSize: 30),
)
],
),
),
);
} else {
if (product.length != 0) {
return showPurchaseCheckLists(product, index,_subscribe);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
}
},
separatorBuilder: (BuildContext context, int index) {
return Container(
height: 14,
);
});
}
Widget showPurchaseCheckLists(product, index,_subscribe) {
int getCurrentProduct() {
String? checkListModelid = CheckListModel.checkListModelNamesFromServer[index].checkListId?.toLowerCase();
int indexx = 0;
for (int i = 0; i < product.length; i++) {
if (checkListModelid == product[i].id) {
indexx = i;
}
}
return indexx;
}
return InkWell(
child: Container(
decoration: BoxDecoration(border: Border.all(color: Color.fromARGB(Desing.colorFromARGBBtn[0], Desing.colorFromARGBBtn[1], Desing.colorFromARGBBtn[2], Desing.colorFromARGBBtn[3]), width: 1), borderRadius: BorderRadius.circular(10), color: Color.fromARGB(255, 240, 240, 240)),
width: 50,
height: 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"${CheckListModel.checkListModelNamesFromServer[index].name}",
style: TextStyle(fontSize: 25),
),
Text("Купить за ${product[getCurrentProduct()].price}")
],
),
),
onTap: () {
_subscribe(product: product[getCurrentProduct()]);
},
);
}
Widget listViweBody(addCheckListMethod, serverLink, xQueryReqestForCheckListNames, product,_subscribe) {
if (CheckListModel.checkListModelNamesFromServer.length == 0) {
return FutureBuilder(
future: CheckListModel.getDataFromServer(xQueryReqestForCheckListNames, serverLink),
builder: (context, data) {
if (data.connectionState == ConnectionState.done) {
return listView(addCheckListMethod, serverLink, product,_subscribe);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
});
} else {
return listView(addCheckListMethod, serverLink, product,_subscribe);
}
}

how to CupertinoSwitch dynamically(Map) value set in flutter?

Creating dynamic list(request), I put a switch in there.
However, the switches refuse to show their intended state (The status does not change when clicked.)
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:scat/request.dart' as request;
import 'package:scat/util.dart' as util;
class _ConfigPushState extends State<ConfigPush> {
Future<request.ApiResult> configList;
Map<String, bool> _onOffMap = {};
#override
void initState() {
super.initState();
configList = request.configList();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(50.0), // here the desired height
child: new AppBar(
backgroundColor: Colors.black,
leading: new IconButton(
icon: new Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
centerTitle: true,
title: new Text('API 설정', style: util.appTitleStyle),
),
),
body: Center(
child: FutureBuilder<request.ApiResult>(
future: configList,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return CircularProgressIndicator();
}
_onOffMap = {};
return ListView.separated(
separatorBuilder: (context, index) => Divider(),
itemCount: snapshot.data.data.length,
itemBuilder: (context, index) {
var row = snapshot.data.data[index];
var type = row['type'] as String;
var value = row['value'] as bool;
var disabled = row['isDisabled'] as bool;
var subtitle = disabled ? '아직 준비중' : row['description'];
_onOffMap[type] = value;
// _arr.add(value);
// _lights = value;
print('item builder ${type}');
return Container(
height: util.isEmpty(subtitle) ? 50 : 70,
child: new ListTile(
title: new Text(row['name']),
subtitle: new Text(subtitle),
trailing: CupertinoSwitch(
activeColor: Colors.deepPurple,
// value: _arr[index],
// value: _lights,
value: _onOffMap[type],
onChanged: (bool value) {
setState(() {
if (disabled) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('개발중..')));
return;
}
// _lights = value;
print("before $_onOffMap $type");
_onOffMap[type] = value;
print("after $_onOffMap");
request
.pushSet(type, value.toString(), "", "")
.then((a) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('처리 되었습니다.')));
});
});
},
),
));
});
})),
);
}
}
try this one I hope it work for you:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:scat/request.dart' as request;
import 'package:scat/util.dart' as util;
class _ConfigPushState extends State<ConfigPush> {
Future<request.ApiResult> configList;
Map<String, bool> _onOffMap = {};
#override
void initState() {
super.initState();
configList = request.configList();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(50.0), // here the desired height
child: new AppBar(
backgroundColor: Colors.black,
leading: new IconButton(
icon: new Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
centerTitle: true,
title: new Text('API 설정', style: util.appTitleStyle),
),
),
body: Center(
child: FutureBuilder<request.ApiResult>(
future: configList,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return CircularProgressIndicator();
}
_onOffMap = {
"type": true;
};
return ListView.separated(
separatorBuilder: (context, index) => Divider(),
itemCount: snapshot.data.data.length,
itemBuilder: (context, index) {
var row = snapshot.data.data[index];
var type = row['type'] as String;
var value = row['value'] as bool;
var disabled = row['isDisabled'] as bool;
var subtitle = disabled ? '아직 준비중' : row['description'];
_onOffMap["type"] = value;
// _arr.add(value);
// _lights = value;
print('item builder ${type}');
return Container(
height: util.isEmpty(subtitle) ? 50 : 70,
child: new ListTile(
title: new Text(row['name']),
subtitle: new Text(subtitle),
trailing: CupertinoSwitch(
activeColor: Colors.deepPurple,
// value: _arr[index],
// value: _lights,
value: _onOffMap["type"],
onChanged: (bool value) {
setState(() {
if (disabled) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('개발중..')));
return;
}
// _lights = value;
print("before $_onOffMap $type");
_onOffMap["type"] = value;
print("after $_onOffMap");
request
.pushSet(type, value.toString(), "", "")
.then((a) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('처리 되었습니다.')));
});
});
},
),
));
});
})),
);
}
}

How to access all of child's state from Parent Widget in flutter?

I have a parent widget called createRoutineScreen and it has 7 similar children widget called RoutineFormCard. RoutineFormCard is a form and which has a state _isPostSuccesful of boolean type to tell whether the form is saved to database or not. Now, I have to move to the other screen from createRoutine only when all of it's 7 children has _isPostSuccesful true. How can I access all of children's state from createRoutineScreen widget?
My Code is:
class CreateRoutineScreen extends StatefulWidget {
final String userID;
CreateRoutineScreen({this.userID});
//TITLE TEXT
final Text titleSection = Text(
'Create a Routine',
style: TextStyle(
color: Colors.white,
fontSize: 25,
)
);
final List<Map> weekDays = [
{"name":"Sunday", "value":1},
{"name":"Monday", "value":2},
{"name":"Tuesday", "value":3},
{"name":"Wednesday", "value":4},
{"name":"Thursday", "value":5},
{"name":"Friday", "value":6},
{"name":"Saturday", "value":7},
];
#override
_CreateRoutineScreenState createState() => _CreateRoutineScreenState();
}
class _CreateRoutineScreenState extends State<CreateRoutineScreen> {
Routine routine;
Future<List<dynamic>> _exercises;
dynamic selectedDay;
int _noOfRoutineSaved;
List _keys = [];
Future<List<dynamic>>_loadExercisesData()async{
String url = BASE_URL+ "exercises";
var res = await http.get(url);
var exercisesList = Exercises.listFromJSON(res.body);
//var value = await Future.delayed(Duration(seconds: 5));
return exercisesList;
}
#override
void initState(){
super.initState();
_exercises = _loadExercisesData();
_noOfRoutineSaved = 0;
for (int i = 0; i< 7; i++){
_keys.add(UniqueKey());
}
}
void _changeNoOfRoutineSaved(int a){
setState(() {
_noOfRoutineSaved= _noOfRoutineSaved + a;
});
}
#override
Widget build(BuildContext context) {
print(_noOfRoutineSaved);
return Scaffold(
appBar: AppBar(
title:Text("Create a Routine"),
centerTitle: true,
actions: <Widget>[
FlatButton(
child: Text("Done"),
onPressed: (){
},
),
],
),
body: Container(
color: Theme.of(context).primaryColor,
padding: EdgeInsets.only(top:5.0,left: 10,right: 10,bottom: 10),
child: FutureBuilder(
future: _exercises,
builder: (context, snapshot){
if(snapshot.hasData){
return ListView.builder(
itemCount: widget.weekDays.length,
itemBuilder: (context,index){
return RoutineFormCard(
weekDay: widget.weekDays[index]["name"],
exerciseList: snapshot.data,
userID : widget.userID,
changeNoOfRoutineSaved:_changeNoOfRoutineSaved,
key:_keys[index]
);
},
);
}
else if(snapshot.hasError){
return SnackBar(
content: Text(snapshot.error),
);
}
else{
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.grey,
)
);
}
},
)
),
);
}
}
And my child widget is:
class RoutineFormCard extends StatefulWidget {
final Function createRoutineState;
final String weekDay;
final List<dynamic> exerciseList;
final String userID;
final Function changeNoOfRoutineSaved;
RoutineFormCard({this.createRoutineState,
this.weekDay, this.exerciseList, this.changeNoOfRoutineSaved,
this.userID, Key key}):super(key:key);
#override
_RoutineFormCardState createState() => _RoutineFormCardState();
}
class _RoutineFormCardState extends State<RoutineFormCard> {
bool _checkBoxValue= false;
List<int> _selectedExercises;
bool _inAsyncCall;
bool _successfulPost;
#override
void initState(){
super.initState();
_selectedExercises = [];
_inAsyncCall = false;
_successfulPost= false;
}
void onSaveClick()async{
setState(() {
_inAsyncCall = true;
});
String url = BASE_URL + "users/routine";
List selectedExercises = _selectedExercises.map((item){
return widget.exerciseList[item].value;
}).toList();
String dataToSubmit = jsonEncode({
"weekDay":widget.weekDay,
"userID": widget.userID==null?"5e9eb190b355c742c887b88d":widget.userID,
"exercises": selectedExercises
});
try{
var res =await http.post(url, body: dataToSubmit,
headers: {"Content-Type":"application/json"});
if(res.statusCode==200){
print("Succesful ${res.body}");
widget.changeNoOfRoutineSaved(1);
setState(() {
_inAsyncCall = false;
_successfulPost = true;
});
}
else{
print("Not succesful ${res.body}");
setState(() {
_inAsyncCall = false;
});
}
}catch(err){
setState(() {
_inAsyncCall = false;
});
print(err);
}
}
Widget saveAndEditButton(){
if(_inAsyncCall){
return CircularProgressIndicator();
}
else if(_successfulPost)
{
return IconButton(
icon: Icon(Icons.edit, color: Colors.black,),
onPressed: (){
widget.changeNoOfRoutineSaved(-1);
setState(() {
_successfulPost = false;
});
},
);
}
else{
return FlatButton(child: Text("Save"),
onPressed: !_checkBoxValue&&_selectedExercises.length==0?null:onSaveClick,);
}
}
//Card Header
Widget cardHeader(){
return AppBar(
title: Text(widget.weekDay, style: TextStyle(
fontFamily: "Raleway",
fontSize: 20,
color: Colors.black,),
),
actions: <Widget>[
saveAndEditButton()
],
backgroundColor: Colors.lime[400],
);
}
Widget cardBody(){
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Text("Rest Day"),
Checkbox(
value: _checkBoxValue,
onChanged: (value){
setState(() {
_checkBoxValue = value;
});
},
)
],
),
),
_checkBoxValue?Container():
SearchableDropdown.multiple(
hint: "Select Exercise",
style: TextStyle(color: Colors.black),
items: widget.exerciseList.map<DropdownMenuItem>((item){
return DropdownMenuItem(
child: Text(item.name), value: item
);
}).toList(),
selectedItems: _selectedExercises,
onChanged: (values){
setState(() {
_selectedExercises = values;
});
},
isExpanded: true,
dialogBox: true,
),
],
);
}
#override
Widget build(BuildContext context) {
print("<><><><><><><><><><><>${widget.weekDay} called");
return Card(
elevation: 8.0,
child: Form(
key: GlobalKey(),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
cardHeader(),
_successfulPost?Container():cardBody()
],
),
),
);
}
}
As you can see, I've tried callBack from parent widget which increases or decrease no of form saved from each of the child widget. It does the work but, when one form is saved, parent state is modified and all other children got rebuild which is unnecessary in my opionion. What's the best way to do it?
Try to use GlobalKey instead of UniqueKey for each RoutineFormCard. It will help you to access the state of each RoutineFormCard. You can do it like this :
// 1. In the top of your CreateRoutineScreen file, add this line (make your RoutineFormCardState class public before)
final List<GlobalKey<RoutineFormCardState>> routineFormCardKeys = <GlobalKey<RoutineFormCardState>>[
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
];
// 2. Then construct your RoutineFormCard using the right key
RoutineFormCard(
weekDay: widget.weekDays[index]["name"],
exerciseList: snapshot.data,
userID : widget.userID,
changeNoOfRoutineSaved:_changeNoOfRoutineSaved,
key: routineFormCardKeys[index]
);
// 3. Now you can create a method in CreateRoutineScreen which will check the state of all RoutineFormCard
bool _allRoutineFormCardsCompleted() {
bool result = true;
for (int i = 0; i < 7; i++)
result = result && routineFormCardKeys[i].currentState.isPostSuccessful;
return result;
}
// 4. Finally use the result of the previous method where you want to move on another page
I'm sharing a quick idea to solve your problem, I've not tested it, but I'm ready to improve the answer if needed
Hope this will help!

how to refresh state on Navigator.Pop or Push in flutter

Here I have two pages first is called BSP_signup_terms page and the second is Bsp_Service_page. when I am on BSP_signup_terms on that page I have to select some checkbox based on the selected checkbox it will show me some data. but problem is that it will show me the complete data but when I get back to the BSP_signup_terms from Bsp_signup_page and I am changing the checkbox and then again when I am pressing next button it will not change the result it same as the previous result.
Here is the Image of Output Page
In this image I've attached both screen output when I am selecting only one checkbox it will render some value in service page and when I am back to the Terms and Condition page and select one more checkbox then it will not updating service page
Here is the code I've tried.
BSP_Signup_Terms_Page
class BspLicensedSignupTermsPage extends StatefulWidget {
static const String routeName = "/bspLicensedSignupTerms";
final BspSignupCommonModel bspSignupCommonModel;
BspLicensedSignupTermsPage({
Key key,
#required this.bspSignupCommonModel,
}) : super(key: key);
#override
_BspLicensedSignupTermsPageState createState() =>
_BspLicensedSignupTermsPageState();
}
class _BspLicensedSignupTermsPageState
extends State<BspLicensedSignupTermsPage> {
#override
void initState() {
super.initState();
}
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
bool _isWalkIn = false;
bool _isHome = false;
bool _isOnDemand = false;
Widget _buildselectcheckbox() {
return Text(
AppConstantsValue.appConst['bsplicensedsignupterms']['selectcheck']
['translation'],
);
}
// Walkin
_onCustomerWalkin(value) {
setState(() {
_isWalkIn = value;
});
}
Widget _buildCustomerWalkIn() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bsplicensedsignupterms']
['CustomerWalkIn']['translation'],
onChanged: (value) {
print(value);
_onCustomerWalkin(value);
},
validate: false,
);
}
// Home
_onCustomerInHome(value) {
setState(() {
_isHome = value;
});
}
Widget _buildCustomerInHome() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bsplicensedsignupterms']
['CustomerInHome']['translation'],
onChanged: (value) {
_onCustomerInHome(value);
},
validate: false,
);
}
Widget _buildCustomerInHomeHelp() {
return Text(
AppConstantsValue.appConst['bsplicensedsignupterms']['businesscheckhelp']
['translation'],
);
}
// On Demand
_onCustomerOnDemand(value) {
setState(() {
_isOnDemand = value;
});
}
Widget _buildBusinessOnDemand() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bsplicensedsignupterms']
['BusinessOnDemand']['translation'],
onChanged: (value) {
_onCustomerOnDemand(value);
},
validate: false,
);
}
Widget _buildBusinessOnDemandHelp() {
return Text(AppConstantsValue.appConst['bsplicensedsignupterms']
['businessprovidehelp']['translation']);
}
#override
Widget build(BuildContext context) {
final appBar = AppBar(
title: Text("Bsp Licensed Signup Terms and Condition"),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
NavigationHelper.navigatetoBack(context);
},
),
centerTitle: true,
);
final bottomNavigationBar = Container(
height: 56,
//margin: EdgeInsets.symmetric(vertical: 24, horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FlatButton.icon(
icon: Icon(Icons.close),
label: Text('Clear'),
color: Colors.redAccent,
textColor: Colors.black,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
_formKey.currentState.reset();
},
),
new FlatButton.icon(
icon: Icon(FontAwesomeIcons.arrowCircleRight),
label: Text('Next'),
color: colorStyles["primary"],
textColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
if (_formKey.currentState.validate()) {
if (_isHome == false &&
_isOnDemand == false &&
_isWalkIn == false) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => ShowErrorDialog(
title: Text('Select Service'),
content: Text(
'Please select atleast one service type to proceed next',
),
));
} else {
BspSignupCommonModel model = widget.bspSignupCommonModel;
model.isWalkin = _isWalkIn;
model.isHome = _isHome;
model.isOnDemand = _isOnDemand;
print(model.toJson());
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
BspServicePage(bspSignupCommonModel: model),
),
);
}
}
},
),
],
),
);
return new Scaffold(
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: Container(
height: double.infinity,
width: double.infinity,
child: Stack(
children: <Widget>[
SingleChildScrollView(
child: SafeArea(
child: Form(
autovalidate: true,
key: _formKey,
child: Scrollbar(
child: SingleChildScrollView(
dragStartBehavior: DragStartBehavior.down,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: new Container(
decoration: BoxDecoration(
borderRadius: new BorderRadius.circular(25)),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildselectcheckbox(),
_buildCustomerWalkIn(),
_buildCustomerInHome(),
_buildCustomerInHomeHelp(),
_buildBusinessOnDemand(),
_buildBusinessOnDemandHelp(),
],
),
),
),
),
),
),
),
],
),
),
);
}
}
BSP_Service_Page
class BspServicePage extends StatefulWidget {
static const String routeName = "/bspService";
final BspSignupCommonModel bspSignupCommonModel;
BspServicePage({
Key key,
#required this.bspSignupCommonModel,
}) : super(key: key);
#override
_BspServicePageState createState() => _BspServicePageState();
}
class _BspServicePageState extends State<BspServicePage> {
List<int> servicesIds = [];
Map<String, bool> selection = {};
List<BspServices.Service> selectedServices = [];
SearchBarController _controller = new SearchBarController();
String _searchText = '';
bool refreshservices = true;
#override
void initState() {
super.initState();
}
void _showErrorDialog(String message) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => ShowErrorDialog(
title: Text('An Error Occurred!'),
content: Text(message),
),
);
}
void refresh() {
setState(() {
refreshservices = !refreshservices;
});
}
#override
Widget build(BuildContext context) {
var _bspServiceBloc = new BspServiceBloc();
final appBar = SearchBar(
controller: _controller,
onQueryChanged: (String query) {
print('Search Query $query');
setState(() {
_searchText = query;
});
},
defaultBar: AppBar(
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
refresh();
NavigationHelper.navigatetoBack(context);
}),
title: Text('Select Services'),
),
);
final bottomNavigationBar = Container(
height: 56,
// margin: EdgeInsets.symmetric(vertical: 24, horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FlatButton.icon(
icon: Icon(Icons.close),
label: Text('Clear'),
color: Colors.redAccent,
textColor: Colors.black,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
print('reseting the state');
setState(() {
selection = {};
servicesIds = [];
});
},
),
new FlatButton.icon(
icon: Icon(FontAwesomeIcons.arrowCircleRight),
label: Text('Next'),
color: colorStyles["primary"],
textColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
BspSignupCommonModel model = widget.bspSignupCommonModel;
model.servicesIds = servicesIds;
model.services = selectedServices;
print('servicesIds at the next button');
print(servicesIds);
print(model.toJson());
if (servicesIds.length == 0) {
_showErrorDialog(
'You need to select at least one service to proceed next!');
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BusinessProfilePage(
bspSignupCommonModel: model,
),
),
);
}
},
),
],
),
);
return new Scaffold(
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: new BspServiceScreen(
bspServiceBloc: _bspServiceBloc,
bspSignupCommonModel: widget.bspSignupCommonModel,
servicesIds: servicesIds,
selection: selection,
searchQuery: _searchText,
selectedServices: selectedServices,
refresh: refresh,
),
);
}
}
Bsp_service_screen
class BspServiceScreen extends StatefulWidget {
final BspServiceBloc _bspServiceBloc;
final String searchQuery;
final List<int> servicesIds;
final Map<String, bool> selection;
final BspSignupCommonModel bspSignupCommonModel;
final List<BspServices.Service> selectedServices;
final Function refresh;
const BspServiceScreen({
Key key,
#required BspServiceBloc bspServiceBloc,
#required this.bspSignupCommonModel,
#required this.servicesIds,
#required this.selection,
#required this.selectedServices,
#required this.refresh,
this.searchQuery,
}) : _bspServiceBloc = bspServiceBloc,
super(key: key);
#override
BspServiceScreenState createState() {
return new BspServiceScreenState(_bspServiceBloc);
}
}
class BspServiceScreenState extends State<BspServiceScreen> {
final BspServiceBloc _bspServiceBloc;
BspServiceScreenState(this._bspServiceBloc);
// Map<String, bool> _selection = {};
#override
void initState() {
super.initState();
bool isHome = widget.bspSignupCommonModel.isHome;
bool isWalkIn = widget.bspSignupCommonModel.isWalkin;
bool isOnDemand = widget.bspSignupCommonModel.isOnDemand;
this._bspServiceBloc.dispatch(LoadBspServiceEvent(
countryId: 1,
isHome: isHome,
isOnDemand: isOnDemand,
isWalkin: isWalkIn,
));
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<BspServiceBloc, BspServiceState>(
bloc: widget._bspServiceBloc,
builder: (
BuildContext context,
BspServiceState currentState,
) {
if (currentState is UnBspServiceState) {
return Center(child: CircularProgressIndicator());
}
if (currentState is ErrorBspServiceState) {
return new Container(
child: new Center(
child: new Text(currentState.errorMessage ?? 'Error'),
),
);
}
if (currentState is InBspServiceState) {
// print(
// 'in bsp service state, ${currentState.bspServices.servicesByCountry.length}');
if (currentState.bspServices.servicesByCountry.length == 0) {
return Container(
child: Center(
child: Text("No Services available for this combination"),
),
);
} else {
return new Container(
child:
_renderServices(currentState.bspServices.servicesByCountry),
);
}
}
return Container();
},
);
}
List<ServicesByCountry> finalList = new List();
ListView _renderServices(List<ServicesByCountry> lovCountryServices) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.searchQuery != '') {
finalList.clear();
lovCountryServices.forEach((ServicesByCountry data) {
if (data.name
.toLowerCase()
.contains(widget.searchQuery.toLowerCase())) {
setState(() {
finalList.add(data);
});
} else {
data.services.forEach((ServiceList.Service services) {
if (services.name
.toLowerCase()
.contains(widget.searchQuery.toLowerCase())) {
setState(() {
finalList.add(data);
});
}
});
}
});
} else {
setState(() {
finalList.clear();
finalList.addAll(lovCountryServices);
});
}
});
return ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
itemCount: finalList.length,
itemBuilder: (BuildContext context, int index) {
ServicesByCountry item = finalList[index];
List itemsList = item.services;
return ExpansionTile(
title: Text(item.name),
children: List.generate(itemsList.length, (i) {
widget.selection[itemsList[i].name] =
widget.selection[itemsList[i].name] ?? itemsList[i].isSelected;
return CheckboxListTile(
title: Text(itemsList[i].name),
value: widget.selection[itemsList[i].name],
onChanged: (val) {
setState(() {
widget.selection[itemsList[i].name] = val;
if (val) {
widget.servicesIds.add(itemsList[i].id);
List<BspServices.Service> services =
widget.selectedServices.where((service) {
return service.mainCategory == item.name;
}).toList();
SubCategory subService = new SubCategory(
id: itemsList[i].id,
name: itemsList[i].name,
);
List<SubCategory> subCategories = [];
if (services.length == 0) {
subCategories.add(subService);
widget.selectedServices.add(
new BspServices.Service(
mainCategory: item.name,
mainCategoryId: item.id,
subCategory: subCategories,
),
);
} else {
print('services in else');
print(services[0].subCategory);
subCategories = services[0].subCategory;
subCategories.add(subService);
}
} else {
widget.servicesIds.removeWhere((service) {
return service == itemsList[i].id;
});
List<BspServices.Service> services =
widget.selectedServices.where((service) {
return service.mainCategory == item.name;
}).toList();
services[0].subCategory.removeWhere((subService) {
return subService.id == itemsList[i].id;
});
}
});
print('widget.servicesIds after set state');
print(widget.servicesIds);
},
);
}),
);
},
);
}
}
You can use setState() after return to the first page:
Navigator.push(context, MaterialPageRoute(builder: (context) => Page2())).then((value) {
setState(() {
// refresh state
});
});
Please try below code:-
First you add one method async method:-
void redirectToNextScreen() async {
final Route route = MaterialPageRoute(
builder: (context) => BspServicePage(bspSignupCommonModel: model));
final result = await Navigator.push(mContext, route);
try {
if (result != null) {
if (result) {
//Return callback here.
}
}
} catch (e) {
print(e.toString());
}
}
Then Next you can call this method in "BSP_Signup_Terms_Page" on Next button Pressed event.
Second you can add below line in "BspServicePage" screen Next and Cancel Events.
Navigator.pop(mContext, true); //true means refresh back page and false means not refresh.