Slider not updating, and will not move when touched - flutter

I am having an issue with a slider for an audioplayer in flutter. It is not updating while audio is playing, and cannot be moved at all. The debugprint for position is always showing 0:00.
Here is where the slider is built:
Future<Widget> slider() async {
var player = AudioPlayer();
AudioPlayerController apc = AudioPlayerController(selectedPath);
await apc.setSource(selectedPath);
var _position = apc.position;
var _duration = apc.duration;
// var _position =
debugPrint('$_position');
debugPrint('$_duration');
return Slider(
activeColor: AppColor.AppDarkBlueColor,
divisions: _duration?.inSeconds.toInt() ?? 1,
value: _position.inMilliseconds.toDouble() * .001,
min: 0.0,
max: _duration?.inSeconds.toDouble() ?? 0.0,
onChanged: (double value1) {
Duration _newPosition = Duration(seconds: value1.toInt());
setState(() {
player.seek(_newPosition);
_position = _newPosition;
debugPrint('$_position');
});
});
}
And Here is where the slider is implemented into the page:
bottomNavigationBar: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: AppColor.AppLightBlueColor),
height: (MediaQuery.of(context).size.height / 6),
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
child: FutureBuilder(
future: slider(),
builder: (context, data) {
if (data.hasData) {
dynamic slide = data.data ?? [];
return (Container(
child: slide,
));
} else {
return Text("");
}
},
)),
I can see the position is updating elsewhere in the code, but I am not sure why it isn't updating with the slider. The position is set using audioplayer's .position argument.

I don't think it can work this way: A Future only returns a single event, so there will never be any updates.
I think what you need here is a StateFulWidget to hold the player, and a StreamBuilder or ValueListanableBuilder to update your slider.

Related

Flutter Firebase Pagination Problem to Scrolling Top

I made a social media application using Flutter Firebase, and like every social media application, I have a stream of posts shared by users on the home screen. At first, I didn't have any problems, but as the number of data increased, I started to have problems especially getting photos. Later I found out that this was because I was getting all the data at once and decided to use Pagination. I have successfully used Pagination and I also started using Cached Network Image to load my photos faster. But I still have such a problem in the flow. When I scroll the screen to the bottom, the data is loaded at the limit I set, in the example my limit is 12, so I have no problem when scrolling down the screen, but when I want to quickly scroll the screen up, it tries to load all the data again, the system is having too much difficulty, I can't load it at the end and the application gives a lost connection error and closes itself.
In my opinion, the same thing should happen when we swipe the screen up, just as the data is loaded piece by piece as much as the limit number we set when swiping down the screen.
Otherwise, this problem that I am experiencing occurs.
Do you know any solution for this?
This is my code for Pagination;
getData() async {
var Ref1 = (widget.post != null)
? _firestore
.collection("users")
.doc(widget.post["profileID"])
.collection("Datas")
.orderBy("uploadTime", descending: true)
.limit(perpage)
: null;
setState(() {
loadingProducts = true;
});
var reponse = await Ref1.get();
listt = reponse.docs;
lastDocument = reponse.docs[reponse.docs.length - 1];
setState(() {
loadingProducts = false;
});
}
getmoreData() async {
if (moreDataAvailable == false) {
return;
}
if (gettingmoreData == true) {
return;
}
setState(() {
gettingmoreData = true;
});
var Ref1 = (widget.post != null)
? _firestore
.collection("users")
.doc(widget.post["profileID"])
.collection("Datas")
.orderBy("uploadTime", descending: true)
.startAfterDocument(lastDocument)
.limit(perpage)
: null;
var reponse = await Ref1.get();
if (reponse.docs.length < perpage) {
moreDataAvailable = false;
}
lastDocument = reponse.docs[reponse.docs.length - 1];
listt.addAll(reponse.docs);
setState(() {});
setState(() {
gettingmoreData = false;
});
}
And this is my Builder;
GridView.builder(
controller: scrollController,
physics: ScrollPhysics(),
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemCount: listt.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () =>
navigateToDetail(listt[index]),
child: Hero(
tag: (listt[index]["foto"] != null)
? NetworkImage(
listt[index]["foto"])
: AssetImage(
"assets/images/n_image.jpg"),
child: Container(
child: Column(
mainAxisAlignment:
MainAxisAlignment.end,
children: [
Container(
height: size.height * 0.078,
width: double.infinity,
decoration: BoxDecoration(
borderRadius:
BorderRadius.only(
bottomRight:
Radius.circular(
10.0),
bottomLeft:
Radius.circular(
10.0),
),
color: Colors.grey[600]
.withOpacity(0.5)),
child: Center(
child: AutoSizeText(
"${listt[index]["name"]}",
textAlign:
TextAlign.center,
style: GoogleFonts.lora(
textStyle: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
maxLines: 2,
),
),
),
],
),
margin: EdgeInsets.all(5.0),
decoration: BoxDecoration(
image: DecorationImage(
image: (listt[index]
["foto"] !=
null)
? OptimizedCacheImageProvider(
listt[index]["foto"])
: AssetImage(
"assets/images/n_image.jpg"),
fit: BoxFit.cover,
),
color: Colors.white,
borderRadius:
BorderRadius.circular(10.0),
),
),
),
);
},
),
And im listening controller in initstate with this;
scrollController.addListener(() {
double maxScroll = scrollController.position.maxScrollExtent;
double currentScroll = scrollController.position.pixels;
double delta = MediaQuery.of(context).size.height * 0.25;
if (maxScroll - currentScroll <= delta) {
getmoreTarif();
}
});
Your current code tracks the last document of the current results and then calls startAfterDocument with that document to get the next set of results. This works for scrolling forward, but not when scrolling backward. To paginate backwards, you'll also need to track the first document of the current results and then call endBeforeDocument with that document.

TweenAnimationBuilder reset animation to the starting point onEnd and start again when an event is triggered

Trying to build an Exam app where I'm getting only one question at a time on each hit from API. What I'm trying to do is to build question and start the timer (90-0) when question is displayed. After timer is 0 or submit button is pressed, the new question is build (after getting data from API) and timer starts again from 90 seconds. At the moment, timer goes from 90 to 0 and stops and doesn't start after setState
I have seen and tried other solutions like creating a startTimer() function but TweenAnimationBuilder seems fit for the task. However, couldn't figure out to start it again using onEnd.
Here is my TweenAnimationBuilder:
TweenAnimationBuilder(
tween: Tween(begin: 90.0, end: 0.0),
duration: Duration(seconds: 90),
builder: (context, value, child) {
num val = value;
num time = val.toInt();
return Stack(
alignment: Alignment.center,
children: [
normalText(
text: "$time",
size: 25,
color: color__orange),
SizedBox(
width: 60,
height: 60,
child: CircularProgressIndicator(
value: time /
90,
valueColor: const AlwaysStoppedAnimation(
color__orange),
),
),
]
);
},
onEnd: (){
var questionID = snapshot.data!.quizzesData!.first.questionID;
QuizQuestion(questionID);
setState(() {
});
},
),
And Here is the Submit Button:
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: color__orange ),
onPressed: (){
if (selectedOptionStatus == 'True') {
var questionID = snapshot.data!.quizzesData!.first.questionID;
var score = snapshot.data!.quizzesData!.first.question!.first.marks;
var answerID = snapshot.data!.quizzesData!.first.question!.first.answer![index].answerID;
QuizQuestion(questionID);
StudentQuestionAnswer(questionID, score, answerID);
// currentQuestionIndex = int.parse(widget.totalQuestions) - remainingQuestions;
currentQuestionIndex++;
if ( (currentQuestionIndex + 1) == int.parse(widget.totalQuestions)) {
updateStudentQuizLoginHistoryFinish();
}
setState(() {});
}
else {
if (selectedOptionStatus == 'False') {
var questionID = snapshot.data!.quizzesData!.first.questionID;
var score = '0';
var answerID = snapshot.data!.quizzesData!.first.question!.first.answer![index].answerID;
QuizQuestion(questionID);
StudentQuestionAnswer(questionID, score, answerID);
currentQuestionIndex++;
if (currentQuestionIndex + 1 == int.parse(widget.totalQuestions)) {
StudentQuiz();
}
setState(() {});
}
}
},
child: const Text('Save Answer')
)
Provide key to TweenAnimationBuilder
key: ValueKey(questionId)
This will force the previous TweenAnimationBuilder to be disposed and new one will be created and attached.

setState is not updating UI in Flutter

I have a dialog box which is a stateful widget with multiple tabs wrapped inside Animated Switcher.
Inside it I have a button which on clicked calls a function switchPage() which has a switch statement with each case setting the state of Widget? currentTab variable to a different one.
The problem here arrives when I use this switchPage() function to change the value of currentTab to a different widget also wrapped in a different method getWidget2()
The code for the
Example on DartPad
Try clicking as I suggest...
Click Floating Button.
Click on the first checkbox.
Now click on PAGE 2 button.
Click the second checkbox only once. Now notice the checkbox doesn't work when clicked.
Click PAGE 1 again to go the working checkbox.
Now click on PAGE 2 button again. The Checkbox value and state did change but did not update the time it was clicked, but it has updated when we forcefully re visited the checkbox.
I cannot find solution to this anywhere and I really need the code structure to be as optimized as possible.
Please, if anyone has any explanation or any suggestions, it would be greatly appreciated..
Thanks, in advance.
This is a very rough working example, but you can build upon it.
In the code below, you can see that I have created two methods that handle setting the state of the checkbox for each widget. I have also reset the page once this method is triggered. What this does, is trigger the redrawing of the inner widgets (which is what I explained in my comment).
class _DemoDialogState extends State<DemoDialog> {
Widget? currentTab;
bool valueOfCheckbox1 = false;
bool valueOfCheckbox2 = false;
void switchPage(name) {
switch (name) {
case 1:
setState(() {
currentTab = getWidget1(setCheckbox1State); // <---- Notice the change here
});
break;
case 2:
setState(() {
currentTab = getWidget2(setCheckbox2State); // <---- Notice the change here
});
break;
}
}
void setCheckbox1State(bool? newState) {
if (newState != null) {
setState(() { // <---- Notice the change here
valueOfCheckbox1 = newState;
currentTab = getWidget1(setCheckbox1State);
});
}
}
void setCheckbox2State(bool? newState) {
if (newState != null) {
setState(() { // <---- Notice the change here
valueOfCheckbox2 = newState;
currentTab = getWidget2(setCheckbox2State);
});
}
}
Widget getWidget1(Function(bool?) checkboxFunction) {
return Container(
child: Row(
children: [
Text('Hello from widget 1'),
Checkbox(
value: valueOfCheckbox1,
onChanged: (value) { // <---- Notice the change here
checkboxFunction(value);
})
],
));
}
Widget getWidget2(Function(bool?) checkboxFunction) {
return Container(
child: Row(
children: [
Text('Hello from widget 2'),
Checkbox(
value: valueOfCheckbox2,
onChanged: (value) { // <---- Notice the change here
checkboxFunction(value);
})
],
));
}
#override
Widget build(BuildContext context) {
return Dialog(
child: Container(
width: 280,
height: 600,
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
AnimatedSwitcher(
duration: Duration(milliseconds: 250),
reverseDuration: Duration(milliseconds: 250),
transitionBuilder: (child, animation) {
var begin = Offset(0.5, 0);
var end = Offset.zero;
var curve = Curves.easeIn;
var tween = Tween(begin: begin, end: end)
.chain(CurveTween(curve: curve));
var begin2 = 0.0;
var end2 = 1.0;
var curve2 = Curves.easeIn;
var tween2 = Tween(begin: begin2, end: end2)
.chain(CurveTween(curve: curve2));
return SlideTransition(
position: animation.drive(tween),
child: FadeTransition(
opacity: animation.drive(tween2), child: child),
);
},
layoutBuilder: (widget, list) {
return Align(
alignment: Alignment.topCenter,
child: widget,
);
}, // <---- Notice the change here
child: currentTab == null ? getWidget1(setCheckbox1State) : currentTab,
),
TextButton(
onPressed: () {
switchPage(1);
},
child: Text('PAGE 1')),
TextButton(
onPressed: () {
switchPage(2);
},
child: Text('PAGE 2'))
],
),
),
);
}
}
This is just an example that makes things work, but it in no way represents how you should build things appropriately. I would look into separating the code into stateless and stateful widgets.

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

Flutter TextField covered by keyboard, I can't use resizeToAvoidBottomInset = true

I'm trying to make a an autocomplete Textfield that decide whether to show the list of suggestions up or down respect to the textfield depending on where there is more space in the screen. Like this:
AutocompleteTextFieldExample
To decide in which direction visualize it I need to know how much space is covered by the keyboard, so I use: "MediaQuery.of(context).viewPadding.bottom"
However "MediaQuery.of(context).viewPadding.bottom" returns always 0.0 if I set resizeToAvoidBottomInset: true.
So I need to set it false. But doing like that the textfields are covered by the keyboard when on focus, as you can see in this two images:
Screen
Screen with focus on the bottom textfield
This is my textFieldCode so far:
class _AutoCompleteTextFieldState extends State<AutoCompleteTextField> {
final double _maxSuggestionBoxDimension = 300;
OverlayEntry _overlayEntry;
final LayerLink _layerLink = LayerLink();
final FocusNode _focusNode = FocusNode();
final TextEditingController _textEditingController =
new TextEditingController();
// it contains the listTiles utilized by the overlay
// the items are changed inside the
List<ListTile> listTiles = [];
#override
void initState() {
super.initState();
// it inserts or remove the suggestions container depending on the focus of the textfield
_focusNode.addListener(() {
if (_focusNode.hasFocus) {
this._overlayEntry = this._createOverlayEntry();
Overlay.of(context).insert(this._overlayEntry);
} else {
this._overlayEntry.remove();
}
});
// if text is not empty it updates the suggestion in the suggestions box
_textEditingController.addListener(() {
// move this logic inside updateSuggestinoContainer
if (_textEditingController.text.isNotEmpty) {
updateSuggestionContainer(_textEditingController.text);
} else {
listTiles = [];
_overlayEntry.markNeedsBuild();
}
});
}
// it modify the suggestion container depending on the input text (in the textfield),
// it works in 3 part
// - defining the List of string which rappresent the suggestions that will be in the suggestions container
// - create and assign the new List of ListTile that will be used to build the suggestions container
// - mark _overlayEntry as dirty to rebuild and
void updateSuggestionContainer(String text) {
List<String> stringSuggestions = getStringSuggestions(text);
listTiles = getListTileSuggestion(stringSuggestions);
_overlayEntry.markNeedsBuild();
}
// it defines what suggestion to visualize
List<String> getStringSuggestions(String text) {
return widget.suggestions
.where((possibleSuggestion) =>
possibleSuggestion.substring(0, text.length) == text)
.toList();
}
// it define how to visualize each single suggestion
List<ListTile> getListTileSuggestion(List<String> suggestions) {
List<ListTile> listTiles = [];
for (String suggestion in suggestions) {
listTiles.add(ListTile(title: Text(suggestion)));
}
return listTiles;
}
OverlayEntry _createOverlayEntry() {
RenderBox renderBox = context.findRenderObject();
var size = renderBox.size;
Offset position = renderBox.localToGlobal(Offset.zero);
Future.delayed(Duration(seconds: 3)).then((_) {
print("${_showBottom(position.dy)}");
});
return OverlayEntry(
builder: (context) => Positioned(
width: size.width,
child: CompositedTransformFollower(
link: this._layerLink,
showWhenUnlinked: false,
offset: Offset(
0.0,
_showBottom(position.dy)
? min(size.height + 5.0, _maxSuggestionBoxDimension)
: max(-listTiles.length * 56.0 - size.height + 40.0,
-_maxSuggestionBoxDimension)),
child: Material(
child: Container(
height: min(
listTiles.length * 56.0, _maxSuggestionBoxDimension),
//duration: Duration(milliseconds: 150),
color: Colors.white,
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: ListView(
reverse: _showBottom(position.dy) ? false : true,
children: listTiles.isNotEmpty
? ListTile.divideTiles(
context: context, tiles: listTiles)
.toList()
: List()),
),
),
),
),
));
}
// controll whetever the suggestion should be shown above or bottom the textField
bool _showBottom(double textFieldCordinateY) {
print("textFieldCordinateY = $textFieldCordinateY");
print(
"MediaQuery.of(context).size.height = ${MediaQuery.of(context).size.height}");
print(
"MediaQuery.of(context).viewInsets.bottom = ${MediaQuery.of(context).viewInsets.bottom}");
print(
"MediaQuery.of(context).viewPadding.bottom = ${MediaQuery.of(context).viewPadding.bottom}");
// TODO
// consider also the top viewInsets
if ((MediaQuery.of(context).size.height -
MediaQuery.of(context).viewInsets.bottom) /
2 >
textFieldCordinateY)
return true;
else
return false;
}
#override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: this._layerLink,
child: TextFormField(
//scrollPadding: EdgeInsets.all(500),
controller: _textEditingController,
focusNode: this._focusNode,
decoration: InputDecoration(suffixIcon: Icon(Icons.arrow_drop_down)),
),
);
}
}
This is the build in my screen code:
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(title: Text("Titolo")),
body: ListView(children: [
SizedBox(height: 15),
Container(
padding: EdgeInsets.all(20),
child: AutoCompleteTextField(["Ape", "Areoplano", "Austronauta"])),
SizedBox(height: 200),
Container(
padding: EdgeInsets.all(20),
child: AutoCompleteTextField([
"Biliardo",
"Bufu",
"Bamba",
"Bici",
"Bambolina",
"Busta",
"Bella",
"Basta",
"Balza"
])),
SizedBox(height: 200),
Container(
padding: EdgeInsets.all(20),
child: AutoCompleteTextField(["Ciru", "Capanna", "Casa"]),
),
]),
);
}
Thank you in advance.