Related
I am new to flutter, I have built a quizz app that takes 5 questions randomly from a pool of questions and presents them to the user one after the other, then displays the total score at the end (on a different) screen with the option of retaking the quiz (with another set of randomly picked questions).
My issue I am facing is that when I choose to retake the quiz, if in the pool of questions presented there is a question from the past quiz, it still has its options highlighted (marked either wrong or correct as per the previous selection).
Can someone help me on how to totally dismiss previous choices after taking a quiz ?
This is an example of question answered in the previous quiz, and it came back with the option already highlighted (my previous answer).
[enter image description here][1]
[1]: https://i.stack.imgur.com/U1YFf.png[enter image description here][1]
Here is my code:
import 'package:flutter/material.dart';
import 'package:percent_indicator/percent_indicator.dart';
import 'package:schoolest_app/widgets/quizz/quizz_image_container.dart';
import '../../models/quizz.dart';
import '../../widgets/quizz/options_widget.dart';
import '../../widgets/quizz/quizz_border_container.dart';
import '../../widgets/quizz/result_page.dart';
class QuizzDisplayScreen extends StatefulWidget {
const QuizzDisplayScreen({
Key? key,
}) : super(key: key);
static const routeName = '/quizz-display';
#override
State<QuizzDisplayScreen> createState() => _QuizzDisplayScreenState();
}
class _QuizzDisplayScreenState extends State<QuizzDisplayScreen> {
enter code here
late String quizzCategoryTitle;
late List<Question> categoryQuestions;
late List<Question> quizCategoryQuestions;
var _loadedInitData = false;
#override
void didChangeDependencies() {
if (!_loadedInitData) {
final routeArgs =
ModalRoute.of(context)!.settings.arguments as Map<String, String>;
quizzCategoryTitle = (routeArgs['title']).toString();
// final categoryId = routeArgs['id'];
categoryQuestions = questions.where((question) {
return question.categories.contains(quizzCategoryTitle);
}).toList();
quizCategoryQuestions =
(categoryQuestions.toSet().toList()..shuffle()).take(5).toList();
_loadedInitData = true;
}
super.didChangeDependencies();
}
late PageController _controller;
int _questionNumber = 1;
int _score = 0;
int _totalQuestions = 0;
bool _isLocked = false;
void _resetQuiz() {
for (var element in quizCategoryQuestions) {
setState(()=> element.isLocked == false);
}
}
#override
void initState() {
super.initState();
_controller = PageController(initialPage: 0);
}
#override
void dispose() {
_controller.dispose();
_resetQuiz();
super.dispose();
}
#override
Widget build(BuildContext context) {
final myPrimaryColor = Theme.of(context).colorScheme.primary;
final mySecondaryColor = Theme.of(context).colorScheme.secondary;
double answeredPercentage =
(_questionNumber / quizCategoryQuestions.length);
return quizCategoryQuestions.isEmpty
? Scaffold(
appBar: AppBar(
title: Text(
'Quizz - $quizzCategoryTitle',
style: TextStyle(color: myPrimaryColor),
),
iconTheme: IconThemeData(
color: myPrimaryColor,
),
centerTitle: true,
backgroundColor: Colors.transparent,
elevation: 0,
flexibleSpace: Container(
decoration: BoxDecoration(`enter code here`
borderRadius: const BorderRadius.only(`enter code here`
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15),
),
color: mySecondaryColor,
border: Border.all(color: myPrimaryColor, width: 1.0),
),
),
),
body: const Center(
child: Text('Cette catégorie est vide pour l\'instant'),
))
: Scaffold(
appBar: AppBar(
title: Text(
'Quizz - $quizzCategoryTitle',
style: TextStyle(color: myPrimaryColor),
),
iconTheme: IconThemeData(
color: myPrimaryColor,
),
centerTitle: true,
backgroundColor: Colors.transparent,
elevation: 0,
flexibleSpace: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15),
),
color: mySecondaryColor,
border: Border.all(color: myPrimaryColor, width: 1.0),
),
),
),
body: Container(
// height: 600,
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'Question $_questionNumber/${quizCategoryQuestions.length}',
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
),
CircularPercentIndicator(
radius: 40,
// animation: true,
// animationDuration: 2000,
percent: answeredPercentage,
progressColor: myPrimaryColor,
backgroundColor: Colors.cyan.shade100,
circularStrokeCap: CircularStrokeCap.round,
center: Text(
// ignore: unnecessary_brace_in_string_interps
'${(answeredPercentage * 100).round()} %',
style: const TextStyle(
fontSize: 10, fontWeight: FontWeight.bold),
),
// lineWidth: 10,
)
],
),
const SizedBox(height: 10),
Divider(
thickness: 1,
color: myPrimaryColor,
),
Expanded(
child: PageView.builder(
itemCount: quizCategoryQuestions.length,
controller: _controller,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final _question = quizCategoryQuestions[index];
return buildQuestion(_question);
},
),
),
_isLocked
? buildElevatedButton(context)
: const SizedBox.shrink(),
const SizedBox(height: 10),
],
),
),
);
}
Column buildQuestion(Question question) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
question.text!.isNotEmpty
? QuizzBorderContainer(
childWidget: Text(
question.text!,
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
),
)
: const SizedBox.shrink(),
question.imagePath!.isNotEmpty
? QuizzImageContainer(imagePath: question.imagePath!)
: const SizedBox.shrink(),
Expanded(
child: OptionsWidget(
question: question,
onClickedOption: (option) {
if (question.isLocked) {
return;
} else {
setState(() {
question.isLocked = true;
question.selectedOption = option;
});
_isLocked = question.isLocked;
if (question.selectedOption!.isCorrect) {
_score++;
}
}
},
),
),
],
);
}
ElevatedButton buildElevatedButton(BuildContext context) {
final mySecondaryColor = Theme.of(context).colorScheme.secondary;
return ElevatedButton(
onPressed: () {
if (_questionNumber < quizCategoryQuestions.length) {
_controller.nextPage(
duration: const Duration(milliseconds: 1000),
curve: Curves.easeInExpo,
);
setState(() {
_questionNumber++;
_isLocked = false;
});
} else {
setState(() {
// _isLocked = false;
_totalQuestions = quizCategoryQuestions.length;
});
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
ResultPage(score: _score, totalQuestions: _totalQuestions),
),
);
}
},
child: Text(
_questionNumber < quizCategoryQuestions.length
? 'Suivant'
: 'Voir le résultat',
style: TextStyle(
color: mySecondaryColor,
fontWeight: FontWeight.bold,
),
),
);
}
}
I don't seem to the solution to this.
And this is the code on the result page:
import 'package:flutter/material.dart';
import '../../screens/quizz/quizz_screen.dart';
class ResultPage extends StatefulWidget {
final int score;
final int totalQuestions;
const ResultPage({
Key? key,
required this.score,
required this.totalQuestions,
}) : super(key: key);
#override
State<ResultPage> createState() => _ResultPageState();
}
class _ResultPageState extends State<ResultPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SizedBox(
height: 150,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
'You got ${widget.score}/${widget.totalQuestions}',
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const QuizzScreen(),
),
);
},
child: const Text('OK'),
),
],
),
),
),
);
}
}
I don't know what is missing to get the reset right.
When you want to take retest try to dispose all the answers which are saved in the memory. Or you can use these navigators to which might help you in solving the issue. Try using pushReplacement or pushAndRemoveUntil when navigating to retest, this will clear the memory of last pages and you will achive the goal which you want.
I'm developing a fill-in-the-blanks quiz app.
There are 5 question statements in one quiz, but when I move on to the next question statement, the value entered in the text field remains. Could you please tell me what are the possible causes?
class PlayGame extends StatefulWidget {
final List document;
List correctList = [];
PlayGame({Key? key, required this.document}) : super(key: key);
#override
State<PlayGame> createState() => _PlayGameState();
}
class _PlayGameState extends State<PlayGame> {
int quizNum = 0;
int quizCount = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: Center(
child: Text(
"$quizCount/5",
style: const TextStyle(
fontSize: 25,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold),
),
),
actions: [
Row(
children: [
IconButton(
onPressed: () {
setState(
() {
if (quizNum < 4) {
quizNum += 1;
quizCount += 1;
} else if (quizNum == 4) {
print(widget.correctList.length);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Result()),
);
}
},
);
},
icon: const Icon(
Icons.arrow_circle_right_outlined,
size: 40,
),
),
const SizedBox(
width: 10,
)
],
)
],
automaticallyImplyLeading: false,
),
body: SizedBox(
height: double.infinity,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(bottom: 30.0),
child: TextWithBlanks(
text: widget.document[quizNum],
correctList: widget.correctList),
),
),
),
);
}
}
This is the code I was taught here. Words surrounded by "{}" are BlankWord.
class TextWithBlanks extends StatefulWidget {
final String text;
static final regex = RegExp("(?={)|(?<=})");
List correctList = [];
TextWithBlanks({Key? key, required this.text, required this.correctList})
: super(key: key);
#override
State<TextWithBlanks> createState() => _TextWithBlanksState();
}
class _TextWithBlanksState extends State<TextWithBlanks> {
#override
Widget build(BuildContext context) {
final split = widget.text.split(TextWithBlanks.regex);
return Padding(
padding: const EdgeInsets.only(top: 30.0, right: 30.0, left: 30.0),
child: Text.rich(
TextSpan(
style: const TextStyle(fontSize: 15, height: 3.0),
children: <InlineSpan>[
for (String text in split)
text.startsWith('{')
? WidgetSpan(
child: blankWord(text.substring(1, text.length - 1),
widget.correctList),
)
: TextSpan(text: text),
],
),
),
);
}
}
This is the BlankWord.
class _blankWordState extends State<blankWord> {
#override
Widget build(BuildContext context) {
return SizedBox(
width: widget.answerWidth,
child: TextField(
maxLines: null,
cursorColor: Colors.grey,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
autofocus: false,
maxLength: widget.answerLength + 5,
onChanged: (enterWord) {
widget.value = enterWord;
if (enterWord == widget.answer) {
if (widget.answerBool == false) {
widget.answerBool = true;
widget.correctList.add(widget.answer);
}
} else {
if (widget.answerBool == true) {
widget.answerBool = false;
widget.correctList.remove(widget.answer);
}
}
},
decoration: InputDecoration(
counterText: "",
hintText: widget.answerHint,
hintStyle: const TextStyle(color: Colors.grey, fontSize: 12),
),
),
);
}
}
When you update the page by changing the quiz number also reset the value that you are sending to the blank widget. When the blank widget is updated the widget.value is being updated, and that value stays in the class and when a new blank widget is added the value is being sent to the blank widget again I think
widget.value = enterWord;
I am trying to build a questionnaire within an application
The idea is that the user answers questions
So every answer he gives is in one question
Accordingly he will receive the following question
For example if he answered a question with answer a
Will get one question
If he answered the same question with answer b
Will get another question later
how can i solve this
The code is attached
Thanks
import 'package:flutter/material.dart';
import 'package:gsheets/question_model.dart';
class QuizScreen extends StatefulWidget {
#override
State<QuizScreen> createState() => _QuizScreenState();
}
class _QuizScreenState extends State<QuizScreen> {
//define the datas
List<Question> questionList = getQuestions();
int currentQuestionIndex = 0;
int score = 0;
Answer? selectedAnswer;
int? nextQuestionId;
int? questionId;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromARGB(255, 5, 50, 80),
body: Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 32),
child:
Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
const Text(
"Simple Quiz App",
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
_questionWidget(),
_answerList(),
_nextButton(),
]),
),
);
}
_questionWidget() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Question ${currentQuestionIndex + 1}/${questionList.length.toString()}",
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 20),
Container(
alignment: Alignment.center,
width: double.infinity,
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.orangeAccent,
borderRadius: BorderRadius.circular(16),
),
child: Text(
questionList[currentQuestionIndex].questionText,
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
)
],
);
}
_answerList() {
return Column(
children: questionList[currentQuestionIndex]
.answerList
.map(
(e) => _answerButton(e),
)
.toList(),
);
}
Widget _answerButton(Answer answer) {
bool isSelected = answer == selectedAnswer;
return Container(
width: double.infinity,
margin: const EdgeInsets.symmetric(vertical: 8),
height: 48,
child: ElevatedButton(
child: Text(answer.answerText),
style: ElevatedButton.styleFrom(
shape: const StadiumBorder(),
primary: isSelected ? Colors.orangeAccent : Colors.white,
onPrimary: isSelected ? Colors.white : Colors.black,
),
onPressed: () {
// if (selectedAnswer == null) {
// if (answer.isCorrect) {
// score++;
// }
setState(() {
selectedAnswer = answer;
});
}
// },
),
);
}
_nextButton() {
// if(answer == )
bool isLastQuestion = false;
if (currentQuestionIndex == questionList.length - 1) {
isLastQuestion = true;
}
return Container(
width: MediaQuery.of(context).size.width * 0.5,
height: 48,
child: ElevatedButton(
child: Text(isLastQuestion ? "Submit" : "Next"),
style: ElevatedButton.styleFrom(
shape: const StadiumBorder(),
primary: Colors.blueAccent,
onPrimary: Colors.white,
),
onPressed: () {
// if(nextQuestionId == questionId)
if (isLastQuestion) {
//display score
showDialog(context: context, builder: (_) => _showScoreDialog());
} else {
//next question
setState(() {
selectedAnswer = null;
currentQuestionIndex++;
});
}
},
),
);
}
_showScoreDialog() {
bool isPassed = false;
if (score >= questionList.length * 0.6) {
//pass if 60 %
isPassed = true;
}
String title = isPassed ? "Passed " : "Failed";
return AlertDialog(
title: Text(
title + " | Score is $score",
style: TextStyle(color: isPassed ? Colors.green : Colors.redAccent),
),
content: ElevatedButton(
child: const Text("Restart"),
onPressed: () {
Navigator.pop(context);
setState(() {
currentQuestionIndex = 0;
score = 0;
selectedAnswer = null;
});
},
),
);
}
}
========================
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gsheets/quiz.dart';
class Question {
final String questionText;
final List<Answer> answerList;
final int questionId;
Question(this.questionText, this.answerList, this.questionId);
}
class Answer {
final String answerText;
final int nextQuestionId;
// final bool selectedOption;
Answer(this.answerText, this.nextQuestionId);
}
List<Question> getQuestions() {
List<Question> list = [];
list.add(Question(
"Which school did you learn",
[
Answer("a", 3),
Answer("b", 2),
],
3,
));
list.add(Question(
"what is your age",
[
Answer("18", 3),
Answer("20", 5),
],
2,
));
list.add(Question(
"which car do you drive",
[
Answer("c", 3),
Answer("d", 2),
],
3,
));
return list;
}
I am building a quiz app and I created a custom widget to save me a lot of time as I have a lot of questions for the quiz. Everything works apart from the scoring system. If I create multiple instances of the same widget the score will not be incremented and it will stay on 1. Is there any way I can pass each score of the widgets to a global variable in my main widget so then I can add all the scores? (I'm new to flutter).
Custom Widget
class Questions extends StatefulWidget {
final String imagePath;
final String question;
final String answer1;
final String answer2;
final String answer3;
final String answer4;
final bool iscorrectAnswer1;
final bool iscorrectAnswer2;
final bool iscorrectAnswer3;
final bool iscorrectAnswer4;
int score = 0;
bool questionsAnswered = false;
Questions(
this.imagePath,
this.question,
this.answer1,
this.answer2,
this.answer3,
this.answer4,
this.iscorrectAnswer1,
this.iscorrectAnswer2,
this.iscorrectAnswer3,
this.iscorrectAnswer4,
);
#override
_QuestionsState createState() => _QuestionsState();
}
class _QuestionsState extends State<Questions> {
disableButton() {
setState(() {
widget.questionsAnswered = true;
Quiz().score += widget.score;
});
}
#override
#override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
width: 600,
height: 600,
child: Image.asset(widget.imagePath),
),
Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(
top: 20,
),
child: Text(
widget.question,
style: TextStyle(
color: Colors.white,
fontSize: 38,
),
),
)),
Padding(
padding: EdgeInsets.only(
top: 40,
),
child: SizedBox(
width: 500,
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Color(0xFF304e60),
),
),
child: Text(
widget.answer1,
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
onPressed: widget.questionsAnswered == false
? () {
setState(() {
if (widget.iscorrectAnswer1 == true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Correct!'),
),
);
disableButton();
widget.score += 1;
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text('Wrong Answer!'),
));
}
});
print(widget.iscorrectAnswer1);
print(widget.score);
}
: null),
),
)),
Padding(
padding: EdgeInsets.only(
top: 10,
),
child: SizedBox(
width: 500,
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Color(0xFF565462))),
child: Text(
widget.answer2,
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
onPressed: widget.questionsAnswered == false
? () {
setState(() {
if (widget.iscorrectAnswer2 == true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Correct!'),
),
);
widget.score += 1;
} else {
disableButton();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text('Wrong Answer!'),
));
}
});
}
: null),
),
)),
Padding(
padding: EdgeInsets.only(
top: 10,
),
child: SizedBox(
width: 500,
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Color(0xFF84693b))),
child: Text(
widget.answer3,
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
onPressed: widget.questionsAnswered == false
? () {
setState(() {
if (widget.iscorrectAnswer3 == true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Correct!'),
),
);
widget.score += 1;
} else {
disableButton();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text('Wrong Answer!'),
));
}
});
}
: null),
),
),
)
],
);
}
}
Main widget where I call this custom widget
class Quiz extends StatefulWidget {
Quiz({Key? key}) : super(key: key);
int score = 0;
#override
_QuizState createState() => _QuizState();
}
class _QuizState extends State<Quiz> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('CyberQuiz'),
),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
Questions(
'images/malware_quiz.jpeg',
'1. What is a malware?',
'Designed to damage computers, servers or any other devices',
"Used to get user's credentials",
"It's used to destroy networks",
'',
true,
false,
false,
false,
),
],
)));
}
}
As you suggest in your question, you could create a global variable and increment/decrease/reset that.
Basic example code:
import 'package:flutter/material.dart';
class Score {
static int score = 0;
}
class ScoreCounter extends StatefulWidget {
const ScoreCounter({Key? key}) : super(key: key);
#override
State<ScoreCounter> createState() => _ScoreCounterState();
}
class _ScoreCounterState extends State<ScoreCounter> {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
setState(() {
Score.score++;
});
},
child: Text('increase score'),
),
),
Expanded(child: Text(Score.score.toString()))
],
);
}
}
Another option is to use the Provider package - link here which has an example
Provider Package
I'm working on weather forecast app (code same as from previous issue How to make SliverAppBar listen to botomSheet change).
This model worked without errors for changing city, however, when I implemented BottomAppBar navigation (changing daily and hourly forecast), like below, it doesn't update the content anymore - function for getting city works, but CustomScrollView doesn't "redraw" for updated data.
I changed following:
Body of Scaffold-class app now loads from list, by index selected in BottomAppBar.
body: _children[_currentIndex]
List
final List<Widget> _children = [
MyScrollView('hourly'),
MyScrollView('daily')
];
And I put whole CustomScrollView into MyScrollView() class, alltogether with data fetching methods
class MyScrollView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new CustomScrollView(...)}
}
How to change CustomScrollView after the city is changed?
Edit:
After further research, my problem seems, that Stateless Widget cannot change its' State, but as soon as I change MyScrollView to Stateful, it isn't accepted as Scaffold's body.
Source Code
Edit: 1
You're not returning any widget from your build method, so replace
#override
Widget build(BuildContext context) {
new Scaffold(...);
}
with
#override
Widget build(BuildContext context) {
return new Scaffold(...); // add return
}
Edit: 2
Screenshot:
Full code:
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _currentIndex = 0;
String _cityGet;
double _cityLat;
double _cityLon;
#override
void initState() {
super.initState();
// initializeDateFormatting();
}
Widget _getChild(int index) {
switch (index) {
case 0:
return MyScrollView("hourly");
case 1:
return MyScrollView("daily");
}
return MyScrollView("hourly");
}
Future _changeCity(BuildContext context) async {
Map results = await Navigator.of(context).push(MaterialPageRoute<Map>(builder: (BuildContext context) {
return CityPage();
}));
if (results != null && results.containsKey('enter')) {
_cityGet = '${results['enter']}, ${results['code']}';
_cityLat = results['lat'];
_cityLon = results['lon'];
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
//todo: change colors
//todo: set theme styles for text
//todo: maybe delete appbar?
floatingActionButton: FloatingActionButton(
child: Icon(Icons.location_city),
backgroundColor: Theme.of(context).accentColor,
onPressed: () => _MyScrollViewState()._changeCity(context),
),
bottomNavigationBar: BottomAppBar(
shape: CircularNotchedRectangle(),
notchMargin: 5,
clipBehavior: Clip.antiAlias,
child: BottomNavigationBar(
onTap: onTabTapped,
currentIndex: _currentIndex,
backgroundColor: Colors.grey.shade900,
selectedItemColor: Colors.yellow.shade600,
items: [
BottomNavigationBarItem(icon: Icon(Icons.watch_later), title: Text('Hodinová predpoveď')),
BottomNavigationBarItem(icon: Icon(Icons.calendar_today), title: Text('Denná predpoveď')),
]),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
body: _getChild(_currentIndex));
//backgroundColor: Colors.grey.shade700);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
class MyScrollView extends StatefulWidget {
final String rate;
const MyScrollView(this.rate);
#override
_MyScrollViewState createState() => _MyScrollViewState();
}
class _MyScrollViewState extends State<MyScrollView> {
String interval;
String _cityName;
var _cityLat;
var _cityLon;
// MyScrollView(this.interval);
String _rate; // do whatever you want to do with this
// you are free to call setState anywhere from this class
#override
void initState() {
super.initState();
_rate = widget.rate;
}
Future _changeCity(BuildContext context) async {
Map results = await Navigator.of(context).push(MaterialPageRoute<Map>(builder: (BuildContext context) {
return CityPage();
}));
if (results != null && results.containsKey('enter')) {
_cityName = '${results['enter']}, ${results['code']}';
_cityLat = results['lat'];
_cityLon = results['lon'];
}
}
#override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: <Widget>[
SliverAppBar(
centerTitle: true,
title: Text(
_cityName == null ? 'Liptovský Mikuláš, SK' : _cityName,
style: TextStyle(fontSize: 17.5, fontWeight: FontWeight.w200),
),
expandedHeight: 300,
floating: true,
pinned: true,
snap: true,
flexibleSpace: FlexibleSpaceBar(
background: Container(
child: updateCurrentWeather(),
)),
),
FutureBuilder(
future: _cityLat == null || _cityLon == null ? getWeather(49.083351, 19.609819) : getWeather(_cityLat, _cityLon),
builder: (BuildContext context, AsyncSnapshot<Map> snapshot) {
var forecast = snapshot.data;
var childrenCount = 0;
if (snapshot.connectionState != ConnectionState.done || snapshot.hasData == null)
childrenCount = 1;
else if (interval == 'hourly') {
childrenCount = 24;
} else {
childrenCount = 8;
}
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
if (snapshot.connectionState != ConnectionState.done) {
//todo handle state
return Container(); //todo set progress bar
}
if (interval == null || forecast == null) {
return Container();
}
var tempMax = forecast['$interval']['data'][index]['temperatureMax'];
var tempMin = forecast['$interval']['data'][index]['temperatureMin'];
var temperature = forecast['$interval']['data'][index]['temperature'];
String icon = forecast['$interval']['data'][index]['icon'];
var template = DateFormat('Hm');
int hour = forecast['$interval']['data'][index]['time'];
var hourObject = DateTime.fromMillisecondsSinceEpoch(hour * 1000);
String time = template.format(hourObject);
// initializeDateFormatting('sk');
var templateDay = DateFormat('EEEE', 'sk');
int dayData = forecast['$interval']['data'][index]['time'];
var dayObject = DateTime.fromMillisecondsSinceEpoch(dayData * 1000);
String capitalize(String s) => s[0].toUpperCase() + s.substring(1);
String dayUncap = templateDay.format(dayObject);
String day = capitalize(dayUncap);
String summary = forecast['$interval']['data'][index]['summary'];
var probability = forecast['$interval']['data'][index]['precipProbability'];
var precipitation = forecast['$interval']['data'][index]['precipIntensity'];
var humidity = forecast['$interval']['data'][index]['humidity'];
var uv = forecast['$interval']['data'][index]['uvIndex'];
var pressure = forecast['$interval']['data'][index]['pressure'];
return Card(
margin: index == 0 ? EdgeInsets.fromLTRB(20, 6, 20, 3) : EdgeInsets.fromLTRB(20, 3, 20, 3),
color: Colors.black12,
child: ExpansionTile(
leading: Image.asset(chocolateImage),
trailing: Text(
interval == 'hourly' ? '${temperature.toStringAsFixed(0)}°' : '${tempMin.toStringAsFixed(0)}° ${tempMax.toStringAsFixed(0)}°',
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 20.0),
),
title: RichText(
text: TextSpan(
text: interval == 'hourly' ? '$time ' : index == 0 ? 'Dnes ' : index == 1 ? 'Zajtra ' : '$day ',
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16.0),
children: <TextSpan>[TextSpan(text: '$summary', style: TextStyle(fontWeight: FontWeight.w300, fontSize: 15.5))])),
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 15),
//height: 80,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
// TEMP STATS - HOUR
padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 6),
// child: Icon(
// WeatherIcons.thermometer,
// size: 20,
// ),
),
Text(
interval == 'hourly' ? '${temperature.toStringAsFixed(0)}°' : '',
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w600),
),
Text('Teplota', style: TextStyle(fontWeight: FontWeight.w300, fontSize: 14.0))
],
)),
Container(
// RAIN STATS - HOUR
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 6),
// child: Icon(
// WeatherIcons.umbrella,
// size: 20,
// ),
),
Text(
'${(probability * 100).toStringAsFixed(0)}%',
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w600),
),
Text('${precipitation.toStringAsFixed(2)} mm', style: TextStyle(fontWeight: FontWeight.w300, fontSize: 14.0))
],
)),
Container(
// HUMIDITY STATS - HOUR
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 6),
// child: Icon(
// WeatherIcons.humidity,
// size: 20,
// ),
),
Text(
'${(humidity * 100).toStringAsFixed(0)}%',
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w600),
),
Text('Vlhkosť', style: TextStyle(fontWeight: FontWeight.w300, fontSize: 14.0))
],
)),
Container(
// UV STATS - HOUR
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 6),
// child: Icon(
// WeatherIcons.day_sunny,
// size: 20,
// ),
),
Text(
'UV $uv',
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w600),
),
Text('Žiarenie', style: TextStyle(fontWeight: FontWeight.w300, fontSize: 14.0))
],
)),
Container(
// PRESSURE STATS - HOUR
padding: const EdgeInsets.fromLTRB(0, 0, 10, 0),
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 6),
// child: Icon(
// WeatherIcons.barometer,
// size: 20,
// ),
),
Text(
'${pressure.toStringAsFixed(0)} hpa',
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w600),
),
Text('Tlak', style: TextStyle(fontWeight: FontWeight.w300, fontSize: 14.0))
],
)),
],
),
)
],
),
);
}, childCount: childrenCount),
);
},
)
],
);
}
Widget updateCurrentWeather() {
return FutureBuilder(
future: getWeather(49.083351, 19.609819),
builder: (BuildContext context, AsyncSnapshot<Map> snapshot) {
if (snapshot.hasData) {
Map content = snapshot.data;
var temperature = content['currently']['temperature'];
var probability = content['currently']['precipProbability'];
var precipitation = content['currently']['precipIntensity'];
var windSpeed = content['currently']['windSpeed'];
int windBearing = content['currently']['windBearing'];
var moonPhase = content['daily']['data'][0]['moonPhase'];
var humidity = content['currently']['humidity'];
String icon = content['currently']['icon'];
//var template = DateFormat('Hm','sk');
// var timeFormat = DateFormat.Hm('sk');
// int sunrise = content['daily']['data'][0]['sunriseTime'];
// var sunriseObject = DateTime.fromMillisecondsSinceEpoch(sunrise*1000);
// String sunriseTime = timeFormat.format(sunriseObject);
// int sunset = content['daily']['data'][0]['sunsetTime'];
// var sunsetObject = DateTime.fromMillisecondsSinceEpoch(sunset);
// String sunsetTime = timeFormat.format(sunsetObject);
String direction;
String phase;
var windIcon;
var moonIcon;
if (windSpeed != 0) {
if ((0 <= windBearing && windBearing <= 22.5) || (360 > windBearing && 337.5 < windBearing)) {
direction = 'S';
// windIcon = WeatherIcons.wind_deg_180;
} else if (22.5 < windBearing && windBearing <= 67.5) {
direction = 'SV';
// windIcon = WeatherIcons.wind_deg_225;
} else if (67.5 < windBearing && windBearing <= 112.5) {
direction = 'V';
// windIcon = WeatherIcons.wind_deg_270;
} else if (112.5 < windBearing && windBearing <= 157.5) {
direction = 'JV';
// windIcon = WeatherIcons.wind_deg_315;
} else if (157.5 < windBearing && windBearing <= 202.5) {
direction = 'J';
// windIcon = WeatherIcons.wind_deg_0;
} else if (202.5 < windBearing && windBearing <= 247.5) {
direction = 'JZ';
// windIcon = WeatherIcons.wind_deg_45;
} else if (247.5 < windBearing && windBearing <= 292.5) {
direction = 'Z';
// windIcon = WeatherIcons.wind_deg_90;
} else if (292.5 < windBearing && windBearing <= 337.5) {
direction = 'SZ';
// windIcon = WeatherIcons.wind_deg_135;
}
} else {
direction = '';
// windIcon = WeatherIcons.na;
}
if (moonPhase == 0) {
// moonIcon = WeatherIcons.moon_new;
phase = 'Nov';
} else if (0 < moonPhase && moonPhase < 0.25) {
// moonIcon = WeatherIcons.moon_alt_waxing_crescent_3;
phase = 'Dorastá';
} else if (moonPhase == 0.25) {
// moonIcon = WeatherIcons.moon_first_quarter;
phase = '1. štvrť';
} else if (0.25 < moonPhase && moonPhase < 0.5) {
// moonIcon = WeatherIcons.moon_alt_waxing_gibbous_3;
phase = 'Dorastá';
} else if (moonPhase == 0.5) {
// moonIcon = WeatherIcons.moon_full;
phase = 'Spln';
} else if (0.5 < moonPhase && moonPhase < 0.75) {
// moonIcon = WeatherIcons.moon_alt_waning_gibbous_3;
phase = 'Cúva';
} else if (moonPhase == 0.75) {
// moonIcon = WeatherIcons.moon_third_quarter;
phase = '3. štvrť';
} else {
// moonIcon = WeatherIcons.moon_waning_crescent_3;
phase = 'Cúva';
}
return Container(
alignment: Alignment.topCenter,
child: Column(
children: <Widget>[
Stack(
alignment: Alignment.topCenter,
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(25, 30, 140, 0),
child: Image.asset(
chocolateImage,
width: 160,
)),
Container(padding: const EdgeInsets.fromLTRB(0, 50, 0, 0), child: Image.asset(chocolateImage)),
],
),
Padding(padding: const EdgeInsets.fromLTRB(0, 10, 0, 0)),
Row(
//crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
// TEMP STATS - CURRENT
padding: const EdgeInsets.fromLTRB(20, 0, 0, 0),
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 6),
// child: Icon(
// WeatherIcons.thermometer,
// size: 20,
// ),
),
Text(
'${temperature.toStringAsFixed(0)}°',
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w600),
),
Text('Teplota', style: TextStyle(fontWeight: FontWeight.w300, fontSize: 14.0))
],
)),
Container(
// RAIN STATS - CURRENT
//padding: const EdgeInsets.fromLTRB(0, 220, 0, 0),
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 6),
// child: Icon(
// WeatherIcons.umbrella,
// size: 20,
// ),
),
Text(
'${(probability * 100).toStringAsFixed(0)}%',
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w600),
),
Text('${precipitation.toStringAsFixed(2)} mm', style: TextStyle(fontWeight: FontWeight.w300, fontSize: 14.0))
],
)),
Container(
// HUMIDITY STATS - CURRENT
//padding: const EdgeInsets.fromLTRB(0, 220, 0, 0),
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 6),
// child: Icon(
// WeatherIcons.humidity,
// size: 20,
// ),
),
Text(
'${(humidity * 100).toStringAsFixed(0)}%',
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w600),
),
Text('Vlhkosť', style: TextStyle(fontWeight: FontWeight.w300, fontSize: 14.0))
],
)),
Container(
// WIND STATS - CURRENT
//padding: const EdgeInsets.fromLTRB(0, 220, 0, 0),
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 6),
child: Icon(
windIcon,
size: 20,
)),
Text(
'${windSpeed.toStringAsFixed(1)} m/s',
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w600),
),
//todo: condition update - if wind speed == 0: wind bearing= none
Text('$direction', style: TextStyle(fontWeight: FontWeight.w300, fontSize: 14.0))
],
)),
Container(
// MOON STATS - CURRENT
padding: const EdgeInsets.fromLTRB(0, 0, 20, 0),
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 6),
child: Icon(
moonIcon,
size: 20,
)),
Text(
'$phase',
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w600),
),
//todo: condition update - if wind speed == 0: wind bearing= none
Text('Fáza', style: TextStyle(fontWeight: FontWeight.w300, fontSize: 14.0))
],
)),
Container()
],
)
],
),
);
} else {
return Container();
}
});
}
Future<Map> getWeather(double lat, double lon) async {
String key = '847155bb7e53129f8c2d68472a0b07b6';
//todo: switch to deploy key
String apiUrl = 'https://api.darksky.net/forecast/$key/$lat,$lon?lang=sk&units=si';
http.Response response = await http.get(apiUrl);
return json.decode(response.body);
}
}
Make MyScrollView a Stateful Widget.
Edit:
Initialize _currentIndex to 0 and use setState to change it to 1 whenever the user click on the FAB.
If your trying to update the data from another class then check the below link:
How to Set/Update Sate of StatefulWidget from other StatefulWidget in Flutter?
Screenshot:
Not sure if it would help you.
Code:
void main() => runApp(MaterialApp(home: MyPage()));
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
int _count = 0;
String _text = "";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Weather"),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => setState(() => ++_count),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
IconButton(
icon: Icon(Icons.call),
onPressed: () => setState(() => _text = "Call"),
),
IconButton(
icon: Icon(Icons.message),
onPressed: () => setState(() => _text = "Message"),
)
],
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Count: $_count"),
SizedBox(height: 8),
Text(_text),
],
),
),
);
}
}