I'm creating a quiz app using Flutter & Firebase. When the quiz starts, I can click the correct answer and it will show the correct and incorrect answers, but I can't click on incorrect options. When I try to press the incorrect options, nothing happens.
Is there anything wrong in this code that is making it happen?
import 'package:flutter/material.dart';
import '../constants.dart';
import '../models/question_model.dart';
import '../widgets/question_widget.dart';
import '../widgets/next_button.dart';
import '../widgets/option_card.dart';
import '../widgets/result_box.dart';
import '../models/db_connect.dart';
class TestScreen extends StatefulWidget {
const TestScreen({Key? key}) : super(key: key);
#override
State<TestScreen> createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
var db = DBconnect();
late Future _questions;
Future<List<Question>> getData() async {
return db.fetchQuestions();
}
#override
void initState() {
_questions = getData();
super.initState();
}
int index = 0;
int score = 0;
bool isPressed = false;
bool isAlreadySelected = false;
void nextQuestion(int questionLength) {
if (index == questionLength - 1 || score == 12) {
showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => ResultBox(
result: score,
questionLength: questionLength,
));
} else {
if (isPressed) {
setState(() {
index++;
isPressed = false;
isAlreadySelected = false;
});
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('Please select any option'),
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.symmetric(vertical: 20.0),
));
}
}
}
void checkAnswerAndUpdate(bool value) {
if (isAlreadySelected) {
return;
} else {
if (value == true) {
score++;
setState(() {
isPressed = true;
isAlreadySelected = false;
});
}
}
}
void startOver() {
setState(() {
Text('You have already attempted the LL Test');
});
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _questions as Future<List<Question>>,
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Center(
child: Text('${snapshot.error}'),
);
} else if (snapshot.hasData) {
var extractedData = snapshot.data as List<Question>;
return Scaffold(
backgroundColor: background,
appBar: AppBar(
title: const Text('LL Test'),
backgroundColor: background,
shadowColor: Colors.transparent,
actions: [
Padding(
padding: const EdgeInsets.all(18.0),
child: Text(
'Score: $score',
style: TextStyle(fontSize: 18.0),
),
)
],
),
body: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
children: [
QuestionWidget(
question: extractedData[index].title,
indexAction: index,
totalQuestions: extractedData.length,
),
const Divider(
color: neutral,
),
const SizedBox(height: 25.0),
for (int i = 0;
i < extractedData[index].options.length;
i++)
GestureDetector(
onTap: () => checkAnswerAndUpdate(
extractedData[index].options.values.toList()[i]),
child: OptionCard(
option: extractedData[index].options.keys.toList()[i],
color: isPressed
? extractedData[index]
.options
.values
.toList()[i] ==
true
? correct
: incorrect
: neutral,
),
),
],
),
),
floatingActionButton: GestureDetector(
onTap: () => nextQuestion(extractedData.length),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: NextButton(),
),
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
);
}
} else {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 20.0),
Text('Please Wait While Questions Are Loading..'),
],
),
);
}
return const Center(
child: Text('NoData'),
);
},
);
}
}
You never set isPressed to true when you select the wrong (false) answer.
Your checkAnswerAndUpdate should probably always call the setState method, even if the answer was wrong.
Related
I need to generate an N amount of DropDownButtons fetched from FireBase when I click on a button for example. I have this code where I generate the N amount but the variables are the same for each one of them, the ideal would be to store each DropDownButton with an independent variable.
import 'package:flutter/material.dart';
class Formulario2Screen extends StatefulWidget {
const Formulario2Screen({Key? key}) : super(key: key);
#override
State<Formulario2Screen> createState() => _Formulario2ScreenState();
}
List<Widget> bodyElements = [];
int num = 0;
var selecCurrency, eleccion, selecCurrency2, eleccion2;
class _Formulario2ScreenState extends State<Formulario2Screen> {
bool pressed = false;
void addBodyElement() {
bodyElements.add(
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('Sintomas').snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return const Text('Loading');
} else {
List<DropdownMenuItem> currencyItems = [];
for (int i = 0; i < snapshot.data.docs.length; i++) {
DocumentSnapshot snap = snapshot.data.docs[i];
currencyItems.add(
DropdownMenuItem(
child: Text(
snap.id,
),
value: snap.id,
),
);
eleccion = currencyItems;
}
}
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DropdownButton<dynamic>(
items: eleccion,
onChanged: (currencyValue) {
var snackBar =
SnackBar(content: Text('Se seleccionó $currencyValue'));
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(snackBar);
setState(() {
selecCurrency = currencyValue;
});
},
value: selecCurrency,
hint: const Text('Selecciona un sintoma')),
],
);
},
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Center(child: Text('Home')),
brightness: Brightness.dark,
leading: IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
setState(() {
bodyElements.clear();
num = 0;
});
},
),
),
body: ListView(
children: <Widget>[
Column(
children: bodyElements,
),
],
),
floatingActionButton: FloatingActionButton.extended(
icon: Icon(Icons.add),
label: Text('Add'),
onPressed: () {
num++;
setState(() {
addBodyElement();
});
},
),
);
}
}
Basically what I do is every time I press the Add button, the previously created widget is added.
I'm creating a quiz app using Flutter & Firebase. I've added 150 questions in Firebase Realtime Database and have fetched all the questions in the Quiz App. But the app shows all the added questions in the same order. I want to generate 20 random questions every time from the questions I've added in Firebase Realtime Database.
How can I make it happen?
Code:
import 'package:flutter/material.dart';
import '../constants.dart';
import '../models/question_model.dart';
import '../widgets/question_widget.dart';
import '../widgets/next_button.dart';
import '../widgets/option_card.dart';
import '../widgets/result_box.dart';
import '../models/db_connect.dart';
class TestScreen extends StatefulWidget {
const TestScreen({Key? key}) : super(key: key);
#override
State<TestScreen> createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
var db = DBconnect();
late Future _questions;
Future<List<Question>> getData() async {
return db.fetchQuestions();
}
#override
void initState() {
_questions = getData();
super.initState();
}
int index = 0;
int score = 0;
bool isPressed = false;
bool isAlreadySelected = false;
void nextQuestion(int questionLength) {
if (index == questionLength - 1 || score == 12) {
showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => ResultBox(
result: score,
questionLength: questionLength,
));
} else {
if (isPressed) {
setState(() {
index++;
isPressed = false;
isAlreadySelected = false;
});
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('Please select any option'),
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.symmetric(vertical: 20.0),
));
}
}
}
void checkAnswerAndUpdate(bool value) {
if (isAlreadySelected) {
return;
} else {
if (value == true) {
score++;
setState(() {
isPressed = true;
isAlreadySelected = false;
});
} else if (value == false) {
setState(() {
isPressed = true;
isAlreadySelected = false;
});
}
}
}
void startOver() {
setState(() {
Text('You have already attempted the LL Test');
});
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _questions as Future<List<Question>>,
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Center(
child: Text('${snapshot.error}'),
);
} else if (snapshot.hasData) {
var extractedData = snapshot.data as List<Question>;
return Scaffold(
backgroundColor: background,
appBar: AppBar(
title: const Text('LL Test'),
backgroundColor: background,
shadowColor: Colors.transparent,
actions: [
Padding(
padding: const EdgeInsets.all(18.0),
child: Text(
'Score: $score',
style: TextStyle(fontSize: 18.0),
),
)
],
),
body: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
children: [
QuestionWidget(
question: extractedData[index].title,
indexAction: index,
totalQuestions: extractedData.length,
),
const Divider(
color: neutral,
),
const SizedBox(height: 25.0),
for (int i = 0;
i < extractedData[index].options.length;
i++)
GestureDetector(
onTap: () => checkAnswerAndUpdate(
extractedData[index].options.values.toList()[i]),
child: OptionCard(
option: extractedData[index].options.keys.toList()[i],
color: isPressed
? extractedData[index]
.options
.values
.toList()[i] ==
true
? correct
: incorrect
: neutral,
),
),
],
),
),
floatingActionButton: GestureDetector(
onTap: () => nextQuestion(extractedData.length),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: NextButton(),
),
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
);
}
} else {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 20.0),
Text('Please Wait While Questions Are Loading..'),
],
),
);
}
return const Center(
child: Text('NoData'),
);
},
);
}
}
Try use Shuffle for sort random item in list
List<String> newList = [];
List<String> countries = ["USA", "United Kingdom","China", "Russia", "Brazil"];
countries.shuffle();
for(var i == 0; i <= 20; i++){
newList.add(countries[i])
}
print(newList);
// [United Kingdom, Brazil, Russia, China, USA]
//the output will be in different order every time this code is executed.
So I am going to create a quiz app but while I run it, I get the following error
'pakage:flutter/src/wigets/text.dart': Field assertion : line 298 pos
10 :'data !=null': A non-null String must be provided to a Text widget
it is a flutter based code, what are the possible thing i missed ? Can you help me please ! i shall be obliged for this :)
Here Is image of Error
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:crazyquiz/resultpage.dart';
class getjson extends StatelessWidget {
String langname;
getjson(this.langname);
String assettoload;
setasset() {
if (langname == "Funny") {
assettoload = "assets/funny.json";
} else if (langname == "General Knowledge") {
assettoload = "assets/gk.json";
} else if (langname == "Javascript") {
assettoload = "assets/js.json";
} else if (langname == "C++") {
assettoload = "assets/cpp.json";
} else {
assettoload = "assets/linux.json";
}
}
#override
Widget build(BuildContext context) {
setasset();
return FutureBuilder(
future:
DefaultAssetBundle.of(context).loadString(assettoload, cache: true),
builder: (context, snapshot) {
List mydata = json.decode(snapshot.data.toString());
if (mydata == null) {
return Scaffold(
body: Center(
child: Text(
"Loading",
),
),
);
} else {
return quizpage(mydata: mydata);
}
},
);
}
}
class quizpage extends StatefulWidget {
var mydata;
quizpage({Key key, this.mydata = ""}) : super(key: key);
#override
_quizpageState createState() => _quizpageState(mydata);
}
class _quizpageState extends State<quizpage> {
var mydata;
_quizpageState(this.mydata);
Color colortoshow = Colors.indigoAccent;
Color right = Colors.green;
Color wrong = Colors.red;
int marks = 0;
int i = 1;
int j = 1;
int timer = 30;
String showtimer = "30";
var random_array;
Map<String, Color> btncolor = {
"a": Colors.purple,
"b": Colors.lightBlueAccent,
"c": Colors.blueGrey,
"d": Colors.blueAccent,
};
bool canceltimer = false;
genrandomarray() {
var distinctIds = [];
var rand = new Random();
for (int i = 0;;) {
distinctIds.add(rand.nextInt(10));
random_array = distinctIds.toSet().toList();
if (random_array.length < 10) {
continue;
} else {
break;
}
}
print(random_array);
}
#override
void initState() {
starttimer();
genrandomarray();
super.initState();
}
#override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
void starttimer() async {
const onesec = Duration(seconds: 1);
Timer.periodic(onesec, (Timer t) {
setState(() {
if (timer < 1) {
t.cancel();
nextquestion();
} else if (canceltimer == true) {
t.cancel();
} else {
timer = timer - 1;
}
showtimer = timer.toString();
});
});
}
void nextquestion() {
timer = 30;
setState(() {
if (j < 10) {
i = random_array[j];
j++;
} else {
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => resultpage(marks: marks),
));
}
btncolor["a"] = Colors.purple;
btncolor["b"] = Colors.lightBlueAccent;
btncolor["c"] = Colors.blueGrey;
btncolor["d"] = Colors.blueAccent;
});
starttimer();
}
void checkanswer(String k) {
if (mydata[2][i.toString()] == mydata[1][i.toString()][k]) {
[i.toString()][k]);
marks = marks + 5;
colortoshow = right;
} else {
}
setState(() {
btncolor[k] = colortoshow;
canceltimer = true;
});
Timer(Duration(seconds: 1), nextquestion);
}
Widget choicebutton(String k) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: 10.0,
horizontal: 20.0,
),
child: MaterialButton(
onPressed: () => checkanswer(k),
child: Text(
mydata[1][i.toString()][k],
style: TextStyle(
color: Colors.white,
fontFamily: "Alike",
fontSize: 16.0,
),
maxLines: 1,
),
color: btncolor[k],
splashColor: Colors.indigo[700],
highlightColor: Colors.indigo[700],
minWidth: 200.0,
height: 45.0,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
),
);
}
#override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitDown, DeviceOrientation.portraitUp]);
return WillPopScope(
onWillPop: () {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(
"Quizstar",
),
content: Text("You Can't Go Back At This Stage."),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Ok',
),
)
],
));
},
child: Scaffold(
body: Column(
children: <Widget>[
Expanded(
flex: 3,
child: Container(
padding: EdgeInsets.all(15.0),
alignment: Alignment.bottomLeft,
child: Text(
mydata[0][i.toString()],
style: TextStyle(
fontSize: 16.0,
fontFamily: "Quando",
),
),
),
),
Expanded(
flex: 6,
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
choicebutton('a'),
choicebutton('b'),
choicebutton('c'),
choicebutton('d'),
],
),
),
),
Expanded(
flex: 1,
child: Container(
alignment: Alignment.topCenter,
child: Center(
child: Text(
showtimer.toString(),
style: TextStyle(
fontSize: 35.0,
fontWeight: FontWeight.w700,
fontFamily: 'Times New Roman',
),
),
),
),
),
],
),
),
);
}
}`
I think Yalin is perfectly right, but I would add:
Dart has some very useful operators to prevent that kind of errors, which can be catastrofic on real world usage. You can check them here. In particular you could use
mydata[0][i.toString()] ?? "Default text"
to prevent this kind of problem when an object is null
Not a flutter guy. But I'll try to help you.
If you look into flutter source and search your error message you'll get a clue what to do.
So at text.dart we can find that dart check that you fill data field with a String when you call a constructor.
So my bet is that you misplace toString here mydata[0][i.toString()]
Text widget needs a string to be initialized. As vsenik mentioned, you are probably giving a null instead of string to a text widget. The problem is in one of the below lines.
mydata[1][i.toString()][k]
mydata[0][i.toString()]
You could use debug or insert a print statement before these lines.
Hi I have a screen for adding images. I use the multi_image_picker for picking the images. Upon selection of the images, all images picked will be shown in a grid view with a FloatingActionButton on top of each image for removal of each image. The problem is, when I click the button for removing an image, the last image is removed not the one I clicked on. Has anyone experienced this?
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:krakatoa/api/FileApi.dart';
import 'package:krakatoa/components/AssetView.dart';
import 'package:krakatoa/config/Themes.dart' as themes;
import 'package:krakatoa/mixins/SnackBarMixin.dart';
import 'package:krakatoa/podos/User.dart';
import 'package:krakatoa/utils/Common.dart' as common;
import 'package:multi_image_picker/multi_image_picker.dart';
class AddImageScreen extends StatefulWidget {
final List<String> currentImageUrls;
final String postUrl;
AddImageScreen({this.currentImageUrls, #required this.postUrl});
#override
State<StatefulWidget> createState() => _AddImageState();
}
class _AddImageState extends State<AddImageScreen> with SnackBarMixin {
List<Asset> _images = [];
User _user;
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Text('Add images'),
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: ListView(
children: <Widget>[
Container(
padding: EdgeInsets.only(bottom: 10),
child: Text(
"Upload Images:",
style: Theme.of(context).textTheme.headline,
),
),
Container(
padding: EdgeInsets.only(bottom: 10),
child: GridView.count(
shrinkWrap: true,
crossAxisSpacing: 3,
mainAxisSpacing: 3,
crossAxisCount: 3,
children: _renderPickedImages(),
),
),
Container(
child: FlatButton(
child: Text('UPLOAD'),
padding: EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
color: themes.primaryColor,
onPressed: _onUploadBtnPress,
),
),
],
),
),
);
}
#override
void dispose() {
super.dispose();
for (var image in _images) {
image.release();
}
}
#override
void initState() {
super.initState();
common.getCurrentUser().then((user) {
if (user != null) {
_user = user;
} else {
_user = User();
}
});
}
void showProgressAlert() {
showDialog(
context: context,
builder: (context) {
return WillPopScope(
onWillPop: () async => false,
child: AlertDialog(
content: ListTile(
leading: CircularProgressIndicator(
value: null,
),
title: Text('Processing...'),
),
),
);
},
barrierDismissible: false,
);
}
void _onAddImageBtnPress() async {
List<Asset> resultList;
try {
resultList = await MultiImagePicker.pickImages(maxImages: 5, enableCamera: true);
} on PlatformException catch (e) {
debugPrint("AddImageScreen._onAddImageBtnPress: ${e.toString()}");
}
if (!mounted) return;
if (resultList.isNotEmpty) {
setState(() {
_images.addAll(resultList);
});
}
}
void _onUploadBtnPress() {
if (_images.isNotEmpty) {
showProgressAlert();
_uploadImages();
} else {
showSnackBarMessage("No images to upload", seconds: 5);
}
}
void _removeImage(int index, Asset image) {
debugPrint("INDEX: $index");
debugPrint("Orig Image: ${_images[index].hashCode}");
setState(() {
_images.removeAt(index);
});
}
List<Widget> _renderPickedImages() {
List<Widget> imageWidgets = [];
imageWidgets.add(InkWell(
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey,
),
),
child: Center(
child: Icon(
Icons.add,
size: 60,
color: Colors.grey,
),
),
),
onTap: _onAddImageBtnPress,
));
var ctr = 0;
for (var image in _images) {
imageWidgets.add(Container(
child: Stack(
fit: StackFit.expand,
overflow: Overflow.visible,
children: <Widget>[
AssetView(image),
Positioned(
bottom: 0,
right: 0,
child: _ImageRemoveButton(
index: ctr,
removeItem: _removeImage,
image: image,
),
),
],
),
));
ctr++;
}
return imageWidgets;
}
Future<void> _uploadImages() async {
if (_user.id <= 0) {
showSnackBarMessage("User is not logged in");
Navigator.of(context).pop("User is not logged in");
return;
}
try {
await FileApi.uploadImages(widget.postUrl, _images);
Navigator.of(context).pop();
Navigator.of(context).pop("Success");
} on Exception catch (e) {
debugPrint(e.toString());
showSnackBarMessage(e.toString());
Navigator.of(context).pop(e.toString());
}
}
}
class _ImageRemoveButton extends StatelessWidget {
final Function removeItem;
final Asset image;
final int index;
_ImageRemoveButton({this.removeItem, this.index, this.image});
#override
Widget build(BuildContext context) {
return FloatingActionButton(
backgroundColor: Colors.white,
mini: true,
heroTag: "ImageAction_$index",
isExtended: false,
child: Icon(
Icons.close,
size: 15,
color: Colors.black,
),
onPressed: _onPress,
);
}
void _onPress() {
debugPrint("${this.hashCode}");
debugPrint("Passed Image: ${image.hashCode}");
removeItem(index, image);
}
}
add key: Key(YOUR_LIST.length.toString()),
having KEY helps with the update of the ListView
that worked for me on ListView.builder
Can anyone post a sample flutter code to enable a button when the scrollbar is at the bottom of the list?
I used NotificationListener and NotificationListener but event is not firing when scrollbar is at the bottom of the list.
I want to enable the button only when the scrollbar is at the bottom of the list else it should be disabled.
Below is the code that I am using.
class _TermsAndCondnsPage extends State<TermsAndCondnsPage> {
bool _isButtonEnabled = false;
bool _scrollingStarted() {
setState(() => _isButtonEnabled = false);
return false;
}
bool _scrollingEnded() {
setState(() => _isButtonEnabled = true);
return true;
}
ScrollController scrollcontroller;
#override
Widget build(BuildContext context) {
var acceptAndContinueButtonPressed;
if (_isButtonEnabled) {
acceptAndContinueButtonPressed = () {};
} else {
acceptAndContinueButtonPressed == null;
}
return new Scaffold(
appBar: new AppBar(
automaticallyImplyLeading: false,
title: new Text('Terms And Conditions', textAlign: TextAlign.center),
),
body:
NotificationListener<ScrollStartNotification>(
onNotification: (_) => _scrollingStarted(),
child: NotificationListener<ScrollEndNotification>(
onNotification: (_) => _scrollingEnded(),
child: new MaterialApp(
home: new Scaffold(
body: new SingleChildScrollView(
controller: scrollcontroller,
child: new Column(
children: <Widget>[
new Center(
child: new HtmlView(
data: html,
),
),
],
),
),
persistentFooterButtons: <Widget>[
new RaisedButton(
color: Colors.blue,
onPressed: _isButtonEnabled ? () {} : null,
child: new Text(
'Accept and Continue',
style: new TextStyle(
color: Colors.white,
),
)),
],
),
),
)),
);
}
String html = '''
<p>Sample HTML</p>
''';
}
Here you have a basic example, enable the button when reach the bottom, and disable when reach the top.
class ExampleState extends State<Example> {
final list = List.generate((40), (val) => "val $val");
final ScrollController _controller = new ScrollController();
var reachEnd = false;
_listener() {
final maxScroll = _controller.position.maxScrollExtent;
final minScroll = _controller.position.minScrollExtent;
if (_controller.offset >= maxScroll) {
setState(() {
reachEnd = true;
});
}
if (_controller.offset <= minScroll) {
setState(() {
reachEnd = false;
});
}
}
#override
void initState() {
_controller.addListener(_listener);
super.initState();
}
#override
void dispose() {
_controller.removeListener(_listener);
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(children: [
Positioned(
top: 0.0,
bottom: 50.0,
width: MediaQuery.of(context).size.width,
child: ListView.builder(
controller: _controller,
itemBuilder: (context, index) {
return ListTile(
title: Text(list[index]),
);
},
itemCount: list.length,
),
),
Align(
alignment: Alignment.bottomCenter,
child: RaisedButton(
child: Text("button"),
color: Colors.blue,
onPressed: reachEnd ? () => null : null,
))
]);
}
}