My Bad
It was problem with my future function HashtagService().getSuggestion('topic'); returning empty List before data is properly loaded. EditTopicPage didn't have any problem.
Original Question
I have text form field inside futurebuilder. When I first open page, future is not loaded. When I click on text field to enter something, future is loaded.
I want future to be loaded when the page is first opened.
class EditTopicPage extends StatefulWidget {
const EditTopicPage({required UserProfileModel userProfile, Key? key}) : _userProfile = userProfile, super(key: key);
final UserProfileModel _userProfile;
#override
_EditTopicPageState createState() => _EditTopicPageState();
}
class _EditTopicPageState extends State<EditTopicPage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController _controller = TextEditingController();
List<String> _hashList = [];
List<String> _suggestionList = [];
late final Future<List<String>> _future;
bool _disabled = true;
final RegExp hashRegex = RegExp(r'^[a-z|A-Z|ㄱ-ㅎ|ㅏ-ㅣ|가-힣|ㆍ|ᆢ]*$');
#override
void initState() {
_future = HashtagService().getSuggestion('topic');
_hashList = widget._userProfile.topic.cast<String>();
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
UserProfileService _userProfileService = UserProfileService();
final Size size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
appBar: TopBar(pageTitle: "관심 대화 주제 수정", context: context),
body: ListView(
padding: EdgeInsets.all(16.sp),
children: [
FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
_suggestionList = List<String>.from(snapshot.data.reversed);
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SingleLineTextFormField(
controller: _controller,
hintText: '어떤 주제로 대화하고 싶으신가요?',
onChanged: (value) {
if (value.length > 1) {
var lastChar = value.substring(
value.length - 1);
if (!hashRegex.hasMatch(lastChar)) {
var newHash = value.substring(
0, value.length - 1);
if (!_hashList.contains(newHash)) {
setState(() {
_hashList.add(newHash);
_disabled = _hashList.isEmpty || (_hashList.length > 3);
});
}
_controller.clear();
}
} // else if (_expHash.length == 3 && value.isNotEmpty) {
_formKey.currentState!.validate();
},
validator: (value) {
if (!hashRegex.hasMatch(value!)) {
return '\u26a0 한글, 영문만 가능해요';
}
return null;
}
),
DefaultSpacing(),
Row(
children: [
Text('추천 : ',
style: TextStyle(
fontSize: 10.sp,
color: Colors.grey[800],
),
),
Wrap(
spacing: 6.0,
runSpacing: 6.0,
children: _suggestionList.map((suggestion) =>
HashtagSuggestionChip(
suggestion: suggestion,
type: 'topic',
onPressed: () {
if (!_hashList.contains(suggestion)) {
setState(() {
_hashList.add(suggestion);
_disabled = _hashList.isEmpty || (_hashList.length > 3);
});
}
},
)).toList(),
),
],
),
DefaultSpacing(),
Wrap(
spacing: 6.0,
runSpacing: 6.0,
children: _hashList.map((hashtag) =>
HashtagChip(
hashtag: hashtag,
type: 'topic',
onDelete: () {
setState(() {
_hashList.remove(hashtag);
_formKey.currentState!.validate();
_disabled = _hashList.isEmpty || (_hashList.length > 3);
});
},
)).toList()
),
]),
);
}
return CircularProgressIndicator();
}
),
],
),
floatingActionButton: FloatingSaveButton(context: context,
text: '저장',
width: size.width * 0.9,
disabled: _disabled,
onPressed: () async {
_userProfileService.updateTopicHashtag(hashList: _hashList);
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(SaveSnackBar());
}),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
Result: when the page is first opened
Neither hashtag nor progress indicator is shown.
After text form field is selected
I searched similar questions in stackoverflow, but none of the answers solved my problem.
Try like this
#override
void initState() {
setState(() {
_future = HashtagService().getSuggestion('topic');
_hashList = widget._userProfile.topic.cast<String>();
});
super.initState();
}
so your _future is async so the widget will be loaded before loading the service
Related
How can I scroll to a special widget in a ListView? For example, I want to automatically scroll to some container in ListView if I press a certain button on a previous screen. I will pass to the next screen an Id (from id I will know the index) and when I navigate to the next screen I want to automatically scroll to this widget.
the code in main screen : Navigator.push(context, MaterialPageRoute(builder: (_) => CreatedEstatesScreen(estateId: id)));
the code in the next screen :
class RecentEstateOrdersScreen extends StatefulWidget {
static const String id = "RecentEstateOrdersScreen";
String? estateId;
RecentEstateOrdersScreen({Key? key, this.estateId}) : super(key: key);
#override
_RecentEstateOrdersScreenState createState() =>
_RecentEstateOrdersScreenState();
}
class _RecentEstateOrdersScreenState extends State<RecentEstateOrdersScreen> {
late RecentEstatesOrdersBloc _recentEstatesOrdersBloc;
late ItemScrollController scrollController;
late ItemPositionsListener itemPositionsListener;
String? userToken;
List<EstateOrder> orders = [];
#override
void initState() {
super.initState();
_recentEstatesOrdersBloc = RecentEstatesOrdersBloc(EstateOrderRepository());
_onRefresh();
User? user = BlocProvider.of<UserLoginBloc>(context).user;
if (user != null && user.token != null) {
userToken = user.token;
}
scrollController = ItemScrollController();
itemPositionsListener = ItemPositionsListener.create();
}
_onRefresh() {
if (BlocProvider.of<UserLoginBloc>(context).user!.token != null) {
_recentEstatesOrdersBloc.add(
RecentEstatesOrdersFetchStarted(
token: BlocProvider.of<UserLoginBloc>(context).user!.token!),
);
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(
AppLocalizations.of(context)!.recent_created_orders,
),
),
body: BlocConsumer<RecentEstatesOrdersBloc, RecentEstatesOrdersState>(
bloc: _recentEstatesOrdersBloc,
listener: (context, recentOrdersState) async {
if (recentOrdersState is RecentEstatesOrdersFetchError) {
var error = recentOrdersState.isConnectionError
? AppLocalizations.of(context)!.no_internet_connection
: recentOrdersState.error;
await showWonderfulAlertDialog(
context, AppLocalizations.of(context)!.error, error);
}
},
builder: (BuildContext context, recentOrdersState) {
if (recentOrdersState is RecentEstatesOrdersFetchProgress) {
return const ClientsOrdersShimmer();
}
if (recentOrdersState is! RecentEstatesOrdersFetchComplete) {
return Container();
}
orders = recentOrdersState.estateOrders;
if (orders.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
documentOutlineIconPath,
width: 0.5.sw,
height: 0.5.sw,
color: Theme.of(context)
.colorScheme
.onBackground
.withOpacity(0.42),
),
48.verticalSpace,
Text(
AppLocalizations.of(context)!.have_not_recent_orders,
style: Theme.of(context).textTheme.headline4,
),
],
),
);
}
if (widget.estateId != null) {
SchedulerBinding.instance!.addPostFrameCallback((_) {
jumpToOrder(orders);
});
}
return RefreshIndicator(
color: Theme.of(context).colorScheme.primary,
onRefresh: () async {
_onRefresh();
},
child: ListView.builder(
itemCount: orders.length,
itemBuilder: (_, index) {
return EstateOrderCard(
estateOrder: orders.elementAt(index),
);
}),
);
},
),
),
);
}
jumpToOrder(List<EstateOrder> orders) {
int index = getIndexFromId(orders);
if (index != -1) {
if (scrollController.isAttached) {
scrollController.scrollTo(
index: index,
duration: const Duration(seconds: 2),
curve: Curves.easeInOutCubic);
}
}
}
getIndexFromId(List<EstateOrder> orders) {
for (int i = 0; i < orders.length; i++) {
if (orders.elementAt(i).id == int.parse(widget.estateId!)) {
return i;
}
}
return -1;
}
}```
If you are using the library then you have to use ScrollablePositionedList.builder, not the normal ListView.builder.
I have this comment section in my application.
User can like a comment.
Whenever a comment is added, I call the database to get the latest comment and rebuild the class.
But as shown below When a new comment is added, it retains the state of the previous comment
as seen above when a not liked comment is added it is having state of previous comment
Below is the given code
#override
void initState() {
getComments();
super.initState();
}
getComments() async {
try {
List<Comment> commentList =
await Provider.of<Database>(context, listen: false)
.postComments(widget.postid);
setState(() {
data = commentList;
});
} catch (e) {
setState(() {
data = 'error';
});
// throw ('e');
}
}
dynamic data;
addComment(BuildContext context) async {
String commentData = _textEditingController.text.trim();
if (commentData.isNotEmpty) {
setState(() {
showShimmer = true;
_textEditingController.clear();
});
bool result = await Provider.of<Database>(context, listen: false)
.addMessageToPost(widget.postid, true, commentData);
if (result) {
getComments();
}
}
}
final TextEditingController _textEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: settingsAppBar(context, 'Comments'),
body: Column(
children: [
showShimmer
? Shimmer.fromColors(
baseColor: Colors.grey[200]!,
highlightColor: Colors.grey[100]!,
child: ShimmerWidget())
: Container(),
Expanded(
child: data == null
? Center(child: CircularProgressIndicator())
: data == 'error'
? Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextButton(
onPressed: () {
getComments();
},
child: Text('Retry')),
],
),
)
: data.length >= 1
? RefreshIndicator(
onRefresh: () async {
setState(() {
data = null;
});
getComments();
},
child: ListView(
controller: _scrollController,
children: buidlCommentWidgets()),
)
: Text('No data'),
),
SafeArea(...textfield) ],
),
),
],
),
),
],
),
);
}
List<Widget> buidlCommentWidgets() {
List<Widget> commentWidgetList = [];
for (var i = 0; i < data.length; i++) {
commentWidgetList.add(Padding(
padding: const EdgeInsets.symmetric(vertical: 0.0, horizontal: 8.0),
child: CommentListTile(
comment: data[i],
postUid: widget.postedUid,
),
));
}
return commentWidgetList;
}
}
//////////////////////////////////////////////////////////////
class CommentListTile extends StatefulWidget {
const CommentListTile({
Key? key,
required this.comment,
required this.postUid,
this.isReply = false,
}) : super(key: key);
final Comment comment;
final String postUid;
final bool isReply;
#override
_CommentListTileState createState() => _CommentListTileState();
}
class _CommentListTileState extends State<CommentListTile> {
late bool isLiked;
late int likeCount;
handleLike(BuildContext context) async {
setState(() {
isLiked = !isLiked;
likeCount = isLiked ? likeCount + 1 : likeCount - 1;
});
bool result = await Provider.of<Database>(context, listen: false)
.postActivity(
PostActivityEnum.likeDislikeComment, widget.comment.commentId);
if (!result) {
setState(() {
isLiked = !isLiked;
likeCount = isLiked ? likeCount + 1 : likeCount - 1;
});
}
}
#override
void initState() {
isLiked = widget.comment.isLikedByViewer;
likeCount = widget.comment.likesCount;
super.initState();
}
#override
Widget build(BuildContext context) {
....ListTile
This is a very common problem in the flutter.
This happens due to the widget tree and the render tree difference, Identification of the widget by its own Key can be easier for the flutter to keep an update on both render and widget tree.
For an easy solution, you can pass a unique key to the CommentListTile widget while building the list and also while adding a new widget to the list.
List<Widget> buidlCommentWidgets() {
List<Widget> commentWidgetList = [];
for (var i = 0; i < data.length; i++) {
commentWidgetList.add(Padding(
padding: const EdgeInsets.symmetric(vertical: 0.0, horizontal: 8.0),
child: CommentListTile(
key: SOME_UNIQUE_KEY_HERE // Add Unique key here
comment: data[i],
postUid: widget.postedUid,
),
));
}
return commentWidgetList;
}
It can be easy if you have some kind of commentId.
EDIT: 27 Aug 2021
You definitely should use your buildCommentWidgets() method like this:
List<Widget> buidlCommentWidgets() {
return data.map((comment) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: CommentListTile(
key: SOME_UNIQUE_KEY_HERE // Add Unique key here
comment: comment,
postUid: widget.postedUid,
),
);
}).toList();
}
I'm building a list of news from an api that has next page results as in the image attached.
The api has only two pages with 10 list items each page.
Data is being passed to the widget. My problem is that when I scroll down the view, it loads the same 10 list items from page one.
This is the api I'm using enter link description here
Rest API
//newsModal.dart
class NewsNote {
String banner_image;
String title;
String text;
String sport;
NewsNote(this.banner_image, this.title, this.text, this.sport);
NewsNote.fromJson(Map<String, dynamic> json) {
banner_image = json['banner_image'];
title = json['title'];
text = json['text'];
sport = json['sport'];
}
}
//page news
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:jabboltapp/models/newsModal.dart';
class JabNews extends StatefulWidget {
#override
_JabNewsState createState() => _JabNewsState();
}
class _JabNewsState extends State<JabNews> {
ScrollController _scrollController = ScrollController();
bool isLoading = false;
String url = "https://jabbolt.com/api/news";
List<NewsNote> _newsNotes = List<NewsNote>();
Future<List<NewsNote>> fetchNewsNotes() async {
if (!isLoading) {
setState(() {
isLoading = true;
});
var response = await http.get(url);
var newsNotes = List<NewsNote>();
if (response.statusCode == 200) {
url = jsonDecode(response.body)['next'];
var newsNotesJson = json.decode(response.body)["results"];
for (var newsNoteJson in newsNotesJson) {
newsNotes.add(NewsNote.fromJson(newsNoteJson));
}
setState(() {
isLoading = false;
_newsNotes.addAll(newsNotes);
});
} else {
setState(() {
isLoading = false;
});
}
return newsNotes;
}
}
#override
void initState() {
fetchNewsNotes().then((value) {
setState(() {
_newsNotes.addAll(value);
});
});
this.fetchNewsNotes();
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
fetchNewsNotes();
}
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Widget _buildProgressIndicator() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Opacity(
opacity: isLoading ? 1.0 : 00,
child: CircularProgressIndicator(),
),
),
);
}
Widget _buildList() {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
if (index == _newsNotes.length) {
return _buildProgressIndicator();
} else {
return Padding(
padding: EdgeInsets.all(8.0),
child: Card(
child: ListTile(
title: Text((_newsNotes[index].title)),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(_newsNotes[index])));
},
),
),
);
}
},
controller: _scrollController,
itemCount: _newsNotes.length,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: dGrey,
appBar: AppBar(
title: Text(
"News",
style: TextStyle(
color: textGrey,
fontFamily: 'bison',
fontSize: 32.0,
letterSpacing: 1.2,
),
),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Container(
child: _buildList(),
),
);
}
}
You need to add the page number concatenation in the URL
https://jabbolt.com/api/news?page=2
I gave up trying to find the reason setState() is not running build method. I have a ChoiceChip which has a callback function. Debug showed me that I actually receive the selected chip at the callback but setState() is not updating the ui. I have spent all day trying to understand why setState() is not running the build() method. Here is my code
class SocialStoryCategory extends StatefulWidget {
final Function(String) onMenuItemPress;
SocialStoryCategory({Key key, #required this.onMenuItemPress}) : sup er(key: key);
#override
_SocialStoryCategoryState createState() => _SocialStoryCategoryState();
}
class _SocialStoryCategoryState extends State<SocialStoryCategory> {
int _value = 0;
List<String> categoryList;
#override
Widget build(BuildContext context) {
categoryList = [
NoomeeLocalizations.of(context).trans('All'),
NoomeeLocalizations.of(context).trans('Communication'),
NoomeeLocalizations.of(context).trans('Behavioral'),
NoomeeLocalizations.of(context).trans('ADL'),
NoomeeLocalizations.of(context).trans('Other')
];
return Wrap(
spacing: 4,
children: List<Widget>.generate(5, (int index) {
return Theme(
data: ThemeData.from(
colorScheme: ColorScheme.light(primary: Colors.white)),
child: Container(
child: ChoiceChip(
elevation: 3,
selectedColor: Colors.lightBlueAccent,
label: Text(categoryList.elementAt(index)),
selected: _value == index,
onSelected: (bool selected) {
setState(() {
_value = selected ? index : null;
if (categoryList.elementAt(_value) == "All") {
widget.onMenuItemPress("");
} else {
widget.onMenuItemPress(categoryList.elementAt(_value));
}
});
},
),
),
);
}).toList());
}
}
Here is the place where I get the callback
class SocialStoriesHome extends StatefulWidget {
#override
_SocialStoriesHomeState createState() => _SocialStoriesHomeState();
}
class _SocialStoriesHomeState extends State<SocialStoriesHome>
with TickerProviderStateMixin {
String title;
TabController _tabController;
int _activeTabIndex = 0;
String _defaultStoryCategory;
_goToDetailsPage() {
Navigator.of(context).pushNamed("parent/storyDetails");
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: 2);
_defaultStoryCategory = '';
}
#override
Widget build(BuildContext context) {
return BaseWidget<SocialStoryViewModel>(
model: new SocialStoryViewModel(
socialStoriesService: Provider.of(context),
),
onModelReady: (model) =>
model.fetchDefaultStoriesByCategory(_defaultStoryCategory),
builder: (context, model, child) => DefaultTabController(
length: 2,
child: Scaffold(
body: model.busy
? Center(child: CircularProgressIndicator())
: Container(
child: Column(
children: <Widget>[
new SocialStoryCategory(
onMenuItemPress: (selection) {
setState(() {
_defaultStoryCategory = selection;
});
},
),
Expanded(
child: ListView(
children: getStories(model.socialStories),
),
),
],
),
),
);
}
}
List<Widget> getStories(List<SocialStoryModel> storyList) {
List<Widget> list = List<Widget>();
for (int i = 0; i < storyList.length; i++) {
list.add(Padding(
padding: const EdgeInsets.all(8.0),
child: Template(
title: storyList[i].name,
subTitle: storyList[i].categories,
hadEditIcon: false),
));
}
return list;
}
Finally I have found the solution.
I have simply replaced
new SocialStoryCategory(onMenuItemPress: (selection) {
setState(() {
_defaultStoryCategory = selection;
});
},
),
to
new SocialStoryCategory(
onMenuItemPress: (selection) {
model.fetchDefaultStoriesByCategory(selection);
},
),
my viewModel extend change notifier and build a child as consumer so I totally understand why it works. But I still do not understand why the previous version was not working. I will feel happy again only when you explain me the problem,
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!