How to change content of Stateless Widget - flutter

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),
],
),
),
);
}
}

Related

How Can i create a conditional questionnaire in Flutter

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;
}

How to update state within a custom tab bar with flutter

I'm trying to create a custom TabBar which navigates only forward with a button click.
im using an enum to manage the state and couple of class objects to manage items for Tabs.
What result I expected
VS
What result I've got
The code I worked out is as follows. I am still trying to figure out a way of managing the expected outcome with my code implementation. if anyone can help out will be amazing.
Object classes
class CategoryModel {
final String? title;
final eCategory category;
// final eCategoryState? catState;
CategoryModel({this.title, required this.category});
}
class CategoryStateModel {
final CategoryModel? tItem;
late eTabState? tState;
CategoryStateModel({this.tItem, this.tState});
}
The code portion
class _ContributeTabScreenState extends State<ContributeTabScreen>
with SingleTickerProviderStateMixin {
eButtonState _submitBtnState = eButtonState.bActive;
eButtonState _continueBtnState = eButtonState.bActive;
int pageNo = 0;
TabController? _tabController;
late List<eTabState>? _tabState = [];
late List<CategoryModel>? _categoryModelList = [];
late List<Tab>? _tabList = [];
late List<CategoryStateModel>? _catStateList = [];
#override
void initState() {
_categoryModelList = widget.catList;
_assignTabs();
_tabController = new TabController(
vsync: this,
length: _categoryModelList!.length, //3,
);
super.initState();
// print(_categoryModelList![0].title.toString());
}
List<Tab> _assignTabs() {
for (var item = 0; item < _categoryModelList!.length; item++) {
//
if (item != 0) {
for (var t in _categoryModelList!) {
_catStateList!.add(
new CategoryStateModel(tItem: t, tState: eTabState.tUnSelected));
}
} else {
for (var t in _categoryModelList!) {
_catStateList!.add(
new CategoryStateModel(tItem: t, tState: eTabState.tSelected));
}
}
//
_tabList!.add(
new Tab(
child: _tabItem(_catStateList![item]),
// child: _tabItem(_categoryModelList![item]),
),
);
}
return _tabList!;
}
void _goBack() {
Navigator.of(context).pop();
}
//the onPressed call back I manage the forward move of a tabbar item + tabview
void forwardTabPage() {
if (pageNo >= 0 && pageNo < _categoryModelList!.length) {
setState(() {
// });
// setState(() {
pageNo = pageNo + 1;
// _catStateList![pageNo - 1].tState = eTabState.tCompleted;
// _tabState![pageNo - 1] = _catStateList![pageNo - 1].tState!;
});
_tabController!.animateTo(pageNo);
}
}
...rest of the code
//the Tabbar item
_tabItem(CategoryStateModel item) => Container(
width: 140.0,
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
item.tItem!.title!,
style: tBody4.copyWith(color: CustomColors.mDarkBlue),
),
item.tState == _catStateList![pageNo].tState //eTabState.tCompleted
? SizedBox(
width: 8.0,
)
: SizedBox(
width: 0.0,
),
item.tState == _catStateList![pageNo].tState //eTabState.tCompleted
? CircleAvatar(
radius: 12.0,
backgroundColor: CustomColors.green600,
child: Icon(
Icons.check_outlined,
size: 20.0,
),
)
: SizedBox(
width: 0.0,
),
],
),
);
continueBtn() => ButtonWidget(
btnColor: CustomColors.green600,
borderColor: CustomColors.green600,
textColor: CustomColors.mWhite,
text: "Continue",
eButtonType: eButtonType.bText,
eButtonState: _continueBtnState,
onPressed: () {
forwardTabPage();
// _tabState = eTabState.tCompleted;
},
);
submitBtn() => ButtonWidget(
btnColor: CustomColors.green600,
borderColor: CustomColors.green600,
textColor: CustomColors.mWhite,
text: "Submit",
eButtonType: eButtonType.bText,
eButtonState: _submitBtnState,
onPressed: () {},
);
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: _categoryModelList!.length,
child: Scaffold(
appBar: AppBar(
toolbarHeight: 60.0,
leadingWidth: 100.0,
leading: GestureDetector(
onTap: _goBack,
child: Container(
child: Row(
children: [
SizedBox(
width: 16.0,
),
const Icon(
Icons.arrow_back_ios,
color: CustomColors.grey700,
size: 24.0,
),
Flexible(
child: Text(
"Back",
style: tButtonSmall,
),
),
],
),
),
),
backgroundColor: Colors.white,
elevation: 0.0,
bottom: PreferredSize(
preferredSize: Size.fromHeight(50.0),
child: IgnorePointer(
child: TabBar(
controller: _tabController,
isScrollable: false,
enableFeedback: false,
padding: EdgeInsets.only(bottom: 8.0),
indicatorSize: TabBarIndicatorSize.label,
indicator:
// _tabState == eTabState.tSelected
// ?
BoxDecoration(
borderRadius: BorderRadius.circular(40.0),
color: CustomColors.green300,
border: Border.all(color: CustomColors.mGreen, width: 2),
),
tabs: _tabList!
),
),
),
),
body: Container(
child: TabBarView(
controller: _tabController,
physics: NeverScrollableScrollPhysics(),
children: [
Container(
color: CustomColors.grey600.withOpacity(0.2),
child: Center(
child: Text("Home Tab View"),
),
),
Center(
child: Text("Map Tab View"),
),
],
),
),
persistentFooterButtons: [
Container(
width: MediaQuery.of(context).size.width,
// height: 40,
padding: EdgeInsets.symmetric(horizontal: 24.0),
margin: EdgeInsets.symmetric(vertical: 16.0),
child: (_categoryModelList!.length == 1 ||
pageNo == _categoryModelList!.length - 1)
? submitBtn()
: continueBtn(),
)
],
),
);
}
enum classes
enum eCategoryState {
cSelected,
cUnSelected,
cEnded,
}
enum eTabState {
tSelected,
tUnSelected,
tCompleted,
}
enum eCategory {
cAccess,
cAmenities,
}
I don't know if I understand what you mean or not. but try this. TabBar has a isScrollable property. set it to true

Dart - Tic Tac Toe examples of a method for one of the buttons in my game?

I am making a tic tac toe game using Dart and needed a little help implementing a button for my game. I am not too sure exactly how to start making a method for the buttons on the tic tac toe screen. How would I start with the first button method? I was going to use void _button0() {} but just not sure how to go about it for the first one since I'm new to Dart.
Also do I need to make 9 seperate methods for each of the buttons for the tic tac toe game?
import 'package:flutter/material.dart';
import 'dart:math';
class HomePage extends StatefulWidget {
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
// Constant Characters for each player
static const String humanPlayer = '1';
static const String computerPlayer = '2';
// Initial Text for Info Label
String text = "X's Turn";
// Constant for Board Size
static const boardSize = 9;
// Game Variables
var gameOver = false;
var win = 0;
var turn = 0;
var _mBoard = ["", "", "", "", "", "", "", "", ""];
var rnd = new Random(boardSize);
// Button Text Variables?
// Tic Tac Toe Game Code
void displayBoard() {
print("");
print(_mBoard[0] + " | " + _mBoard[1] + " | " + _mBoard[2]);
print("-----------");
print(_mBoard[3] + " | " + _mBoard[4] + " | " + _mBoard[5]);
print("-----------");
print(_mBoard[6] + " | " + _mBoard[7] + " | " + _mBoard[8]);
print("");
}
void checkGameOver(int win) {
print("");
if (win == 1) {
gameOver = true;
displayMessage("It's a tie.");
} else if (win == 2) {
gameOver = true;
displayMessage(humanPlayer + " wins!");
} else if (win == 3) {
gameOver = true;
displayMessage(computerPlayer + " wins!");
} else
displayMessage("There is a logic Problem!");
}
void displayMessage(String text) {
text = text;
print(text);
}
int checkWinner() {
// Check horizontal wins
for (int i = 0; i <= 6; i += 3) {
if (_mBoard[i] == (humanPlayer) &&
_mBoard[i + 1] == (humanPlayer) &&
_mBoard[i + 2] == (humanPlayer)) return 2;
if (_mBoard[i] == (computerPlayer) &&
_mBoard[i + 1] == (computerPlayer) &&
_mBoard[i + 2] == (computerPlayer)) return 3;
}
// Check vertical wins
for (int i = 0; i <= 2; i++) {
if (_mBoard[i] == (humanPlayer) &&
_mBoard[i + 3] == (humanPlayer) &&
_mBoard[i + 6] == (humanPlayer)) return 2;
if (_mBoard[i] == (computerPlayer) &&
_mBoard[i + 3] == (computerPlayer) &&
_mBoard[i + 6] == (computerPlayer)) return 3;
}
// Check for diagonal wins
if ((_mBoard[0] == (humanPlayer) &&
_mBoard[4] == (humanPlayer) &&
_mBoard[8] == (humanPlayer)) ||
(_mBoard[2] == (humanPlayer) &&
_mBoard[4] == (humanPlayer) &&
_mBoard[6] == (humanPlayer))) return 2;
if ((_mBoard[0] == (computerPlayer) &&
_mBoard[4] == (computerPlayer) &&
_mBoard[8] == (computerPlayer)) ||
(_mBoard[2] == (computerPlayer) &&
_mBoard[4] == (computerPlayer) &&
_mBoard[6] == (computerPlayer))) return 3;
for (int i = 0; i < boardSize; i++) {
// If we find a number, then no one has won yet
if (!(_mBoard[i] == (humanPlayer)) && !(_mBoard[i] == (computerPlayer)))
return 0;
}
// If we make it through the previous loop, all places are taken, so it's a tie*/
return 1;
}
void getComputerMove() {
int move;
// First see if there's a move O can make to win
for (int i = 0; i < boardSize; i++) {
if (_mBoard[i] != humanPlayer && _mBoard[i] != computerPlayer) {
String curr = _mBoard[i];
_mBoard[i] = computerPlayer;
if (checkWinner() == 3) {
print('Computer is moving to ${i + 1}');
return;
} else
_mBoard[i] = curr;
}
}
// See if there's a move O can make to block X from winning
for (int i = 0; i < boardSize; i++) {
if (_mBoard[i] != humanPlayer && _mBoard[i] != computerPlayer) {
String curr = _mBoard[i]; // Save the current number
_mBoard[i] = humanPlayer;
if (checkWinner() == 2) {
_mBoard[i] = computerPlayer;
print('Computer is moving to ${i + 1}');
return;
} else
_mBoard[i] = curr;
}
}
// Generate random move
do {
move = rnd.nextInt(boardSize);
} while (_mBoard[move] == humanPlayer || _mBoard[move] == computerPlayer);
print('Computer is moving to ${move + 1}');
_mBoard[move] = computerPlayer;
}
void _button0() {
}
//=====è
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Tic Tac Toe'),
leading: IconButton(
icon: Icon(
Icons.border_all,
semanticLabel: 'menu',
),
onPressed: () {},
),
actions: <Widget>[
new IconButton(
icon: new Icon(Icons.new_releases),
onPressed: () {},
tooltip: 'New Game',
),
IconButton(
icon: new Icon(Icons.refresh),
onPressed: () {},
tooltip: 'Quit Game',
),
PopupMenuButton(itemBuilder: (BuildContext context) {
return [
PopupMenuItem(
child: new GestureDetector(
onTap: () {},
child: new Text("About",
style: TextStyle(
fontWeight: FontWeight.bold,
)),
),
),
PopupMenuItem(
child: new GestureDetector(
onTap: () {
// Some Method
},
child: new Text(
"Settings",
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
),
];
})
]),
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
///
///
body: Center(
child:
Column(mainAxisAlignment: MainAxisAlignment.start, children: [
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Container(
height: 100,
width: 100,
margin: const EdgeInsets.all(10.0),
child: RaisedButton(
padding: const EdgeInsets.all(10.0),
onPressed: () {
},
child: Text(
"",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 80.0,
fontWeight: FontWeight.bold,
fontFamily: 'Roboto',
),
),
)),
Container(
height: 100,
width: 100,
margin: EdgeInsets.all(10.0),
child: RaisedButton(
padding: const EdgeInsets.all(10.0),
onPressed: () {
},
child: Text(
"",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 80,
fontWeight: FontWeight.bold,
fontFamily: "Roboto"),
),
)),
Container(
height: 100,
width: 100,
margin: EdgeInsets.all(10.0),
child: RaisedButton(
padding: const EdgeInsets.all(10.0),
onPressed: () {},
child: Text(
"",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 80,
fontWeight: FontWeight.bold,
fontFamily: "Roboto"),
),
)),
]),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Container(
height: 100,
width: 100,
margin: const EdgeInsets.all(10.0),
child: RaisedButton(
padding: const EdgeInsets.all(10.0),
onPressed: () {},
child: Text(
"",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 80.0,
fontWeight: FontWeight.bold,
fontFamily: 'Roboto',
),
),
)),
Container(
height: 100,
width: 100,
margin: EdgeInsets.all(10.0),
child: RaisedButton(
padding: const EdgeInsets.all(10.0),
onPressed: () {},
child: Text(
"",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 80,
fontWeight: FontWeight.bold,
fontFamily: "Roboto"),
),
)),
Container(
height: 100,
width: 100,
margin: EdgeInsets.all(10.0),
child: RaisedButton(
padding: const EdgeInsets.all(10.0),
onPressed: () {},
child: Text(
"",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 80,
fontWeight: FontWeight.bold,
fontFamily: "Roboto"),
),
)),
]),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Container(
height: 100,
width: 100,
margin: const EdgeInsets.all(10.0),
child: RaisedButton(
padding: const EdgeInsets.all(10.0),
onPressed: () {},
child: Text(
"",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 80.0,
fontWeight: FontWeight.bold,
fontFamily: 'Roboto',
),
),
)),
Container(
height: 100,
width: 100,
margin: EdgeInsets.all(10.0),
child: RaisedButton(
padding: const EdgeInsets.all(10.0),
onPressed: () {},
child: Text(
"",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 80,
fontWeight: FontWeight.bold,
fontFamily: "Roboto"),
),
)),
Container(
height: 100,
width: 100,
margin: EdgeInsets.all(10.0),
child: RaisedButton(
padding: const EdgeInsets.all(10.0),
onPressed: () {},
child: Text(
"",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 80,
fontWeight: FontWeight.bold,
fontFamily: "Roboto"),
),
)),
]),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 100,
width: 300,
margin: const EdgeInsets.all(10.0),
child: Text(
"X's Turn",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25.0,
fontWeight: FontWeight.bold,
fontFamily: 'Roboto',
),
),
)
],
),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Container(
height: 40,
width: 200,
margin: const EdgeInsets.all(10.0),
child: RaisedButton(
onPressed: () {},
child: Text(
"Reset App",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
fontFamily: 'Roboto',
),
),
))
]),
])));
}
}
You can copy paste run full code below
You can reference https://github.com/sbvkrishna/tictactoe-flutter
Step 1: You can create 9 Box(Button) with GridView.count and pass index to Box(Button) widget
GridView.count(
primary: false,
crossAxisCount: 3,
children: List.generate(9, (index) {
return Box(index);
}),
Step 2: You can put Button logic in onPressed and identify each Button with widget.index
class _BoxState extends State<Box> {
...
#override
Widget build(context) {
return MaterialButton(
padding: EdgeInsets.all(0),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
border: new Border.all(color: Colors.blue)),
child: Center(
child: Text(
_board[widget.index].toUpperCase(),
style: TextStyle(
fontSize: 45,
fontWeight: FontWeight.bold,
),
),
)),
onPressed: () {
if (_board[widget.index] == '') {
if (vsBot == false) {
if (currentMoves % 2 == 0)
_board[widget.index] = 'x';
else
_board[widget.index] = 'o';
} else if (!loading) {
loading = true;
_board[widget.index] = 'o';
if (currentMoves >= 8) {
} else
_bestMove(_board);
//print(_board);
}
//print(vsBot);
pressed();
}
});
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';
import 'package:flutter/gestures.dart';
import 'package:url_launcher/url_launcher.dart';
class About extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("About Me"),
),
body: aboutBody);
}
}
Widget get aboutBody {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [const Color(0xFFB3E5FC), const Color(0xFF2196F3)])),
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
RichText(
text: TextSpan(children: [
TextSpan(
text: 'A Simple TicTacToe game made using ',
style: TextStyle(color: Colors.black, fontSize: 20),
),
TextSpan(
text: 'Flutter',
style: TextStyle(color: Colors.blue[900], fontSize: 20),
recognizer: TapGestureRecognizer()
..onTap = () {
launch('https://flutter.dev');
})
]),
),
Container(
height: 150,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text(
'Developed by Krishna S',
style: TextStyle(fontSize: 20),
),
Row(
children: <Widget>[
Icon(Icons.code),
InkWell(
child: Text(
' Github: sbvkrishna',
style: TextStyle(fontSize: 20, color: Colors.blue[900]),
),
onTap: () => {launch('https://github.com/sbvkrishna')},
)
],
),
Row(
children: <Widget>[
Icon(Icons.email),
Text(
' saladibalavijayakrishna#gmail.com',
style: TextStyle(fontSize: 18),
),
],
),
Text(''),
RichText(
text: TextSpan(children: [
TextSpan(
text: 'This Game\'s Source code is available at ',
style: TextStyle(color: Colors.black, fontSize: 20),
),
TextSpan(
text: 'Github',
style: TextStyle(color: Colors.blue[900], fontSize: 20),
recognizer: TapGestureRecognizer()
..onTap = () {
launch(
'https://github.com/sbvkrishna/tictactoe-flutter');
})
]),
),
],
),
)
],
));
}
int currentMoves = 0;
List<String> _board = ['', '', '', '', '', '', '', '', '']; //empty board
String status = '';
String winner = '';
var _gamePageState;
var _turnState;
var _context;
String _turn = 'First Move: X';
bool loading = false;
bool vsBot;
class GamePage extends StatefulWidget {
bool isBot;
GamePage(this.isBot) {
_resetGame();
vsBot = this.isBot;
if (vsBot) _turn = 'First Move: O';
}
#override
_GamePageState createState() => _GamePageState();
}
class _GamePageState extends State<GamePage> {
#override
Widget build(BuildContext context) {
_gamePageState = this;
return Scaffold(
appBar: AppBar(
//leading: Container(width: 0,height: 0,),
title: Text(vsBot ? 'Playing vs Bot' : 'Playing vs Friend'),
actions: <Widget>[
// IconButton(
// icon: Icon(Icons.settings_brightness),
// tooltip: 'Change Theme',
// onPressed: () {
// },
// ),
IconButton(
icon: Icon(Icons.info),
tooltip: 'About',
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return About();
}));
},
),
],
),
body: Container(
decoration: BoxDecoration(color: Colors.blue[200]),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[_BoxContainer(), Status()],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
awaitfn('Reset?', 'Want to reset the current game?', 'Go Back',
'Reset');
});
},
tooltip: 'Restart',
child: Icon(Icons.refresh),
),
);
}
}
class _BoxContainer extends StatelessWidget {
#override
Widget build(BuildContext context) {
_context = context;
return Container(
width: 300,
height: 300,
decoration: BoxDecoration(
color: Colors.white,
border: new Border.all(color: Colors.blue),
boxShadow: [
BoxShadow(
color: Colors.blue[100],
blurRadius: 20.0,
spreadRadius: 5.0,
offset: Offset(7.0, 7.0))
]),
child: Center(
child: GridView.count(
primary: false,
crossAxisCount: 3,
children: List.generate(9, (index) {
return Box(index);
}),
)));
}
}
class Box extends StatefulWidget {
final int index;
Box(this.index);
#override
_BoxState createState() => _BoxState();
}
class _BoxState extends State<Box> {
void pressed() {
print(currentMoves);
setState(() {
currentMoves++;
if (_checkGame()) {
awaitfnn();
} else if (currentMoves >= 9) {
awaitfn('It\'s a Draw', 'Want to try again?', 'Go Back', 'New Game');
}
_turnState.setState(() {
if (currentMoves % 2 == 0)
_turn = 'Turn: O';
else
_turn = 'Turn: X';
_gamePageState.setState(() {});
});
});
}
#override
Widget build(context) {
return MaterialButton(
padding: EdgeInsets.all(0),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
border: new Border.all(color: Colors.blue)),
child: Center(
child: Text(
_board[widget.index].toUpperCase(),
style: TextStyle(
fontSize: 45,
fontWeight: FontWeight.bold,
),
),
)),
onPressed: () {
if (_board[widget.index] == '') {
if (vsBot == false) {
if (currentMoves % 2 == 0)
_board[widget.index] = 'x';
else
_board[widget.index] = 'o';
} else if (!loading) {
loading = true;
_board[widget.index] = 'o';
if (currentMoves >= 8) {
} else
_bestMove(_board);
//print(_board);
}
//print(vsBot);
pressed();
}
});
}
}
class Status extends StatefulWidget {
#override
_StatusState createState() => _StatusState();
}
class _StatusState extends State<Status> {
#override
Widget build(BuildContext context) {
_turnState = this;
return Card(
margin: EdgeInsets.all(40),
child: Container(
width: 220,
height: 60,
padding: EdgeInsets.fromLTRB(20, 10, 20, 10),
child: Text(
_turn,
style: TextStyle(fontSize: 30),
textAlign: TextAlign.center,
),
));
}
}
//-------------------------------------TicTacToe game fns ---------------------------
bool _checkGame() {
for (int i = 0; i < 9; i += 3) {
if (_board[i] != '' &&
_board[i] == _board[i + 1] &&
_board[i + 1] == _board[i + 2]) {
winner = _board[i];
return true;
}
}
for (int i = 0; i < 3; i++) {
if (_board[i] != '' &&
_board[i] == _board[i + 3] &&
_board[i + 3] == _board[i + 6]) {
winner = _board[i];
return true;
}
}
if (_board[0] != '' && (_board[0] == _board[4] && _board[4] == _board[8]) ||
(_board[2] != '' && _board[2] == _board[4] && _board[4] == _board[6])) {
winner = _board[4];
return true;
}
return false;
}
void _resetGame() {
currentMoves = 0;
status = '';
_board = ['', '', '', '', '', '', '', '', ''];
_turn = 'First Move: X';
loading = false;
}
//------------------------------ Alerts Dialog --------------------------------------
void awaitfnn() async {
bool result = await _showAlertBox(
_context, '$winner won!', 'Start a new Game?', 'Exit', 'New Game');
if (result) {
_gamePageState.setState(() {
_resetGame();
});
} else {
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}
}
Future<bool> _showAlertBox(BuildContext context, String title, String content,
String btn1, String btn2) async {
return showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (BuildContext _context) => AlertDialog(
title: Text(title.toUpperCase()),
content: Text(content),
actions: <Widget>[
RaisedButton(
color: Colors.white,
child: Text(btn1),
onPressed: () {
Navigator.of(context).pop(false);
},
),
RaisedButton(
color: Colors.white,
child: Text(btn2),
onPressed: () {
Navigator.of(context).pop(true);
},
)
],
));
}
awaitfn(String title, String content, String btn1, String btn2) async {
bool result = await _showAlertBox(_context, title, content, btn1, btn2);
if (result) {
_gamePageState.setState(() {
_resetGame();
});
}
}
//------------------------------ MIN-MAX ------------------------------------------
int max(int a, int b) {
return a > b ? a : b;
}
int min(int a, int b) {
return a < b ? a : b;
}
String player = 'x', opponent = 'o';
bool isMovesLeft(List<String> _board) {
int i;
for (i = 0; i < 9; i++) {
if (_board[i] == '') return true;
}
return false;
}
int _eval(List<String> _board) {
for (int i = 0; i < 9; i += 3) {
if (_board[i] != '' &&
_board[i] == _board[i + 1] &&
_board[i + 1] == _board[i + 2]) {
winner = _board[i];
return (winner == player) ? 10 : -10;
}
}
for (int i = 0; i < 3; i++) {
if (_board[i] != '' &&
_board[i] == _board[i + 3] &&
_board[i + 3] == _board[i + 6]) {
winner = _board[i];
return (winner == player) ? 10 : -10;
}
}
if (_board[0] != '' && (_board[0] == _board[4] && _board[4] == _board[8]) ||
(_board[2] != '' && _board[2] == _board[4] && _board[4] == _board[6])) {
winner = _board[4];
return (winner == player) ? 10 : -10;
}
return 0;
}
int minmax(List<String> _board, int depth, bool isMax) {
int score = _eval(_board);
//print(score);
int best = 0, i;
if (score == 10 || score == -10) return score;
if (!isMovesLeft(_board)) return 0;
if (isMax) {
best = -1000;
for (i = 0; i < 9; i++) {
if (_board[i] == '') {
_board[i] = player;
best = max(best, minmax(_board, depth + 1, !isMax));
_board[i] = '';
}
}
return best;
} else {
best = 1000;
for (i = 0; i < 9; i++) {
if (_board[i] == '') {
_board[i] = opponent;
best = min(best, minmax(_board, depth + 1, !isMax));
_board[i] = '';
}
}
//print(best);
return best;
}
}
int _bestMove(List<String> _board) {
int bestMove = -1000, moveVal;
int i, bi;
for (i = 0; i < 9; i++) {
if (_board[i] == '') {
moveVal = -1000;
_board[i] = player;
moveVal = minmax(_board, 0, false);
_board[i] = '';
if (moveVal > bestMove) {
bestMove = moveVal;
bi = i;
}
}
}
_board[bi] = player;
_gamePageState.setState(() {});
loading = false;
_turnState.setState(() {
_turn = 'Turn: X';
currentMoves++;
});
return bestMove;
}
//---------------------------------------- API ----------------------------------
// Future gameAPI() async {
// var url = 'http://perfecttictactoe.herokuapp.com/api/v2/play';
// Map data = {
// "player_piece": "o",
// "opponent_piece": "x",
// "board": [
// {"id": "top-left", "value": _board[0]},
// {"id": "top-center", "value": _board[1]},
// {"id": "top-right", "value": _board[2]},
// {"id": "middle-left", "value": _board[3]},
// {"id": "middle-center", "value": _board[4]},
// {"id": "middle-right", "value": _board[5]},
// {"id": "bottom-left", "value": _board[6]},
// {"id": "bottom-center", "value": _board[7]},
// {"id": "bottom-right", "value": _board[8]}
// ]
// };
// var res = await http.post(url, body: json.encode(data));
// if (res.statusCode == 200) {
// var resBody = json.decode(res.body);
// if (resBody['status'] == 'success') {
// var newBoard = resBody['data'];
// if (newBoard['status'] == 'win') {
// winner = newBoard['winner'];
// awaitfnn();
// } else if (newBoard['status'] == 'draw') {
// awaitfn('It"s a Draw', 'Want to try again?', 'Go Back', 'New Game');
// }
// int i = 0;
// newBoard['board'].forEach((box) => {_board[i++] = box['value']});
// }
// _gamePageState.setState(() {});
// loading = false;
// _turnState.setState(() {
// _turn = 'Turn: X';
// currentMoves++;
// });
// }
// }
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("TicTacToe"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.info),
tooltip: 'About',
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return About();
}));
},
),
],
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [const Color(0xFFB3E5FC), const Color(0xFF2196F3)])),
padding: EdgeInsets.all(5),
child: Column(
children: <Widget>[
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.close,
size: 140,
color: Colors.lightBlue[800],
),
Icon(
Icons.radio_button_unchecked,
size: 108,
color: Colors.lightBlue[800],
)
],
),
),
Center(
child: Container(
width: 310,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
RaisedButton(
padding: EdgeInsets.fromLTRB(10, 5, 10, 6),
child: Container(
width: 130,
child: Center(
child: Text(
'vs Bot',
style: TextStyle(
color: Colors.lightBlue[800], fontSize: 30),
),
)),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return GamePage(true);
}));
},
),
RaisedButton(
padding: EdgeInsets.fromLTRB(10, 5, 10, 6),
child: Container(
width: 130,
child: Center(
child: Text(
'vs Friend',
style: TextStyle(
color: Colors.lightBlue[800], fontSize: 30),
),
)),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return GamePage(false);
}));
},
),
],
),
),
)
],
)),
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'TicTacToe',
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
//fontFamily: ''
),
home: HomePage(),
);
}
}

Null check operator used on a null value Carousel Flutter

Good Morning,
I'm trying to put a Carousel on the home page looking for Firebase data, but for some reason, the first time I load the application it appears the message below:
════════ Exception caught by widgets library ═════════════════════════════════════ ══════════════════
The following _CastError was thrown building DotsIndicator (animation: PageController # 734f9 (one client, offset 0.0), dirty, state: _AnimatedState # 636ca):
Null check operator used on a null value
and the screen looks like this:
After giving a hot reload the error continues to appear, but the image is loaded successfully, any tips of what I can do?
HomeManager:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provantagens_app/models/section.dart';
class HomeManager extends ChangeNotifier{
HomeManager({this.images}){
_loadSections();
images = images ?? [];
}
void addSection(Section section){
_editingSections.add(section);
notifyListeners();
}
final List<dynamic> _sections = [];
List<String> images;
List<dynamic> newImages;
List<dynamic> _editingSections = [];
bool editing = false;
bool loading = false;
int index, totalItems;
final Firestore firestore = Firestore.instance;
Future<void> _loadSections() async{
loading = true;
firestore.collection('home').snapshots().listen((snapshot){
_sections.clear();
for(final DocumentSnapshot document in snapshot.documents){
_sections.add( Section.fromDocument(document));
images = List<String>.from(document.data['images'] as List<dynamic>);
}
});
loading = false;
notifyListeners();
}
List<dynamic> get sections {
if(editing)
return _editingSections;
else
return _sections;
}
void enterEditing({Section section}){
editing = true;
_editingSections = _sections.map((s) => s.clone()).toList();
defineIndex(section: section);
notifyListeners();
}
void saveEditing() async{
bool valid = true;
for(final section in _editingSections){
if(!section.valid()) valid = false;
}
if(!valid) return;
loading = true;
notifyListeners();
for(final section in _editingSections){
await section.save();
}
for(final section in List.from(_sections)){
if(!_editingSections.any((s) => s.id == section.id)){
await section.delete();
}
}
loading = false;
editing = false;
notifyListeners();
}
void discardEditing(){
editing = false;
notifyListeners();
}
void removeSection(Section section){
_editingSections.remove(section);
notifyListeners();
}
void onMoveUp(Section section){
int index = _editingSections.indexOf(section);
if(index != 0) {
_editingSections.remove(section);
_editingSections.insert(index - 1, section);
index = _editingSections.indexOf(section);
}
notifyListeners();
}
HomeManager clone(){
return HomeManager(
images: List.from(images),
);
}
void onMoveDown(Section section){
index = _editingSections.indexOf(section);
totalItems = _editingSections.length;
if(index < totalItems - 1){
_editingSections.remove(section);
_editingSections.insert(index + 1, section);
index = _editingSections.indexOf(section);
}else{
}
notifyListeners();
}
void defineIndex({Section section}){
index = _editingSections.indexOf(section);
totalItems = _editingSections.length;
notifyListeners();
}
}
HomeScreen:
import 'package:carousel_pro/carousel_pro.dart';
import 'package:flutter/material.dart';
import 'package:provantagens_app/commom/custom_drawer.dart';
import 'package:provantagens_app/commom/custom_icons_icons.dart';
import 'package:provantagens_app/models/home_manager.dart';
import 'package:provantagens_app/models/section.dart';
import 'package:provantagens_app/screens/home/components/home_carousel.dart';
import 'package:provantagens_app/screens/home/components/menu_icon_tile.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
// ignore: must_be_immutable
class HomeScreen extends StatelessWidget {
HomeManager homeManager;
Section section;
List<Widget> get children => null;
String videoUrl = 'https://www.youtube.com/watch?v=VFnDo3JUzjs';
int index;
var _tapPosition;
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: const [
Colors.white,
Colors.white,
], begin: Alignment.topCenter, end: Alignment.bottomCenter)),
child: Scaffold(
backgroundColor: Colors.transparent,
drawer: CustomDrawer(),
appBar: AppBar(
backgroundColor: Colors.transparent,
iconTheme: IconThemeData(color: Colors.black),
title: Text('Página inicial', style: TextStyle(color: Color.fromARGB(255, 30, 158, 8))),
centerTitle: true,
actions: <Widget>[
Divider(),
],
),
body: Consumer<HomeManager>(
builder: (_, homeManager, __){
return ListView(children: <Widget>[
AspectRatio(
aspectRatio: 1,
child:HomeCarousel(homeManager),
),
Column(
children: <Widget>[
Container(
height: 50,
),
Divider(
color: Colors.black,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.only(left:12.0),
child: MenuIconTile(title: 'Parceiros', iconData: Icons.apartment, page: 1,),
),
Padding(
padding: const EdgeInsets.only(left:7.0),
child: MenuIconTile(title: 'Beneficios', iconData: Icons.card_giftcard, page: 2,),
),
Padding(
padding: const EdgeInsets.only(right:3.0),
child: MenuIconTile(title: 'Suporte', iconData: Icons.help_outline, page: 6,),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MenuIconTile(iconData: Icons.assignment,
title: 'Dados pessoais',
page: 3)
,
MenuIconTile(iconData: Icons.credit_card_outlined,
title: 'Meu cartão',
page: 4)
,
MenuIconTile(iconData: Icons.account_balance_wallet_outlined,
title: 'Pagamento',
page: 5,)
,
],
),
Divider(
color: Colors.black,
),
Container(
height: 50,
),
Consumer<HomeManager>(
builder: (_, sec, __){
return RaisedButton(
child: Text('Teste'),
onPressed: (){
Navigator.of(context)
.pushReplacementNamed('/teste',
arguments: sec);
},
);
},
),
Text('Saiba onde usar o seu', style: TextStyle(color: Colors.black, fontSize: 20),),
Text('Cartão Pró Vantagens', style: TextStyle(color: Color.fromARGB(255, 30, 158, 8), fontSize: 30),),
AspectRatio(
aspectRatio: 1,
child: Image.network(
'https://static.wixstatic.com/media/d170e1_80b5f6510f5841c19046f1ed5bca71e4~mv2.png/v1/fill/w_745,h_595,al_c,q_90,usm_0.66_1.00_0.01/Arte_Cart%C3%83%C2%B5es.webp',
fit: BoxFit.fill,
),
),
Divider(),
Container(
height: 150,
child: Row(
children: [
AspectRatio(
aspectRatio: 1,
child: Image.network(
'https://static.wixstatic.com/media/d170e1_486dd638987b4ef48d12a4bafee20e80~mv2.png/v1/fill/w_684,h_547,al_c,q_90,usm_0.66_1.00_0.01/Arte_Cart%C3%83%C2%B5es_2.webp',
fit: BoxFit.fill,
),
),
Padding(
padding: const EdgeInsets.only(left:20.0),
child: RichText(
text: TextSpan(children: <TextSpan>[
TextSpan(
text: 'Adquira já o seu',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
TextSpan(
text: '\n\CARTÃO PRÓ VANTAGENS',
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 30, 158, 8)),
),
]),
),
),
],
),
),
Divider(),
tableBeneficios(),
Divider(),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
'O cartão Pró-Vantagens é sediado na cidade de Hortolândia/SP e já está no mercado há mais de 3 anos. Somos um time de profissionais apaixonados por gestão de benefícios e empenhados em gerar o máximo de valor para os conveniados.'),
FlatButton(
onPressed: () {
launch(
'https://www.youtube.com/watch?v=VFnDo3JUzjs');
},
child: Text('SAIBA MAIS')),
],
),
),
Container(
color: Color.fromARGB(255, 105, 190, 90),
child: Column(
children: <Widget>[
Row(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
'© 2020 todos os direitos reservados a Cartão Pró Vantagens.',
style: TextStyle(fontSize: 10),
),
)
],
),
Divider(),
Row(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Rua Luís Camilo de Camargo, 175 -\n\Centro, Hortolândia (piso superior)',
style: TextStyle(fontSize: 10),
),
),
Padding(
padding: const EdgeInsets.only(left: 16),
child: IconButton(
icon: Icon(CustomIcons.facebook),
color: Colors.black,
onPressed: () {
launch(
'https://www.facebook.com/provantagens/');
},
),
),
Padding(
padding: const EdgeInsets.only(left: 16),
child: IconButton(
icon: Icon(CustomIcons.instagram),
color: Colors.black,
onPressed: () {
launch(
'https://www.instagram.com/cartaoprovantagens/');
},
),
),
],
),
],
),
)
],
),
]);
},
)
),
);
}
tableBeneficios() {
return Table(
defaultColumnWidth: FlexColumnWidth(120.0),
border: TableBorder(
horizontalInside: BorderSide(
color: Colors.black,
style: BorderStyle.solid,
width: 1.0,
),
verticalInside: BorderSide(
color: Colors.black,
style: BorderStyle.solid,
width: 1.0,
),
),
children: [
_criarTituloTable(",Plus, Premium"),
_criarLinhaTable("Seguro de vida\n\(Morte Acidental),X,X"),
_criarLinhaTable("Seguro de Vida\n\(Qualquer natureza),,X"),
_criarLinhaTable("Invalidez Total e Parcial,X,X"),
_criarLinhaTable("Assistência Residencial,X,X"),
_criarLinhaTable("Assistência Funeral,X,X"),
_criarLinhaTable("Assistência Pet,X,X"),
_criarLinhaTable("Assistência Natalidade,X,X"),
_criarLinhaTable("Assistência Eletroassist,X,X"),
_criarLinhaTable("Assistência Alimentação,X,X"),
_criarLinhaTable("Descontos em Parceiros,X,X"),
],
);
}
_criarLinhaTable(String listaNomes) {
return TableRow(
children: listaNomes.split(',').map((name) {
return Container(
alignment: Alignment.center,
child: RichText(
text: TextSpan(children: <TextSpan>[
TextSpan(
text: name != "X" ? '' : 'X',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
TextSpan(
text: name != 'X' ? name : '',
style: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 30, 158, 8)),
),
]),
),
padding: EdgeInsets.all(8.0),
);
}).toList(),
);
}
_criarTituloTable(String listaNomes) {
return TableRow(
children: listaNomes.split(',').map((name) {
return Container(
alignment: Alignment.center,
child: RichText(
text: TextSpan(children: <TextSpan>[
TextSpan(
text: name == "" ? '' : 'Plano ',
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
TextSpan(
text: name,
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 30, 158, 8)),
),
]),
),
padding: EdgeInsets.all(8.0),
);
}).toList(),
);
}
void _storePosition(TapDownDetails details) {
_tapPosition = details.globalPosition;
}
}
I forked the library to create a custom carousel for my company's project, and since we updated flutter to 2.x we had the same problem.
To fix this just update boolean expressions like
if(carouselState.pageController.position.minScrollExtent == null ||
carouselState.pageController.position.maxScrollExtent == null){ ... }
to
if(!carouselState.pageController.position.hasContentDimensions){ ... }
Here is flutter's github reference.
This worked for me
So I edited scrollposition.dart package
from line 133
#override
//double get minScrollExtent => _minScrollExtent!;
// double? _minScrollExtent;
double get minScrollExtent {
if (_minScrollExtent == null) {
_minScrollExtent = 0.0;
}
return double.parse(_minScrollExtent.toString());
}
double? _minScrollExtent;
#override
// double get maxScrollExtent => _maxScrollExtent!;
// double? _maxScrollExtent;
double get maxScrollExtent {
if (_maxScrollExtent == null) {
_maxScrollExtent = 0.0;
}
return double.parse(_maxScrollExtent.toString());
}
double? _maxScrollExtent;
Just upgrade to ^3.0.0 Check here https://pub.dev/packages/carousel_slider
I faced the same issue.
This is how I solved it
class PlansPage extends StatefulWidget {
const PlansPage({Key? key}) : super(key: key);
#override
State<PlansPage> createState() => _PlansPageState();
}
class _PlansPageState extends State<PlansPage> {
int _currentPage = 1;
late CarouselController carouselController;
#override
void initState() {
super.initState();
carouselController = CarouselController();
}
}
Then put initialization the carouselController inside the initState method I was able to use the methods jumpToPage(_currentPage ) and animateToPage(_currentPage) etc.
I use animateToPage inside GestureDetector in onTap.
onTap: () {
setState(() {
_currentPage = pageIndex;
});
carouselController.animateToPage(_currentPage);
},
I apologize in advance if this is inappropriate.
I solved the similar problem as follows. You can take advantage of the Boolean variable. I hope, help you.
child: !loading ? HomeCarousel(homeManager) : Center(child:ProgressIndicator()),
or
child: isLoading ? HomeCarousel(homeManager) : SplashScreen(),
class SplashScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Loading...')
),
);
}
}

Flutter: The Method was called on null

I want to run my App,but im getting this error after I tried to reload
it. But i keep getting this error. I tried to remove some code that i
edited befor it stopped working but i keep getting this error. This Code is from the following link and i changed it a bit. "https://pub.dev/packages/flutter_circular_slider" Cansomeone help me?
error Message
My Error Message in the Terminal:
The method '>' was called on null. Receiver: null Tried calling: >(null)
class Register extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: RegisterLayout(),
);
}
}
class RegisterLayout extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: RegisterPage(),
),
);
}
}
class RegisterPage extends StatefulWidget {
#override
_RegisterThirdPageState createState() => _RegisterThirdPageState();
}
class _RegisterThirdPageState extends State<RegisterPage> {
int initTime;
int endTime;
int inBedTime;
int outBedTime;
String startReading;
String endReading;
String overTime;
#override
void _updateLabels(int init, int end, int all) {
setState(() {
inBedTime = init;
outBedTime = end;
});
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(height: 20),
Text(
'How long did you want to read',
style: TextStyle(color: Colors.green[600], fontSize: 22),
),
DoubleCircularSlider(
288,
initTime,
endTime,
height: 300.0,
width: 300.0,
primarySectors: 4,
baseColor: Colors.grey[200],
selectionColor: Colors.green[400],
handlerColor: Colors.green[600],
handlerOutterRadius: 20.0,
onSelectionChange: _updateLabels,
sliderStrokeWidth: 40.0,
child: Padding(
padding: const EdgeInsets.all(42.0),
child: Center(
child: Text('${_formatIntervalTime(inBedTime, outBedTime)}',
style: TextStyle(fontSize: 40.0, color: Colors.green[600]))),
),
),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
_formatBedTime('START', inBedTime),
_formatBedTime('END', outBedTime),
]),
FlatButton(
padding: EdgeInsets.only(top: 10, bottom: 10, left: 100, right: 100),
child: Text('N E X T', style: TextStyle(fontSize: 30),),
color: Colors.green[400],
textColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50.0),
),
onPressed: () {
startReading = _formatTime(inBedTime);
endReading = _formatTime(outBedTime);
overTime = _formatIntervalTime(inBedTime, outBedTime);
print(startReading);
print(endReading);
print(overTime);
},
),
],
);
}
Widget _formatBedTime(String pre, int time) {
return Column(
children: [
Text(pre, style: TextStyle(color: Colors.green[400], fontSize: 16)),
Text('READING AT', style: TextStyle(color: Colors.green[400], fontSize: 16)),
Text(
'${_formatTime(time)}',
style: TextStyle(color: Colors.green[600], fontSize: 24),
)
],
);
}
String _formatTime(int time) {
if (time == 0 || time == null) {
return '00:00';
}
var hours = time ~/ 12;
var minutes = (time % 12) * 5;
return '$hours:$minutes';
}
String _formatIntervalTime(int init, int end) {
var sleepTime = end > init ? end - init : 288 - init + end;
var hours = sleepTime ~/ 12;
var minutes = (sleepTime % 12) * 5;
return '${hours}h${minutes}m';
}
int _generateRandomTime() => Random().nextInt(288);
}