How to show all dates in a horizontal scroll view - flutter

I want to have all the dates of a month in a horizontal scroll view. The current week 7 days should be displayed first and on scrolling right the previous dates should be shown. later on scrolling left the later weeks dates should be displayed an don tap of a date i should get the date in return. How to do this? I have tried using the below. It displays dates and scrolls horizontally as well but it displays only multiple of 7 and all the exact dates of a month. Also on tapping the date it does not return the position form listview builder as 0 and i returns the index.
Widget displaydates(int week) {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int position) {
return Row(
children: <Widget>[
for (int i = 1; i < 8; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
print(position);
},
child: Text(
((week * 7) + i).toString() + " ",
style: TextStyle(),
),
),
),
],
);
});
}
I am calling this like:
displaydates(0),
displaydates(1),
displaydates(2),
displaydates(3),
displaydates(4),

UPDATE:
You can get last day of month using below code :
DateTime(year,month + 1).subtract(Duration(days: 1)).day;
You should use ModalBottomSheet for the same and then pop that on selection with the Navigator.of(context).pop(result);
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int position) {
return Row(
children: <Widget>[
for (int i = 1; i < 8; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(position);
},
child: Text(
((week * 7) + i).toString() + " ",
style: TextStyle(),
),
),
),
],
);
}
);
}
);

I have created a widget that let me select date from a list and it is scrollable(along time ago). There lot of code but you can use the selected date under any widget which parent wrapped from InheritedWidget.
Here is the code(Note that I also created a package for this, if you dont like to write this much code for this):
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class MyInheritedWidget extends InheritedWidget {
final DateTime date;
final int selectedDay;
final int monthDateCount;
final bool isDateHolderActive;
final Map<int, bool> dayAvailabilityMap;
final ValueChanged<bool> toggleDateHolderActive;
final ValueChanged<int> setSelectedDay;
MyInheritedWidget({
Key key,
this.date,
this.selectedDay,
this.monthDateCount,
this.isDateHolderActive,
this.dayAvailabilityMap,
this.toggleDateHolderActive,
this.setSelectedDay,
Widget child,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return oldWidget.selectedDay != selectedDay ||
oldWidget.toggleDateHolderActive != toggleDateHolderActive;
}
}
class DateIndicatorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
DateIndicator(),
Expanded(
child: Container(),
),
],
),
),
);
}
}
class DateIndicator extends StatefulWidget {
static MyInheritedWidget of(BuildContext context) => context.dependOnInheritedWidgetOfExactType();
#override
_DateIndicatorState createState() => _DateIndicatorState();
}
class _DateIndicatorState extends State<DateIndicator> {
DateTime date = DateTime.now();
int selectedDay = 1;
int monthDateCount = 1;
bool isDateHolderActive = false;
Map<int, bool> dayAvailabilityMap = {};
void toggleDateHolderActive(bool flag) {
setState(() {
isDateHolderActive = flag;
});
}
void setSelectedDay(int index) {
setState(() {
selectedDay = index;
});
}
#override
void initState() {
final DateTime dateForValues = new DateTime(date.year, date.month + 1, 0);
monthDateCount = dateForValues.day;
// Just to show how to activate when something exist for this day(from network response or something)
dayAvailabilityMap[1] = true;
dayAvailabilityMap[2] = true;
dayAvailabilityMap[3] = true;
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: 68.0,
padding:
const EdgeInsets.only(left: 7.0, right: 3.0, top: 2.0, bottom: 2.0),
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor,
boxShadow: [
BoxShadow(
color: Colors.blueAccent.withOpacity(.7),
offset: Offset(0.0, .5),
blurRadius: 3.0,
spreadRadius: 0.3),
],
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: monthDateCount, // to avoid showing zero
itemBuilder: (BuildContext context, int index) {
return MyInheritedWidget(
date: date,
selectedDay: selectedDay,
monthDateCount: monthDateCount,
isDateHolderActive: isDateHolderActive,
dayAvailabilityMap: dayAvailabilityMap,
toggleDateHolderActive: toggleDateHolderActive,
setSelectedDay: setSelectedDay,
child: DateHolder(index));
}),
);
}
}
class DateHolder extends StatelessWidget {
DateHolder(this.index);
final int index;
final Widget activeBubble = Container(
width: 15.0,
height: 15.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.deepOrangeAccent,
),
);
#override
Widget build(BuildContext context) {
final appState = DateIndicator.of(context);
return InkWell(
onTap: () {
appState.toggleDateHolderActive(true);
appState.setSelectedDay(index);
print("Date ${index} selected!");
},
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
Container(
margin: const EdgeInsets.only(right: 5.0),
child: Text(
"${DateFormat('EEEE').format(DateTime(appState.date.year, appState.date.month, index)).substring(0, 1)}",
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12.0),
)),
Container(
width: 45.0,
height: 45.0,
margin: const EdgeInsets.only(right: 5.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
border: (index == (appState.selectedDay) &&
appState.isDateHolderActive == true)
? Border.all(width: 2.0, color: Theme.of(context).primaryColor)
: Border.all(color: Colors.transparent),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
"${index + 1}", // to avoid showing zero
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 16.0),
),
),
),
),
],
),
(appState.dayAvailabilityMap[index] ?? false)
? Positioned(right: 8.0, bottom: 5.0, child: activeBubble)
: Container(),
],
),
);
}
}

Related

How to return the PageView to its initial state in Flutter

I am making a quiz app and at first everything works fine, but when I do a quiz the first time, it does the correct or incorrect answer check perfectly.
But when I go back to quiz without restarting the app just navigating from one page to another the PageView does not reset its state again.
Before taking the quiz
enter image description here
After I do the quiz and I want to do it again without restart the app, I get the checked answers.
enter image description here
How to return the PageView to its initial state without restart the app
Here is my code:
import 'package:flutter/material.dart';
import 'package:quizapp/src/models/quiz_model.dart';
import 'package:quizapp/src/screens/result_screen.dart';
class QuizScreen extends StatefulWidget {
const QuizScreen({Key? key}) : super(key: key);
#override
State<QuizScreen> createState() => _QuizScreenState();
}
class _QuizScreenState extends State<QuizScreen> {
int _questionNumber = 1;
late PageController _controller;
int _score = 0;
#override
void initState() {
_controller = PageController(initialPage: 0);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: PageView.builder(
physics: const NeverScrollableScrollPhysics(),
controller: _controller,
itemCount: questions.length,
itemBuilder: (context, index) {
final _question = questions[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 16,
),
Text(
_question.text,
style: const TextStyle(fontSize: 22),
),
const SizedBox(
height: 16,
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: _question.options
.map((option) => GestureDetector(
onTap: () {
Future.delayed(
const Duration(milliseconds: 250),
() {
if (_questionNumber <
questions.length) {
_controller.nextPage(
duration: const Duration(
milliseconds: 250),
curve: Curves.easeInExpo);
setState(() {
if (option.isCorrect == true) {
_score++;
}
});
setState(() {
_questionNumber++;
// _isLocked = false;
});
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
ResultScreen(
score: _score),
));
}
});
if (_question.isLocked) {
return;
} else {
setState(() {
_question.isLocked = true;
_question.selectedOption = option;
});
}
},
child: Container(
height: 50,
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.symmetric(
vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFF6949FD),
borderRadius:
BorderRadius.circular(16),
border: Border.all(
color: getColorForOption(
option, _question))),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
option.text,
style: const TextStyle(
fontSize: 18,
color: Colors.white),
),
const SizedBox(width: 10),
getIconForOption(option, _question)
],
),
),
))
.toList(),
)))
]);
},
)),
),
],
),
));
}
Color getColorForOption(Option option, Question _question) {
final isSelected = option == _question.selectedOption;
if (_question.isLocked) {
if (isSelected) {
return option.isCorrect ? Colors.green : Colors.red;
} else if (option.isCorrect) {
return Colors.green;
}
}
return const Color(0xFF6949FD);
}
Widget getIconForOption(Option option, Question _question) {
final isSelected = option == _question.selectedOption;
if (_question.isLocked) {
if (isSelected) {
return option.isCorrect
? const Icon(Icons.check_circle, color: Colors.green)
: const Icon(Icons.cancel, color: Colors.red);
} else if (option.isCorrect) {
return const Icon(Icons.check_circle, color: Colors.green);
}
}
return const SizedBox.shrink();
}
}
An easier way is to restart the app when you go back or press a button. You can wrap Scaffold() with WillPopScope() to restart when you back. You can use this package to restart.
If you need to save the score, you can save it in local storage. Another easy package for this is get_storage.
dependencies:
flutter_phoenix: ^1.1.0
runApp(Phoenix(child: const MyApp()));
WillPopScope(
onWillPop: () async {
Phoenix.rebirth(context);
},
child: Scaffold())

flutter hide elements dynamically List view

I am building a chatbot with text and button responses. I want to hide the button container when one of the buttons is pressed.These buttons are dynamic. I am trying to use a boolean array to save the visibility of the list of the elements. Now, the buttons disappear whenever i press them, but they appear again when the next response is loaded.
class Body extends StatefulWidget {
final List<Map<String, dynamic>> messages;
final mycallback? hi;
final callback? showdate;
Body({
Key? key,
this.messages = const [],
this.hi,
this.showdate,
}) : super(key: key);
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
List<bool> demo = List<bool>.filled(50, true);
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (context, i) {
var obj = widget.messages[widget.messages.length - 1 - i];
Message message = obj['message'];
bool isUserMessage = obj['isUserMessage'] ?? false;
List<dynamic> label = obj['label'];
bool show = obj['show'];
//if (show) show1();
return Column(
crossAxisAlignment:
isUserMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: isUserMessage
? MainAxisAlignment.end
: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_MessageContainer(
message: message,
isUserMessage: isUserMessage,
),
//if (label.length > 0) _ButtonContainer(label: label),
if (label.length > 0)
if (label[0] != "calendar")
!demo[i]
? SizedBox(
width: 2,
)
: Visibility(
child: Container(
constraints: BoxConstraints(maxWidth: 250),
padding: EdgeInsets.only(top: 10, left: 35),
child: ListView.builder(
itemCount: label.length,
shrinkWrap: true,
itemBuilder: (context, j) {
return ElevatedButton(
child: Text(label[j]),
onPressed: () => {
print(i),
setState(() {
hide(i + 1);//hiding here
print(i);
widget.hi!(label[j], true);//sending message
})
},
style: ButtonStyle(
shape: MaterialStateProperty.all<
RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
side: BorderSide(
color: Colors.black,
width: 2.0,
),
),
),
),
);
},
),
),
)
],
);
},
separatorBuilder: (_, i) => Container(height: 10),
itemCount: widget.messages.length,
reverse: true,
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 20,
),
);
}
void hide(i) {
print("I am inside hide");
print(demo[i]);
inspect(demo);
setState(() {
demo[i] = false;
if (demo[i]) {
debugPrint("true");
} else {
debugPrint("False");
}
});
inspect(demo);
}
}

Why do two UniqueKeys still trigger a "Multiple widgets use the same GlobalKey" assertion?

I'm trying to make a reorderable list of custom widgets, so I use a UniqueKey() in their constructors (seeing as there's not really anything else to differentiate them by). However when I go to create another element, I get the Multiple widgets use the same GlobalKey assertion. I took a look at the Widget Inspector and both of the widgets have the UniqueKey but somehow also a key of null? I suspect this is the origin of the issue but I can't figure out how to solve it.
I have a few other properties in the constructor but all of them are late and can't be used in the constructor of a ValueKey.
My code:
section.dart:
class Section extends StatefulWidget {
final String title;
late int index;
bool selected = false;
Section ({required this.title, required this.index});
Key key = UniqueKey();
#override
SectionState createState() => SectionState();
void deselect() {
this.selected = false;
}
}
main.dart:
class WritingPageState extends State<WritingPage> {
List<Section> sections = [Section(index: 0, title: "First Section")];
ValueNotifier<int> selectedIndex = ValueNotifier(-1);
int newKeyConcatter = 1;
Widget build(BuildContext context) {
Widget addSectionButton = Padding(
key: Key("__ADD_SECTION_BUTTON_KEY__"),
padding: const EdgeInsets.fromLTRB(0, 20, 0, 0),
child: InkWell(
onTap: () => setState(() {
sections.add(Section(index: sections.length, title: "New Section $newKeyConcatter",));
newKeyConcatter++;
}),
child: Container(
height: 90,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Theme.of(context).primaryColor, width: 2),
//color: Theme.of(context).primaryColorLight
),
child: Center(
child: Icon(Icons.add, color: Theme.of(context).primaryColor, size: 40,),
),
),
),
);
List<Widget> sectionsToBeDisplayed = [];
for (Widget actualSection in sections) {
sectionsToBeDisplayed.add(actualSection);
sectionsToBeDisplayed.add(addSectionButton);
}
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(32.0),
child: Row(
children: [
Flexible(
flex: 1,
child: NotificationListener<SectionSelectedNotification> (
onNotification: (notif) {
setState(() {
for (Section s in sections) {
if (s.index != notif.index) {s.deselect();}
}
});
return true;
},
child: ReorderableListView(
buildDefaultDragHandles: false,
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final Section item = sections.removeAt(oldIndex);
sections.insert(newIndex, item);
for (int i = 0; i < sections.length; i++) {
sections[i].index = i;
}
});
},
children: sectionsToBeDisplayed,
),
),
),
VerticalDivider(
indent: 20,
endIndent: 20,
color: Colors.grey,
width: 20,
),
Flexible(
flex: 2,
child: Card(
color: Theme.of(context).primaryColorLight,
child: Center(
child: ValueListenableBuilder(
valueListenable: selectedIndex,
builder: (BuildContext context, int newSelected, Widget? child) {
if ((newSelected < 0 && newSelected != -1) || newSelected > sections.length) {
return Text("""An internal error has occured. Namely, newSelected is $newSelected,
which is something that normally shouldn't happen.""");
} else if (newSelected == -1) {
return Text("Select a section to begin writing.");
}
return TextField(
decoration: InputDecoration(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
hintText: "Once upon a time...",
),
);
},
)
)
)
)
],
)
),
);
}
}
You have several issues with your approach.
First - as I mentioned in my comment - you are not assigning the key properly. Instead of passing the constructor argument 'key', you actually did override the key property with your own.
Additionally, in this code you are adding your addSectionButton multiple times - and each time it has the same Key - which will cause a problem:
List<Widget> sectionsToBeDisplayed = [];
for (Widget actualSection in sections) {
sectionsToBeDisplayed.add(actualSection);
sectionsToBeDisplayed.add(addSectionButton);
}
But the main problem is - your approach is wrong in several ways.
You are trying to maintain list of Widgets to be reordered. You should be reordering the data. Let the widgets rebuild on their own.
Your StatefullWidget is tracking if it is selected or not, and you try to sync all the widgets once the selection changes. Instead, you should - again - be focusing on your data. Let the widgets rebuild on their own.
Here's the solution that works - you can try it in DartPad.
You will see key changes:
-I introduced a List to track your ListTile titles and the text you edit
-New variable to track the selected list item
-onReorder is almost exactly the same as Flutter doc - I only added few lines to track the selected item
-everything is in a single widget. You could still extract your Section widget (and pass a callback function for it to post changes back) - but your Section widget should be stateless.
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final _items=<String> ["First Section"];
final _itemsText=<String> ["First Section Text"];
int _selectedIndex=-1;
void _setSelectedIndex(int? value) {
setState(() {
_selectedIndex=value ?? -1;
});
}
#override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
Widget addSectionButton = Padding(
key: const Key("__ADD_SECTION_BUTTON_KEY__"),
padding: const EdgeInsets.fromLTRB(0, 20, 0, 0),
child: InkWell(
onTap: () => setState(() {
_items.add("New Section ${_items.length+1}");
_itemsText.add("");
}),
child: Container(
height: 90,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Theme.of(context).primaryColor, width: 2),
),
child: Center(
child: Icon(Icons.add, color: Theme.of(context).primaryColor, size: 40,),
),
),
),
);
Widget editor;
if ((_selectedIndex < 0 && _selectedIndex != -1) || _selectedIndex > _items.length) {
editor=Text("""An internal error has occured. Namely, newSelected is $_selectedIndex,
which is something that normally shouldn't happen.""");
} else if (_selectedIndex == -1) {
editor=const Text("Select a section to begin writing.");
} else {
TextEditingController _controller=TextEditingController(text: _itemsText[_selectedIndex]);
editor=TextField(
decoration: InputDecoration(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
hintText: "Once upon a time...",
),
controller: _controller,
onSubmitted: (String value) {
_itemsText[_selectedIndex]=value;
}
);
}
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(32.0),
child: Row(
children: [
Flexible(
flex: 1,
child:
ReorderableListView(
buildDefaultDragHandles: true,
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final String item = _items.removeAt(oldIndex);
_items.insert(newIndex, item);
final String itemText = _itemsText.removeAt(oldIndex);
_itemsText.insert(newIndex, itemText);
if (_selectedIndex==oldIndex) {
_selectedIndex=newIndex;
}
});
},
children: <Widget>[
for (int index = 0; index < _items.length; index++)
RadioListTile(
key: Key('$index'),
value: index,
groupValue: _selectedIndex,
tileColor: index.isOdd ? oddItemColor : evenItemColor,
title: Text(_items[index]),
onChanged: _setSelectedIndex
),
addSectionButton
],
),
//),
),
const VerticalDivider(
indent: 20,
endIndent: 20,
color: Colors.grey,
width: 20,
),
Flexible(
flex: 2,
child: Card(
color: Theme.of(context).primaryColorLight,
child: Center(
child: editor
)
)
)
],
)
),
);
}
}

Update the state of navigation bar item in flutter

I have Navigation page contain navigation bar, one of these items it's friend request and I want show number of friend request, I do that, but I want update the number of friend request of item when the number increasing or decreasing in navigation page.
I'm passing the values of notifications below, so when I try to add a new friend request the notification numbers not change but when I closed app and reopen again the friend requests updated.
body: Stack(
children: <Widget>[
IndexedStack(
children: <Widget>[
FriendRequestes(),
ChatListScreen(),
HomePage),
Tasks(),
Projects()
],
index: _selectedItem,
)
// Streambuilder to update the number of friend request from firestore
StreamBuilder(
stream: _firestore
.collection(USERS_COLLECTION)
.where(UID_FIELD, isEqualTo: widget.me.uid)
.limit(1)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
const Text('Loading...');
} else {
return ListView.builder(
physics:
NeverScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection:
Axis.vertical,
itemCount: snapshot
.data.documents.length,
itemBuilder:
(context, index) {
DocumentSnapshot userData = snapshot.data.documents[index];
List<String> requested = <String>[];
if(userData.data[REQUESTED_FIELD] != null)
{
List.from(userData.data[REQUESTED_FIELD]).forEach((element){
requested.add(element);
});
}
widget.me = User(
arrayRequested: requested,
);
rebuild(widget.me);
return Container(width: 0.0,height: 0.0,);
});
}
return Container(
height: 0.0,
width: 0.0,
);
},
),
bottomNavigationBar: CustomBottomNavigationBar(
onChange: (val) {
setState(() {
_selectedItem = val;
});
},
defaultSelectedIndex: 2,
width: width,
lang: widget.myAppLang,
iconList: [
"assets/icon/icon_friendReq.png",
"assets/icon/icon_chats.png",
"assets/icon/icon_home.png",
"assets/icon/icon_tasks.png",
"assets/icon/icon_projects.png",
],
textList: [
"Profile",
"Chats",
"Home",
"Tasks",
"Projects",
],
// here I'm passing notification number
notiList: [widget.me.arrayRequested.length, 0, 0, 0, 0],
),
// to update the state of number of friend requests from firestore
rebuild(User user){
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
widget.me = user;
});
});
}
// Navigation Bar
class CustomBottomNavigationBar extends StatefulWidget {
final int defaultSelectedIndex;
final double width;
String lang;
final Function(int) onChange;
final List<String> iconList;
final List<String> textList;
final List<int> notiList;
CustomBottomNavigationBar(
{this.defaultSelectedIndex = 0,
#required this.iconList,
#required this.textList,
#required this.notiList,
#required this.onChange,
#required this.width,
#required this.lang,
});
#override
_CustomBottomNavigationBarState createState() =>
_CustomBottomNavigationBarState();
}
class _CustomBottomNavigationBarState extends State<CustomBottomNavigationBar> {
int _selectedIndex = 0;
double _width;
String _lang;
List<String> _iconList = [];
List<String> _textList = [];
List<int> _notiList = [];
#override
void initState() {
// TODO: implement initState
super.initState();
_selectedIndex = widget.defaultSelectedIndex;
_iconList = widget.iconList;
_textList = widget.textList;
_notiList = widget.notiList;
_width = widget.width;
_lang = widget.lang;
}
#override
Widget build(BuildContext context) {
List<Widget> _navBarItemList = [];
for (var i = 0; i < _iconList.length; i++) {
_navBarItemList
.add(buildNavBarItem(_width, _lang, _iconList[i], _textList[i], _notiList[i], i));
}
return Row(
children: _navBarItemList,
);
}
Widget buildNavBarItem(
double width, String lang, String icon, String text, int n, int index) {
return GestureDetector(
onTap: () {
setState(() {
widget.onChange(index);
_selectedIndex = index;
n = n;
});
},
child: Stack(
children: [
Container(
height: width * 0.18,
width: MediaQuery.of(context).size.width / _iconList.length,
decoration: index == _selectedIndex
? BoxDecoration(
color: GlobalUniversal.blackForIcons.withOpacity(0.08),
border: Border(
bottom: BorderSide(width: 4, color: GlobalUniversal.purple),
),
/*
gradient: LinearGradient(colors: [
Colors.green.withOpacity(0.3),
Colors.green.withOpacity(0.015),
], begin: Alignment.bottomCenter, end: Alignment.topCenter)
*/
// color: index == _selectedItemIndex ? Colors.green : Colors.white,
)
: BoxDecoration(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ImageIcon(
AssetImage(icon),
color: index == _selectedIndex
? GlobalUniversal.blackForIcons
: Colors.grey,
),
SizedBox(
height: 3.0,
),
Text(
text,
textScaleFactor: 1.0,
style: TextStyle(
color: index == _selectedIndex
? GlobalUniversal.blackForIcons
: Colors.grey,
fontFamily: lang != LANG_EN ? FONT_AR : FONT_EN),
),
],
),
),
Visibility(
visible: n != 0 && n != null,
child: Container(
margin: EdgeInsets.all(5.0),
padding: EdgeInsets.all(5.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xffc32c37),
),
child: Text(
n.toString(),
textScaleFactor: 1.0,
style: TextStyle(
color: GlobalUniversal.whiteBG,
fontSize: 10.0),
),
),
),
],
),
);
}
}
The state is not updating because the number of the request value is not in a StreamBuilder which means it is not asynchronous.

Flutter Slide Transition To a Specific Location

I'm making a grammar quiz app using flutter, I have a question and a couple of choices, I want to make the choice slides to the empty space part of the question with a slide animation
For example:
How is _ new School?
(You) (Your) (It)
and when I press on (Your) the choice widget slides to the _ leaving an empty container
How is (Your) new School?
(You) ( ) (It)
I Made it with Draggable and DragTarget and you can see it in these images
image 1
image 2
but I want it to slide when I press on it without dragging and dropping
here is some of the code
class QuestionsScreen extends StatefulWidget {
QuestionsScreen({Key key}) : super(key: key);
#override
_QuestionsScreenState createState() => _QuestionsScreenState();
}
class _QuestionsScreenState extends State<QuestionsScreen> {
String userAnswer = "_";
int indexOfDragPlace = QuestionBrain.getQuesitonText().indexOf("_");
#override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
return Scaffold(
body: SafeArea(
child: Column(
children: [
Container(
padding: EdgeInsets.all(10),
color: Colors.white,
child: Center(
child: Scrollbar(
child: ListView(
children: [
Center(
child: Wrap(
children: [
...QuestionBrain.getQuesitonText()
.substring(0, indexOfDragPlace)
.split(" ")
.map((e) => QuestionHolder(
question: e + " ",
)),
_buildDragTarget(),
...QuestionBrain.getQuesitonText()
.substring(indexOfDragPlace + 1)
.split(" ")
.map((e) => QuestionHolder(
question: e + " ",
)),
],
),
)
],
),
),
),
),
Wrap(
children: [
...QuestionBrain.choices.map((choice) {
if (choice == userAnswer) {
return ChoiceHolder(
choice: "",
backGroundColor: Colors.black12,
);
}
return DraggableChoiceBox(
choice: choice,
userAnswer: userAnswer,
onDragStarted: () {
setState(() {
dragedAnswerResult = "";
});
},
onDragCompleted: () {
setState(() {
userAnswer = choice;
setState(() {
answerColor = Colors.orange;
});
print("Called");
});
},
);
}).toList()
],
),
],
),
),
);
}
Widget _buildDragTarget() {
return DragTarget<String>(
builder: (context, icoming, rejected) {
return Material(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
width: MediaQuery.of(context).size.width * 0.20,
height: MediaQuery.of(context).size.height * 0.05,
color: answerColor,
child: FittedBox(
child: Text(
userAnswer,
style: TextStyle(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.bold),
),
),
),
);
},
onAccept: (data) {
userAnswer = data;
answerColor = Colors.orange;
},
);
}
}
class DraggableChoiceBox extends StatelessWidget {
const DraggableChoiceBox({
Key key,
this.choice,
this.userAnswer,
this.onDragCompleted,
this.onDragStarted,
}) : super(key: key);
final String choice;
final String userAnswer;
final Function onDragCompleted;
final Function onDragStarted;
#override
Widget build(BuildContext context) {
return Draggable(
onDragCompleted: onDragCompleted,
data: choice,
child: ChoiceHolder(choice: choice),
feedback: Material(
elevation: 20,
child: ChoiceHolder(
choice: choice,
margin: 0,
),
),
childWhenDragging: ChoiceHolder(
choice: "",
backGroundColor: Colors.black12,
),
onDragStarted: onDragStarted,
);
}
}
You can use overlays similar to the way Hero widgets work, here is an "unpolished" example:
import 'package:flutter/material.dart';
class SlideToPosition extends StatefulWidget {
#override
_SlideToPositionState createState() => _SlideToPositionState();
}
class _SlideToPositionState extends State<SlideToPosition> {
GlobalKey target = GlobalKey();
GlobalKey toMove = GlobalKey();
double dx = 0.0, dy = 0.0, dxStart = 0.0, dyStart = 0.0;
String choosedAnswer = '', answer = 'answer', finalAnswer = '';
OverlayEntry overlayEntry;
#override
void initState() {
overlayEntry = OverlayEntry(
builder: (context) => TweenAnimationBuilder(
duration: Duration(milliseconds: 500),
tween:
Tween<Offset>(begin: Offset(dxStart, dyStart), end: Offset(dx, dy)),
builder: (context, offset, widget) {
return Positioned(
child: Material(
child: Container(
color: Colors.transparent,
height: 29,
width: 100,
child: Center(child: Text(choosedAnswer)))),
left: offset.dx,
top: offset.dy,
);
},
),
);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
SizedBox(
height: 20,
),
Row(
children: [
Text('text'),
Container(
key: target,
height: 30,
width: 100,
child: Center(child: Text(finalAnswer)),
decoration:
BoxDecoration(border: Border(bottom: BorderSide())),
),
Text('text')
],
),
SizedBox(
height: 20,
),
GestureDetector(
child: Container(
height: 30,
width: 100,
color: Colors.blue[200],
child: Center(child: Text(answer, key: toMove))),
onTap: () async {
setState(() {
answer = '';
});
RenderBox box1 = target.currentContext.findRenderObject();
Offset targetPosition = box1.localToGlobal(Offset.zero);
RenderBox box2 = toMove.currentContext.findRenderObject();
Offset toMovePosition = box2.localToGlobal(Offset.zero);
setState(() {
answer = '';
choosedAnswer = 'answer';
});
dxStart = toMovePosition.dx;
dyStart = toMovePosition.dy;
dx = targetPosition.dx;
dy = targetPosition.dy;
Overlay.of(context).insert(overlayEntry);
setState(() {});
await Future.delayed(Duration(milliseconds: 500));
overlayEntry.remove();
setState(() {
finalAnswer = 'answer';
});
},
),
],
),
),
);
}
}
Sorry for the poor naming of the variables :)