Slider onChangeEnd property doesn't work properly - flutter

I want to save to Firestore the last slider value after changes. I tried to do that using onChangeEnd, but instead of last value, I get first picked value.
Examples
What I want to get:
Current slider position is 3. I want to press 3 on slider and slide it to 5 because my answer is 5. I want to be saved in Firestore that my answer is 5. After that, it's not possible to move slider and give new answer.
What I am getting #1:
Current slider position is 3. I press 3 on slider and I want to slide it to 5 because I want my answer to be 5. In Firestore is saved that my answer is 3. After that, it's not possible to move slider and give new answer.
What I am getting #2:
Current slider position is 3. I press 5 on slider (without sliding) and I want my answer to be 5. In Firestore is saved that my answer is 5. After that, it's not possible to move slider and give new answer.
import 'package:flutter/material.dart';
import 'package:gamiforms/services/database.dart';
class LinearScale extends StatefulWidget {
String question, formId;
LinearScale(this.question, this.formId);
#override
LinearScaleState createState() => LinearScaleState(question);
}
class LinearScaleState extends State<LinearScale> {
String question;
LinearScaleState(this.question);
double overall = 3.0;
String overallStatus = "Good";
static final formKey = new GlobalKey<FormState>();
DatabaseService databaseService = DatabaseService();
String answer = "";
bool isLoading = false;
bool enabled = true;
uploadFormData(overall) {
//if (formKey.currentState.validate()) {
setState(() {
isLoading = true;
});
print('overall $overall');
if (overall == 1.0) answer = "Fail";
if (overall == 2.0) answer = "Acceptable";
if (overall == 3.0) answer = "Good";
if (overall == 4.0) answer = "Very good";
if (overall == 5.0) answer = "Excellent";
print('answer $answer');
Map<String, String> answerMap = {
"question": this.question,
"answer": answer,
};
print("${widget.formId}");
databaseService.addAnswerData(answerMap, widget.formId).then((value) {
answer = "";
question = this.question;
setState(() {
isLoading = false;
});
}).catchError((e) {
print(e);
});
}
#override
Widget build(BuildContext context) {
var screenSize = MediaQuery.of(context).size;
var width = screenSize.width;
var height = screenSize.height;
print('TEST $question');
return SizedBox(
width: width,
height: height - 100,
child: Container(
margin: EdgeInsets.only(top: 30.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
margin: EdgeInsets.only(left: 16.0),
child: Text(
question,
style: TextStyle(fontSize: 20),
)),
Container(
margin: EdgeInsets.symmetric(vertical: 50.0),
child: Text(
overallStatus,
style: TextStyle(
color: Colors.teal[800],
fontWeight: FontWeight.bold,
fontSize: 30.0),
textAlign: TextAlign.center,
),
),
Expanded(
child: Center(
child: Slider(
value: overall,
onChanged: (value) {
setState(() {
overall = value.round().toDouble();
_getOverallStatus(overall);
});
},
onChangeEnd: enabled
? (value) {
enabled = false;
setState(() {
overall = value.round().toDouble();
uploadFormData(overall);
});
}
: null,
label: '${overall.toInt()}',
divisions: 4,
min: 1.0,
max: 5.0,
),
),
)
],
),
),
);
}
_getOverallStatus(double overall) {
switch (overall.toInt()) {
case 1:
overallStatus = 'Fail';
break;
case 2:
overallStatus = 'Acceptable';
break;
case 3:
overallStatus = 'Good';
break;
case 4:
overallStatus = 'Very good';
break;
default:
overallStatus = 'Excellent';
break;
}
}
}

Related

Is There a way to scroll to a specific cell on a Table?

I am using a Table to represent my CSV data, but I have not found any way to scroll programmatically to the searched cell.
If the cell text content is 123, I would like to be able to
scroll to the cell programmatically.
I can not use a Listview or Listview.builder because I want to display the data on a Table.
I have seen many examples on the internet but they all use the Listview because its contents have a fixed width and height, but I wonder if it's possible to scroll on Tables.
this is a code example I am using:
It loads the csv data into the Table and while it fills its content it checks if the content is in a list I provide and will change its cell formatting.
class InventoryMap extends StatelessWidget {
final Map singleOrMulti;
const InventoryMap({Key key, this.singleOrMulti}) : super(key: key);
#override
Widget build(BuildContext context) {
final bool _isMapLoaded =
context.watch<InventoryMapDataProvider>().getIsMapLoaded;
final List<List<dynamic>> _csvMapList =
context.watch<InventoryMapDataProvider>().getCsvMapList;
List itemsToLook = [];
bool isSingleItem = false;
List<CartItem> currentItemList = [];
if (singleOrMulti['single'] != null) {
itemsToLook.add(singleOrMulti['single']);
isSingleItem = true;
} else if (singleOrMulti['multi'] != null) {
isSingleItem = false;
UploadedInvoice invoice = singleOrMulti['multi'];
invoice.invoice.forEach((e) {
final item = CartItem.fromJson(e);
currentItemList.add(item);
itemsToLook.add(item.code);
});
}
switch (_isMapLoaded) {
case true:
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Table(
defaultColumnWidth: FixedColumnWidth(80.0),
border: TableBorder.all(width: 1.0, color: Colors.black12),
children: _csvMapList.map((item) {
return TableRow(
children: item.map((row) {
final itemCode = row.toString();
final hasCode = itemsToLook.contains(itemCode);
String itemCodeDisplay = itemCode;
if (hasCode && !isSingleItem) {
final itemQty = currentItemList
.singleWhere((e) => e.code == itemCode)
.qty;
itemCodeDisplay = "$itemCode ($itemQty)";
}
return Container(
color: hasCode ? Colors.red : null,
child: Text(
itemCodeDisplay,
style: TextStyle(
fontSize: 8.0,
color: hasCode ? Colors.white : null),
),
);
}).toList());
}).toList(),
),
),
),
);
case false:
return CircularProgressIndicator();
break;
default:
return Container();
}
}
}

I can't change the hinttext in the dropdownButton

This is my code, and i dont know why the hint doesnt change, when i click the options, obviously all this code is inside a class, I tried to use a for loop to identify the list positions , and if they were right, it would appear on the screen.
final listgenre = ["Masc", "Fem", "Não Binário"];
var listgenre_value;
String newValue = "";
var valueChoose;
String hintValue = "";
Padding(
padding: EdgeInsets.all(16),
child: Container(
padding: EdgeInsets.only(left: 16, top: 10, right: 15),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 2),
borderRadius: BorderRadius.circular(20)),
child: DropdownButton<String>(
hint: Text("Genero :$hintValue"),
dropdownColor: Colors.white,
icon: Icon(Icons.arrow_drop_down),
iconSize: 36,
iconEnabledColor: Colors.black,
isExpanded: true,
style: TextStyle(fontSize: 17, color: Colors.black),
value: valueChoose,
underline: SizedBox(
width: 320,
height: 200,
),
items: listgenre.map((valueitem) {
return DropdownMenuItem(
value: valueitem,
child: Text(valueitem),
);
}).toList(),
onChanged: (newValue) {
setState(() {
for (int i = 0; i <= listgenre.length; i++) {
if (listgenre[i] != newValue) {
listgenre_value = i + 1;
} else {
hintValue = "$newValue";
// ignore: void_checks
return listgenre_value;
}
}
Object? valueChoose = newValue;
String valueChoosen = valueChoose.toString();
});
},
))),
As per your comments, the same code doesn't give the expected output.
It is only possible if you are also initialising the variables inside the build method, which shouldn't be the case.
Incorrect Code
#override
Widget build(BuildContext context) {
// ---- your variables INSIDE build method
final listgenre = ["Masc", "Fem", "Não Binário"];
var listgenre_value;
String newValue = "";
var valueChoose;
String hintValue = "";
// ------- rest of the code continued
Correct Code
final listgenre = ["Masc", "Fem", "Não Binário"];
var listgenre_value;
String newValue = "";
var valueChoose;
String hintValue = "";
#override
Widget build(BuildContext context) {
// ------- rest of the code continued
As the build method is called every time setState is called, hintValue was being initialised to "" i.e empty string.
The correct way to fix this error is abdev's comment. It will help if you put the variables used by the DropdownButton widget above the build method.
I hope it was helpful!

Show counter to number of elements hidden when overflow occurs in flutter row widget

Can anyone please help to implement this feature of Gmail that shows the counter to number of emails hidden when the email list becomes large ? I want to implement this in row widget where instead of being scrollable extra elements count is shown when overflow occurs.Gmail shows +15 counter for hidden emails
I was Curious to give a try to achieve the same effect, as asked.
Just in case, If anyone want a start for writing a custom one, then below code may help.
Here is my Code, Feel free to give any suggestions,
(For Now delete button in chips is not working bcoz of some logic problem, I will make it work another day)
import 'package:flutter/material.dart';
class Demo3 extends StatefulWidget {
#override
_Demo3State createState() => _Demo3State();
}
class _Demo3State extends State<Demo3> {
String temp = "";
bool showChips = false;
List<Widget> chipsList = new List();
TextEditingController textEditingController = new TextEditingController();
final _focusNode = FocusNode();
int countChipsToDeleteLater = 0;
#override
void initState() {
super.initState();
_focusNode.addListener(() {
print("Has focus: ${_focusNode.hasFocus}");
if (!_focusNode.hasFocus) {
showChips = false;
setState(() {});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: new Container(
height: 500,
child: new Center(
child: Container(
width: 300,
child: !showChips
? Row(
children: [
buildTextField(),
showNumberWidgetIfAny(),
],
)
: Center(
child: Wrap(
children: [
Wrap(
children: buildChips(),
),
buildTextField(),
],
),
),
),
),
),
),
);
}
buildChips() {
return chipsList;
}
buildTextField() {
return Container(
width: 200,
child: new TextField(
showCursor: true,
focusNode: _focusNode,
autofocus: true,
cursorColor: Colors.black,
style: new TextStyle(fontSize: 22.0, color: Colors.black),
controller: textEditingController,
// decoration: InputDecoration.collapsed(
// hintText: "",
// ),
onChanged: (value) {
if (value.contains(" ")) {
checkWhatToStoreInChips(value, countChipsToDeleteLater);
textEditingController.clear();
setState(() {
showChips = true;
});
countChipsToDeleteLater++;
}
},
),
);
}
checkWhatToStoreInChips(String val, int chipsIndex) {
temp = "";
for (int i = 0; i < val.length; i++) {
if (val[i] == " ") {
break;
}
temp = temp + val[i];
}
addToChips(temp, chipsIndex);
}
addToChips(String tmp, int chipsIndex) {
chipsList.add(Chip(
// onDeleted: () {
// if (chipsList.length == 0) {
// countChipsToDeleteLater = 0;
// }
// chipsList.removeAt(chipsIndex);
// print(chipsList.length);
// print(chipsIndex);
// setState(() {});
// },
avatar: CircleAvatar(
backgroundColor: Colors.grey.shade800,
child: Text(tmp[0]),
),
label: Text(temp),
));
}
showNumberWidgetIfAny() {
int len = chipsList.length;
if (len >= 1) {
return GestureDetector(
onTap: () {
showChips = true;
setState(() {});
},
child: new Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.blue,
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: new Text(
"${chipsList.length.toString()} ",
style: new TextStyle(color: Colors.white, fontSize: 22),
),
),
),
);
}
return Container();
}
}
How it works:
Write something in text field, then press space, showChips boolean will become true
onChanged will detect the space and will send the string to a function.
That function will extract the string before space and then will add the string to a chip,
Finally the chip will be added to a chipslist.
We will have a boolean variable to check if the textfield is in focus and when to show the textfield and numberwidget (a widget which will keep count of the total chips, same like you asked in your question) or when to show the chipslist and textfield wraped in a wrap widget.
You can play around by changing the decoration of textfield to collapsed, to it look like the same as gmail.
Check this package, if you want to use custom package for ease.
I was facing a similar issue. I found a way to implement the Overflow count text.
Sample image
You basically have to paint the overflow text, and get its width like below
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: style),
textDirection: TextDirection.ltr,
textScaleFactor: WidgetsBinding.instance.window.textScaleFactor,
)..layout();
var textSize = textPainter.size;
textSize.width;
Then subtract that from the width available. Lets call it x.
Then create a sum of width for each row item(using TextPainter.layout() method mentioned above), till its value is less than x.
This way you'll know how many items can be shown in the row.
I have created a Flutter library to help with this.

Flutter - How to reload the entire page(reload the widget with its initstate again) on the call of an action button?

I have a Stateful Widget containing a custom tab view.
At the initialisation of the widget, category data(All, Science, Trending, Health & Fitness here) is fetched from the firestore and accordingly corresponding widgets are added to the tab view.
To add a new category, it can be selected from the last tab('+' here). Inside the corresponding widget, one can select the categories of his interest.
On the tap of "Add Category" button I am adding the selected category to firestore but after that I want to reload the widget and load its initstate(because the category data is fetched in initstate).
Can somone please explain that how can i achieve the same here?
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_advanced_networkimage/provider.dart';
import 'package:flutter_advanced_networkimage/transition.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:journey_togather/pages/home.dart';
import 'package:journey_togather/pages/invite_friends.dart';
import 'package:journey_togather/pages/journey/create_journey.dart';
import 'package:journey_togather/pages/journey/create_paid_journey.dart';
import 'package:journey_togather/pages/journey/journey_home.dart';
import 'package:journey_togather/pages/locator.dart';
import 'package:journey_togather/services/analytics.dart';
import 'package:journey_togather/settings/appbuilder.dart';
import 'package:journey_togather/settings/sizeconfig.dart';
import 'package:journey_togather/settings/textstyle.dart';
import 'package:journey_togather/widgets/all_user_journey.dart';
import 'package:journey_togather/widgets/pillButton.dart';
import 'package:journey_togather/widgets/progress.dart';
import 'package:uuid/uuid.dart';
class ExploreFeed extends StatefulWidget {
#override
_ExploreFeedState createState() => _ExploreFeedState();
}
class _ExploreFeedState extends State<ExploreFeed>
with TickerProviderStateMixin {
bool isLoading = false;
final AnalyticsService _analyticsService = locator<AnalyticsService>();
TabController tabController;
final _scaffoldKey = GlobalKey<ScaffoldState>();
// this will control the button clicks and tab changing
TabController _controller;
// this will control the animation when a button changes from an off state to an on state
AnimationController _animationControllerOn;
// this will control the animation when a button changes from an on state to an off state
AnimationController _animationControllerOff;
// this will give the background color values of a button when it changes to an on state
Animation _colorTweenBackgroundOn;
Animation _colorTweenBackgroundOff;
// this will give the foreground color values of a button when it changes to an on state
Animation _colorTweenForegroundOn;
Animation _colorTweenForegroundOff;
// when swiping, the _controller.index value only changes after the animation, therefore, we need this to trigger the animations and save the current index
int _currentIndex = 0;
// saves the previous active tab
int _prevControllerIndex = 0;
// saves the value of the tab animation. For example, if one is between the 1st and the 2nd tab, this value will be 0.5
double _aniValue = 0.0;
// saves the previous value of the tab animation. It's used to figure the direction of the animation
double _prevAniValue = 0.0;
List<dynamic> _userInterestCategory = ['All'];
List<dynamic> _userInterestCategoryId = ['All'];
List<Widget> _interestFeedBuild = [];
//saves the newly added interest data of a user from add category tab
List _interestDataId = [];
List _interestDataName = [];
// store the pill buttons for categories in add category page
List<Widget> buttons;
Color _foregroundOn = Colors.white;
Color _foregroundOff = Colors.grey;
// active button's background color
Color _backgroundOn = Colors.green;
// Color _backgroundOff = Colors.black;
Color _backgroundOff = Colors.grey.withOpacity(0.1);
// scroll controller for the TabBar
ScrollController _tabScrollController = new ScrollController();
// this will save the keys for each Tab in the Tab Bar, so we can retrieve their position and size for the scroll controller
List _keys = [];
bool _buttonTap = false;
// ScrollController _scrollController = ScrollController();
#override
void initState() {
setState(() {
isLoading = true;
});
_initialiseData();
// WidgetsBinding.instance.addPostFrameCallback(_initialiseData());
super.initState();
}
_initialiseData()async{
_getInterestData().whenComplete(() {
for (int index = 0; index < _userInterestCategory.length; index++) {
// create a GlobalKey for each Tab
_keys.add(new GlobalKey());
}
// this creates the controller with 6 tabs (in our case)
_controller =
TabController(vsync: this, length: _userInterestCategory.length);
// this will execute the function every time there's a swipe animation
_controller.animation.addListener(_handleTabAnimation);
// this will execute the function every time the _controller.index value changes
_controller.addListener(_handleTabChange);
_animationControllerOff = AnimationController(
vsync: this, duration: Duration(milliseconds: 75));
// so the inactive buttons start in their "final" state (color)
_animationControllerOff.value = 1.0;
_colorTweenBackgroundOff =
ColorTween(begin: _backgroundOn, end: _backgroundOff)
.animate(_animationControllerOff);
_colorTweenForegroundOff =
ColorTween(begin: _foregroundOn, end: _foregroundOff)
.animate(_animationControllerOff);
_animationControllerOn = AnimationController(
vsync: this, duration: Duration(milliseconds: 150));
// so the inactive buttons start in their "final" state (color)
_animationControllerOn.value = 1.0;
_colorTweenBackgroundOn =
ColorTween(begin: _backgroundOff, end: _backgroundOn)
.animate(_animationControllerOn);
_colorTweenForegroundOn =
ColorTween(begin: _foregroundOff, end: _foregroundOn)
.animate(_animationControllerOn);
_userInterestCategoryId.forEach((element) {
if (element != '+') {
_interestFeedBuild.add(
BuildInterestFeed(
categoryId: element,
),
);
} else {
buildButton();
_interestFeedBuild.add(
_buildAddInterest(),
);
}
});
setState(() {
isLoading = false;
});
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
_getInterestData() async {
DocumentSnapshot _doc =
await userInterestsRef.document(currentUser.id).get();
List _interestsFetched = _doc.data['categories'];
_interestsFetched = _interestsFetched.toSet().toList();
Map _userInterestData = {};
for (var val in _interestsFetched) {
Map _filteredData =
globalInterest.firstWhere((element) => element['categoryId'] == val);
_userInterestData[_filteredData['categoryId']] =
(_filteredData['categoryName']);
}
setState(() {
_userInterestCategory =
_userInterestCategory + _userInterestData.values.toList() + ['+'];
_userInterestCategoryId =
_userInterestCategoryId + _userInterestData.keys.toList() + ['+'];
});
}
//tab bar functions
// runs during the switching tabs animation
_handleTabAnimation() {
// gets the value of the animation. For example, if one is between the 1st and the 2nd tab, this value will be 0.5
_aniValue = _controller.animation.value;
// if the button wasn't pressed, which means the user is swiping, and the amount swipped is less than 1 (this means that we're swiping through neighbor Tab Views)
if (!_buttonTap && ((_aniValue - _prevAniValue).abs() < 1)) {
// set the current tab index
_setCurrentIndex(_aniValue.round());
}
// save the previous Animation Value
_prevAniValue = _aniValue;
}
// runs when the displayed tab changes
_handleTabChange() {
// if a button was tapped, change the current index
if (_buttonTap) _setCurrentIndex(_controller.index);
// this resets the button tap
if ((_controller.index == _prevControllerIndex) ||
(_controller.index == _aniValue.round())) _buttonTap = false;
// save the previous controller index
_prevControllerIndex = _controller.index;
}
_setCurrentIndex(int index) {
// if we're actually changing the index
if (index != _currentIndex) {
setState(() {
// change the index
_currentIndex = index;
});
// trigger the button animation
_triggerAnimation();
// scroll the TabBar to the correct position (if we have a scrollable bar)
_scrollTo(index);
}
}
_triggerAnimation() {
// reset the animations so they're ready to go
_animationControllerOn.reset();
_animationControllerOff.reset();
// run the animations!
_animationControllerOn.forward();
_animationControllerOff.forward();
}
_scrollTo(int index) {
// get the screen width. This is used to check if we have an element off screen
double screenWidth = MediaQuery.of(context).size.width;
// get the button we want to scroll to
RenderBox renderBox = _keys[index].currentContext.findRenderObject();
// get its size
double size = renderBox.size.width;
// and position
double position = renderBox.localToGlobal(Offset.zero).dx;
// this is how much the button is away from the center of the screen and how much we must scroll to get it into place
double offset = (position + size / 2) - screenWidth / 2;
// if the button is to the left of the middle
if (offset < 0) {
// get the first button
renderBox = index-1 < 0 ? _keys[0].currentContext.findRenderObject() : _keys[index - 1].currentContext.findRenderObject();
// get the position of the first button of the TabBar
position = renderBox.localToGlobal(Offset.zero).dx;
// if the offset pulls the first button away from the left side, we limit that movement so the first button is stuck to the left side
if (position > offset) offset = position;
} else {
// if the button is to the right of the middle
// get the last button
renderBox = index+1 == _userInterestCategory.length ? _keys[_userInterestCategory.length-1]
.currentContext
.findRenderObject(): _keys[index + 1]
.currentContext
.findRenderObject();
// get its position
position = renderBox.localToGlobal(Offset.zero).dx;
// and size
size = renderBox.size.width;
// if the last button doesn't reach the right side, use it's right side as the limit of the screen for the TabBar
if (position + size < screenWidth) screenWidth = position + size;
// if the offset pulls the last button away from the right side limit, we reduce that movement so the last button is stuck to the right side limit
if (position + size - offset < screenWidth) {
offset = position + size - screenWidth;
}
}
// scroll the calculated amount
_tabScrollController.animateTo(offset + _tabScrollController.offset,
duration: new Duration(milliseconds: 150), curve: Curves.easeInOut);
}
_getBackgroundColor(int index) {
if (index == _currentIndex) {
// if it's active button
return _colorTweenBackgroundOn.value;
} else if (index == _prevControllerIndex) {
// if it's the previous active button
return _colorTweenBackgroundOff.value;
} else {
// if the button is inactive
return _backgroundOff;
}
}
_getForegroundColor(int index) {
// the same as the above
if (index == _currentIndex) {
return _colorTweenForegroundOn.value;
} else if (index == _prevControllerIndex) {
return _colorTweenForegroundOff.value;
} else {
return _foregroundOff;
}
}
callback(Map data) {
if (data['method'] == 'delete') {
setState(() {
_interestDataId.removeWhere((element) => element == data['id']);
_interestDataName.removeWhere((element) => element == data['name']);
});
} else {
setState(() {
_interestDataId.add(data['id']);
_interestDataName.add(data['name']);
});
}
}
addInterest(context) async {
if(_interestDataId.length > 0 && _interestDataName.length > 0) {
await userInterestsRef
.document(currentUser.id)
.updateData({'categories': FieldValue.arrayUnion(_interestDataId)});
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) {
return Home();
},
),
);
}
else{
SnackBar snackbar = SnackBar(
content: Text("Please select atleast 1 interest before submitting"),
);
_scaffoldKey.currentState.showSnackBar(snackbar);
}
}
buildButton() {
List<Widget> _buttons = [];
globalInterest.forEach((element) {
if (_userInterestCategoryId.contains(element['categoryId'])) {
} else {
_buttons.add(PillButton(
interestId: element['categoryId'],
interestTitle: element['categoryName'],
callback: callback,
));
}
});
setState(() {
buttons = _buttons;
});
}
Widget _buildAddInterest() {
return Scaffold(
bottomNavigationBar: buttons.length != 0 ?
Container(
margin: EdgeInsets.only(bottom: 16.0),
padding: EdgeInsets.symmetric(horizontal: 16.0),
height: 40,
child: FlatButton(
onPressed: () => addInterest(context),
color: Colors.black,
child: Text(
'Add category',
style: Theme.of(context)
.textTheme
.subtitle2
.copyWith(color: Theme.of(context).primaryColor),
),
),
) : Container(),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: buttons.length != 0
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Wrap(
spacing: 12.0,
children: buttons,
),
],
)
: Center(
child: Text(
'Voila! You\'ve selected all the available interests.',
style: Theme.of(context).textTheme.subhead.copyWith(
color:
Theme.of(context).primaryColorDark.withOpacity(0.40),),
// Colors.black,)
),
),
),
);
}
#override
Widget build(BuildContext context) {
return isLoading
? circularProgress(context)
: AppBuilder(
builder: (context){
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Theme.of(context).primaryColor,
elevation: 0,
title: IconButton(
iconSize: SizeConfig.safeBlockHorizontal * 21,
icon: Image.asset(
Theme.of(context).brightness == Brightness.light
? 'assets/images/logo.png'
: 'assets/images/journey_exploreFeed_dark.png',
),
),
titleSpacing: 8.0,
actions: <Widget>[
IconButton(
padding: EdgeInsets.only(right: 24),
icon: new Image.asset(
Theme.of(context).brightness == Brightness.light
? 'assets/icons/create_journey_black.png'
: 'assets/icons/create_journey_white.png'),
onPressed: () {
_analyticsService.logCreateJourneyExploreFeed();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreateJourney()),
);
},
),
IconButton(
padding: EdgeInsets.only(right: 16),
icon:
new Image.asset('assets/icons/invite_friends_icon.png'),
onPressed: () {
_analyticsService.logShareApp();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => InviteFriends()),
);
},
),
]),
backgroundColor: Colors.transparent,
body: Column(children: <Widget>[
// this is the TabBar
Container(
height: 52.0,
color: Theme.of(context).primaryColor,
// this generates our tabs buttons
child: ListView.builder(
// this gives the TabBar a bounce effect when scrolling farther than it's size
physics: BouncingScrollPhysics(),
controller: _tabScrollController,
// make the list horizontal
scrollDirection: Axis.horizontal,
// number of tabs
itemCount: _userInterestCategory.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
// each button's key
key: _keys[index],
// padding for the buttons
padding: EdgeInsets.fromLTRB(8.0, 4.0, 0.0, 8.0),
child: ButtonTheme(
child: AnimatedBuilder(
animation: _colorTweenBackgroundOn,
builder: (context, child) => FlatButton(
// get the color of the button's background (dependent of its state)
color: _getBackgroundColor(index),
padding:
EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0),
// make the button a rectangle with round corners
shape: RoundedRectangleBorder(
borderRadius:
new BorderRadius.circular(24.0)),
onPressed: () {
setState(() {
_buttonTap = true;
// trigger the controller to change between Tab Views
_controller.animateTo(index);
// set the current index
_setCurrentIndex(index);
// scroll to the tapped button (needed if we tap the active button and it's not on its position)
_scrollTo(index);
});
},
child: index !=
_userInterestCategory.length - 1
? Text(
// get the icon
_userInterestCategory[index],
// get the color of the icon (dependent of its state)
style: (TextStyle(
color: _getForegroundColor(index),
)),
)
: Icon(
Icons.add,
color: _getForegroundColor(index),
)),
)));
})),
Flexible(
// this will host our Tab Views
child: TabBarView(
// and it is controlled by the controller
controller: _controller,
children: _interestFeedBuild,
)),
]));
},
);
}
}
class BuildInterestFeed extends StatefulWidget {
final String categoryId;
BuildInterestFeed({this.categoryId});
#override
_BuildInterestFeedState createState() => _BuildInterestFeedState();
}
class _BuildInterestFeedState extends State<BuildInterestFeed>
with AutomaticKeepAliveClientMixin {
bool isLoading = false;
List<Journey> journeys = [];
final AnalyticsService _analyticsService = locator<AnalyticsService>();
String docId = Uuid().v4();
List<DocumentSnapshot> journeysFetched = []; // stores fetched products
bool hasMore = true; // flag for more products available or not
int documentLimit = 10; // documents to be fetched per request
DocumentSnapshot
lastDocument; // flag for last document from where next 10 records to be fetched
ScrollController _verticalScrollController = ScrollController();
#override
void initState() {
super.initState();
if (widget.categoryId != '+') {
_getExploreFeedData();
_verticalScrollController.addListener(() {
double maxScroll = _verticalScrollController.position.maxScrollExtent;
double currentScroll = _verticalScrollController.position.pixels;
double delta = MediaQuery.of(context).size.height * 0.10;
if (maxScroll - currentScroll <= delta) {
_getExploreFeedData();
}
});
}
}
_getExploreFeedData() async {
if (!hasMore) {
print('No More Journeys');
return;
}
if (isLoading) {
return;
}
setState(() {
isLoading = true;
});
if (widget.categoryId == 'All') {
await buildAllDataFeed();
} else {
QuerySnapshot querySnapshot;
if (lastDocument == null) {
querySnapshot = await journeyRef
.orderBy('createdAt', descending: true)
.where('category', arrayContains: widget.categoryId)
.limit(documentLimit)
.getDocuments();
} else {
querySnapshot = await journeyRef
.orderBy('createdAt', descending: true)
.where('category', arrayContains: widget.categoryId)
.startAfterDocument(lastDocument)
.limit(documentLimit)
.getDocuments();
}
if (querySnapshot.documents.length != 0) {
if (querySnapshot.documents.length < documentLimit) {
hasMore = false;
}
if (querySnapshot.documents.length != 0) {
lastDocument =
querySnapshot.documents[querySnapshot.documents.length - 1];
} else {
lastDocument = null;
}
journeysFetched.addAll(querySnapshot.documents);
}
}
setState(() {
isLoading = false;
});
}
buildAllDataFeed() async {
QuerySnapshot querySnapshot;
if (lastDocument == null) {
querySnapshot = await journeyRef
.orderBy('createdAt', descending: true)
.limit(documentLimit)
.getDocuments();
} else {
querySnapshot = await journeyRef
.orderBy('createdAt', descending: true)
.startAfterDocument(lastDocument)
.limit(documentLimit)
.getDocuments();
}
if (querySnapshot.documents.length < documentLimit) {
hasMore = false;
}
if (querySnapshot.documents.length != 0) {
lastDocument =
querySnapshot.documents[querySnapshot.documents.length - 1];
} else {
lastDocument = null;
}
// lastDocument = querySnapshot.documents[querySnapshot.documents.length - 1];
journeysFetched.addAll(querySnapshot.documents);
}
addInterestData({tags}) async{
userInterestsRef.document(currentUser.id).updateData({
'tags': FieldValue.arrayUnion(tags),
});
}
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return
RefreshIndicator(
onRefresh: () => _getExploreFeedData(),
child:
Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(children: [
Expanded(
child: journeysFetched.length == 0
? Center(
child: Text(
'There\'s nothing new here right now',
style: Theme.of(context).textTheme.subhead.copyWith(
color: Theme.of(context)
.primaryColorDark
.withOpacity(0.40)),
),
)
: StaggeredGridView.countBuilder(
controller: _verticalScrollController,
crossAxisCount: 2,
itemCount: journeysFetched.length,
itemBuilder: (BuildContext context, int index) {
Map journeyData = journeysFetched.elementAt(index).data;
String journeyId =
journeysFetched.elementAt(index).documentID;
return GestureDetector(
child: Container(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TransitionToImage(
transitionType: TransitionType.fade,
borderRadius: BorderRadius.circular(8.0),
loadingWidget: Container(
height: 242,
decoration: (BoxDecoration(
borderRadius:
BorderRadius.circular(8.0),
color: Theme.of(context)
.primaryColorLight
.withOpacity(0.05),
))),
image: AdvancedNetworkImage(
journeyData['coverImage'],
useDiskCache: true,
cacheRule: CacheRule(
maxAge: Duration(hours: 9999))),
),
Padding(
padding: EdgeInsets.only(
top: SizeConfig.safeBlockVertical * 0.5,
left:
SizeConfig.safeBlockVertical * 0.5),
child: Text(
journeyData['title'],
style: Theme.of(context)
.textTheme
.bodyText2
.copyWith(
fontWeight: FontWeight.w500),
))
],
),
),
onTap: () {
addInterestData(
tags: journeyData['community']['hashtags']);
Navigator.push(
context,
MaterialPageRoute(
settings:
RouteSettings(name: 'journey_home'),
builder: (context) =>
JourneyHome(journeyId: journeyId)));
});
},
staggeredTileBuilder: (int index) => StaggeredTile.fit(1),
mainAxisSpacing: SizeConfig.safeBlockVertical * 2,
crossAxisSpacing: 8.0,
),
),
isLoading ? circularProgress(context) : Container()
]),
),
),
);
}
}
There is also a pub package named states_rebuilder
https://pub.dev/packages/states_rebuilder
or
our Widget should have a setState() method, this method is called, the widget is redrawn

Question about Flutter State and retrieving variables from State vs StatefulWidget

Here's the context:
In my app, users can create a question, and all questions will be displayed on a certain page. This is done with a ListView.builder whose itemBuilder property returns a QuestionTile.
The problem:
If I create a new question, the text of the new question is (usually) displayed as the text of the previous question.
Here's a picture of me adding three questions in order, "testqn123", "testqn456", "testqn789", but all are displayed as "testqn123".
Hot restarting the app will display the correct texts for each question, but hot reloading wont work.
In my _QuestionTileState class, if I change the line responsible for displaying the text of the question on the page, from
child: Text(text)
to
child: Text(widget.text)
the issue will be resolved for good. I'm not super familiar with how hot restart/reload and state works in flutter, but can someone explain all of this?
Here is the code for QuestionTile and its corresponding State class, and the line changed is the very last line with words in it:
class QuestionTile extends StatefulWidget {
final String text;
final String roomName;
final String roomID;
final String questionID; //
QuestionTile({this.questionID, this.text, this.roomName, this.roomID});
#override
_QuestionTileState createState() => _QuestionTileState(text);
}
class _QuestionTileState extends State<QuestionTile> {
final String text;
int netVotes = 0;
bool expand = false;
bool alreadyUpvoted = false;
bool alreadyDownvoted = false;
_QuestionTileState(this.text);
void toggleExpansion() {
setState(() => expand = !expand);
}
#override
Widget build(BuildContext context) {
RoomDbService dbService = RoomDbService(widget.roomName, widget.roomID);
final user = Provider.of<User>(context);
print(widget.text + " with questionID of " + widget.questionID);
return expand
? ExpandedQuestionTile(text, netVotes, toggleExpansion)
: Card(
elevation: 10,
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 7, 15, 7),
child: GestureDetector(
onTap: () => {
Navigator.pushNamed(context, "/ChatRoomPage", arguments: {
"question": widget.text,
"questionID": widget.questionID,
"roomName": widget.roomName,
"roomID": widget.roomID,
})
},
child: new Row(
// crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Column(
// the stack overflow functionality
children: <Widget>[
InkWell(
child: alreadyUpvoted
? Icon(Icons.arrow_drop_up,
color: Colors.blue[500])
: Icon(Icons.arrow_drop_up),
onTap: () {
dynamic result = dbService.upvoteQuestion(
user.uid, widget.questionID);
setState(() {
alreadyUpvoted = !alreadyUpvoted;
if (alreadyDownvoted) {
alreadyDownvoted = false;
}
});
},
),
StreamBuilder<DocumentSnapshot>(
stream: dbService.getQuestionVotes(widget.questionID),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
// print("Current Votes: " + "${snapshot.data.data["votes"]}");
// print("questionID: " + widget.questionID);
return Text("${snapshot.data.data["votes"]}");
}
},
),
InkWell(
child: alreadyDownvoted
? Icon(Icons.arrow_drop_down,
color: Colors.red[500])
: Icon(Icons.arrow_drop_down),
onTap: () {
dbService.downvoteQuestion(
user.uid, widget.questionID);
setState(() {
alreadyDownvoted = !alreadyDownvoted;
if (alreadyUpvoted) {
alreadyUpvoted = false;
}
});
},
),
],
),
Container(
//color: Colors.red[100],
width: 290,
child: Align(
alignment: Alignment.centerLeft,
child: Text(text)), // problem solved if changed to Text(widget.text)
),
}
}
You can wrap your UI with a Stream Builder, this will allow the UI to update every time any value changes from Firestore.
Since you are using an item builder you can wrap the widget that is placed with the item builder.
That Should update the UI