i think the on Pressed function in elevated button is null but i dont understand why
my main file where i am using List and Map to create and switch questions and answers
answers are on the buttons and they are printed on them but they are greyed out
import './quiz.dart';
import './result.dart';
void main() => runApp(TestApp());
#override
class TestApp extends StatefulWidget {
const TestApp({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _TestAppState();
}
}
class _TestAppState extends State<TestApp> {
var _i = 0;
final _question = const [
{
'q1': 'whats the capital of India',
'a1': ['Delhi', 'Mumbai', 'Chennai', 'Bangalore'],
},
{
'q1': 'whats the Language of India',
'a1': ['Sanskrit', 'Bengali', 'Hindi', 'Kannada'],
},
{
'q1': 'whats the continent India is located in',
'a1': ['Africa', 'Asia', 'America', 'Australia'],
},
{
'q1': 'whats second most spoken language in India',
'a1': ['Hindi', 'Gujarati', 'Marathi', 'English'],
},
];
_answeredQ() {
setState(() {
_i = _i + 1;
});
// return 0;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text("Test App!"),
),
body: _i < _question.length
? Quiz(qMap: _question, aFunction: _answeredQ(), index: _i)
: Result(),
),
);
}
}
**here's my Quiz class using as a custom widget
import './questionText.dart';
import './answer.dart';
class Quiz extends StatelessWidget {
final List<Map<String, Object>> qMap;
final aFunction;
final int index;
Quiz({required this.qMap, required this.aFunction, required this.index});
#override
Widget build(BuildContext context) {
return Column(
children: [
Question(
qMap[index]['q1'],
),
...(qMap[index]['a1'] as List<String>).map((ans) {
return AnswerW(aFunction, ans);
}).toList()
],
);
}
}
and here's the button custom widget class
class AnswerW extends StatelessWidget {
final selAns;
final String answerText;
AnswerW( this.selAns, this.answerText);
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: EdgeInsets.all(10),
child: ElevatedButton(onPressed: selAns,
child: Text(answerText),
),
);
}
}
In ? Quiz(qMap: _question, aFunction: _answeredQ(), index: _i) You are passing the return value of _answeredQ(), not the actual function itself. You can change this to just _answeredQ (without the "()") or aFunction: () => _answeredQ()
FWIW It's good in dart to take advantage of strong typing. It provides you with better error messages and better linting. Because you don't have any types for most of your variables they can be anything, and the linter has a hard time trying to figure out if you have a type mismatch.
Related
I am developing a mobile application with Flutter. And I have a code like this:
PaginationController.dart:
import 'package:get/get.dart';
import 'package:keycehennemi/functions/SecureStorage.dart';
class PaginationController extends GetxController {
RxString ProductImage = RxString("");
}
ListView.dart:
onTap: () {
setState(() {
paginationController.ProductImage.value = snapshot.data.docs[index].data()["Image"];
});
Get.to(const ProductDetailsPage());
},
The page I'm trying to show the image:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:keycehennemi/controllers/PaginationController.dart';
class ProductDetailsPage extends StatefulWidget {
const ProductDetailsPage({Key key}) : super(key: key);
#override
State<ProductDetailsPage> createState() => _ProductDetailsPageState();
}
PaginationController paginationController = PaginationController();
String ImageURL = "";
class _ProductDetailsPageState extends State<ProductDetailsPage> {
#override
void initState() {
super.initState();
setState(() {
ImageURL = paginationController.ProductImage.value;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
Image.network(ImageURL, fit: BoxFit.cover),
],
),
),
);
}
}
When I run these codes, I get an error like this:
How can I solve this problem? I haven't been able to figure it out for days, it bothers me.
When I print the ProductImage in the PaginatonController I see the value is given. So the value is assigned to ProductImage. But I can't display the image on the page I'm trying to display and I'm getting an error.
I know this may not be conventional but this is how i use GetX (It can help you):
First I make a GlobalBindings.dart file:
class GlobalBindings implements Bindings {
#override
void dependencies() {
Get.lazyPut<PaginationController>(() => PaginationController(),
fenix: true);
}
Then I do this in my void main:
void main async {
GlobalBindings().dependencies();
runApp(MyApp());
}
Then in my PaginationController.dart:
class PaginationController extends GetxController {
String _productImage = "";
String get productImage => _productImage;
setProductImage(String value){
_productImage = value;
update();
}
}
Then inside productImage:
class ProductDetailsPage extends StatelessWidget {
ProductDetailsPage({Key? key})
: super(key: key);
#override
Widget build(BuildContext context) {
//Call GetBuilder anywhere in the app
return GetBuilder<PaginationController>(builder:(controller)=> Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
if(controller.productImage.isNotEmpty)
Image.network(controller.productImage, fit: BoxFit.cover),
TextButton(child:Text("Button"),onPressed:(){
controller.setProductImage("You can set image url here");
})
],
),
),
));
}
}
Note: You can use the GetBuilder any where.
I am trying to make test project according to good practices.
Please note that I DON'T want any "hacky" approach. I am willing to learn good way of solving it.
My understanding of "lifting state up" is that any change updates the state, and then view is redrawn (rebuild) using current state. It is great in theory, but it DOES NOT work with TextFormField/TextEditingController.
I want to have a SharedState and bi-directonal TextFormField/TextEditingController, as follows:
case 1 (works):
TextFormField changes -> state is updated -> readonly Text (in WidgetTwo) is updated
case 2 (does not work):
button (in WidgetOne) is clicked -> state is updated -> TextFormField (in WidgetThree) shows new value from state
I have code in 3 different widgets + main file + SharedSate:
main.dart
void main() {
runApp(ChangeNotifierProvider(
create: (_) => sharedState(), child: const MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
WidgetOne(),
WidgetTwo(),
WidgetThree(),
]),
),
);
}
}
shared_state.dart
class SharedState extends ChangeNotifier {
int counter = 0;
void setCounter(int c) {
counter = c;
notifyListeners();
}
void incrementCounter() {
counter++;
notifyListeners();
}
void decrementCounter() {
counter--;
notifyListeners();
}
Future fetchCounterFromWeb() async {
// simulate external call
await Future.delayed(Duration(milliseconds: 500));
setCounter(42);
}
}
widget_one.dart
class WidgetOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
var state = Provider.of<SharedState>(context, listen: false);
return Row(
children: [
ElevatedButton(
onPressed: () => state.decrementCounter(),
child: Text('decrement')),
ElevatedButton(
onPressed: () => state.incrementCounter(),
child: Text('increment')),
ElevatedButton(
onPressed: () => state.fetchCounterFromWeb(),
child: Text('fetch counter from web')),
],
);
}
}
widget_two.dart
class WidgetTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
var state = Provider.of<SharedState>(context, listen: true);
return Row(
children: [Text('Value of counter is: ${state.counter}')],
);
}
}
widget_three.dart (problem is here)
class WidgetThree extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return WidgetThreeState();
}
}
class WidgetThreeState extends State<WidgetThree> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late TextEditingController _controller;
#override
void initState() {
super.initState();
var state = Provider.of<SharedState>(context, listen: false);
_controller = TextEditingController(text: state.counter.toString());
}
#override
Widget build(BuildContext context) {
var state = Provider.of<SharedState>(context, listen: true);
// THE ISSUE:
// It is NOT possible to update Controller (or TextEditing field)
// without this hacky line (which is not good practice)
_controller.text = state.counter.toString();
return Form(
key: _formKey,
child: Column(children: [
TextFormField(
controller: _controller,
keyboardType: TextInputType.number,
onChanged: (v) {
state.setCounter(int.parse(v.isEmpty ? '0' : v));
},
)
]),
);
}
}
I know I can possible move TextEditingController to SharedState, but SharedState should be UI agnostic, and TextEditingController is a UI widget.
I just started programming in Flutter. I want to create an app to keep track of the expiration dates of food.
My app is composed of:
main.dart that returns a MaterialApp class with inside the Home
home.dart that contains the AppBar, a Scaffold which contains a ListBuilder() as body and a FAB which should add a new item.
list_builder.dart that contains the stateful widget ListBuilder which takes a list of Strings from items_list.dart and creates a ListView with some tiles
items_list.dart that contains a List of Strings, a function to remove, add and retrieve the list.
What I made so far is a list of items with a trailing trash icon button that deletes the single item from the list. All works as expected.
Now I want that pressing the FAB, it triggers the ItemsList.addItem() which adds an item to the list. That works, of course, but the list on screen (created by the list_builder.dart) is not updated unless I delete one item.
I tried unsuccessfully to use callback functions, I'm sure I'm missing something.
This is the code:
main.dart
import 'package:flutter/material.dart';
import 'home.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Quando Scade?',
home: Home(),
theme: ThemeData(
primarySwatch: Colors.lightGreen,
),
);
}
}
home.dart
import 'package:flutter/material.dart';
import 'package:quando_scade/items_list.dart';
import 'list_builder.dart';
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Quando Scade?'),
backgroundColor: Theme.of(context).colorScheme.primary,
),
body: ListBuilder(),
floatingActionButton: FloatingActionButton(
onPressed: () {
ItemsList.addItem('ciao');
print('item added!!!');
},
child: const Icon(Icons.add),
),
);
}
}
list_builder.dart
import 'package:flutter/material.dart';
import 'items_list.dart';
class ListBuilder extends StatefulWidget {
const ListBuilder({Key key}) : super(key: key);
#override
_ListBuilderState createState() => _ListBuilderState();
}
class _ListBuilderState extends State<ListBuilder> {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: ItemsList.getItems().length,
itemBuilder: (context, index) {
return _buildRow(ItemsList.getItems()[index], index);
},
);
}
Widget _buildRow(String item, int index) {
return ListTile(
title: Text(
item,
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
ItemsList.removeItem(index);
});
},
),
);
}
}
items_list.dart
class ItemsList {
static List<String> _items = [
'banane',
'latte',
'caffè',
'vino',
'sushi',
'birra',
];
// to add items
static void addItem(String name) => ItemsList._items.add(name);
// to remove item
static void removeItem(int i) => ItemsList._items.removeAt(i);
// returns the list of items
static List<String> getItems() {
return ItemsList._items;
}
}
I see you are using setState on item delete, you should use it when adding an item as well:
...
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
ItemsList.addItem('ciao');
});
print('item added!!!');
},
child: const Icon(Icons.add),
),
...
Of course, make the Home widget as StatefulWidget before that.
Though this is not an optimal solution to what you're looking for, however, it's better than changing your Home class to a stateful widget and rebuilding your entire widget tree. I have modified your code to make it work exactly the way you want it without calling the setState function.
home.dart
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Quando Scade?'),
backgroundColor: Theme.of(context).colorScheme.primary,
),
body: ListBuilder(),
floatingActionButton: FloatingActionButton(
onPressed: () {
ItemsList.itemList.addItem('ciao');
print('item added!!!');
},
child: const Icon(Icons.add),
),
);
}
}
items_list.dart
class ItemsList {
final _list = [
'banane',
'latte',
'caffè',
'vino',
'sushi',
'birra',
];
StreamController<List<String>> _items =
StreamController<List<String>>.broadcast();
Stream<List<String>> get items => _items.stream;
// to add items
void addItem(String name) {
//_reOpenStream();
_list.add(name);
_items.sink.add(_list);
// _items.close();
}
// to remove item
void removeItem(int i) {
//_reOpenStream();
_list.removeWhere((element) => element == _list[i]);
_items.sink.add(_list);
}
void close() {
_items.close();
}
static final ItemsList _singleton = ItemsList._internal();
static ItemsList get itemList => ItemsList();
factory ItemsList() {
return _singleton;
}
ItemsList._internal();
}
list_builder.dart
class ListBuilder extends StatefulWidget {
const ListBuilder({Key key}) : super(key: key);
#override
_ListBuilderState createState() => _ListBuilderState();
}
class _ListBuilderState extends State<ListBuilder> {
#override
Widget build(BuildContext context) {
return StreamBuilder<List<String>>(builder: (_, snapshot) {
int itemCount = snapshot.data.length;
return ListView.builder(
itemCount: itemCount,
itemBuilder: (context, index) {
//Handle the empty list by replacing the container widget with your logic
return itemCount <=0 ? Container(): _buildRow(snapshot.data[index], index);
},
);
}, initialData: [],);
}
Widget _buildRow(String item, int index) {
return ListTile(
title: Text(
item,
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
ItemsList.itemList.removeItem(index);
});
},
),
);
}
}
Only call the close() method when you no longer need the stream, otherwise, it'd throw a bad state error when you try accessing it again. Additionally, as you progress in your learning try refactoring your codes to use an architecture design.. Peace!
I am completly new to Flutter and Stackoverflow. This is my first question to be in fact so please forgive me if I totaly fail at asking this question. I am trying to make a simple Flutter app that provides a ListView of questions and a checkbox beside each. The user can then choose which question they want to answer. My problem is that when the user checks any of the checkboxes then all get checked and vise versa. The questions themselves are retrieved from a backendless database. The code below is what i have so far. I would really appreciate any help anyone can provide me.
import 'package:flutter/material.dart';
class Questions extends StatefulWidget {
final List<Map> questionList;
Questions(this.questionList);
#override
_QuestionsState createState() => _QuestionsState();
}
class _QuestionsState extends State<Questions> {
bool _questionSelected = true;
Widget _buildQuestionItem(BuildContext context, int index) {
return ListTile(
title: Text(widget.questionList[index]['question']),
trailing: Checkbox(
value: _questionSelected,
onChanged: (bool val){
setState(() {
_questionSelected = val;
});
},
),
);
}
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.all(10),
itemBuilder: _buildQuestionItem,
itemCount: widget.questionList.length,
);
}
}
UPDATED:
Thankful for Mohammed Ashab Uddin suggestions I feel that I am close to getting this thing to work but I am still getting an error
"RangeError (index): Invalid value: Valid value range is empty: 0"
I think I should have posted the main.dart code where I set the value of the questionList perhaps it is an order of code execution that causes this error so please find my code for main.dart below in hopes it would help in figuring out this issue.
import 'package:flutter/material.dart';
import 'package:backendless_sdk/backendless_sdk.dart';
import 'package:flutter/rendering.dart';
import 'questions.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'RT Database Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Questions'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
static const String API_HOST = "https://api.backendless.com";
static const String APP_ID = "<APP_ID>";
static const String ANDROID_APP_KEY = "<ANDROID_APP_KEY>";
static const String IOS_APP_KEY = "<IOS_APP_KEY>";
IDataStore<Map> questionsStore = Backendless.data.of('Questions');
List<Map> questionsList = [];
var _questionSelected = false;
#override
void initState() {
super.initState();
_initBackendless();
_enableRealTime();
getQuestions();
}
void _initBackendless() {
Backendless.setUrl(API_HOST);
Backendless.initApp(APP_ID, ANDROID_APP_KEY, IOS_APP_KEY);
}
void _enableRealTime() {
EventHandler<Map> rtHandlers = questionsStore.rt();
rtHandlers.addCreateListener((question) {
setState(() {
questionsList = List.from(questionsList);
questionsList.add(question);
});
});
rtHandlers.addUpdateListener((question) {
setState(() {
questionsList = List.from(questionsList
.map((m) => m['objectId'] == question['objectId'] ? question : m));
});
});
rtHandlers.addDeleteListener((question) {
setState(() {
questionsList = List.from(questionsList);
questionsList.removeWhere((m) => m['objectId'] == question['objectId']);
});
});
}
void _selectQuestion(bool newValue) {
setState(() {
_questionSelected = newValue;
});
}
void getQuestions() {
DataQueryBuilder queryBuilder = DataQueryBuilder()
..pageSize = 100
..sortBy = ['created'];
questionsStore
.find(queryBuilder)
.then((response) => setState(() => questionsList = response));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My Life History"),
),
body: FractionallySizedBox(
heightFactor: 0.5,
child: Questions(questionsList),
),
);
}
}
The variable _questionSelected is a global variable. All the checkbox widgets are using this variable as the value. Therefore, when the variable changes on the onChanged() function, all the values are also changed to the value of _questionSelected.
In this case, you need to keep track of all the values of the checkbox widget. So, you should use an array rather than a single variable.
What I usually do is, create a new list that will contain only the selected elements.
Remove an element if it is not selected and add an element if it is selected.
//generate a list of false values with the length of questionList
List<bool> _questionSelected;
initState(){
_questionSelected = List<bool>.filled(questionList.length, false, growable: true);
super.initState();
}
Widget _buildQuestionItem(BuildContext context, int index) {
return ListTile(
title: Text(widget.questionList[index]['question']),
trailing: Checkbox(
value: _questionSelected[index],
onChanged: (bool val){
setState(() {
_questionSelected[index] = val;
});
},
),
);
}
I've been looking for answers for a whole day, I'm tryin to add fields in a form dynamically (for the moment, by creating a loop which create fields), I can't find any answer for my problem. If you can do something, just let me know :
I'm creating a view with my Fieldset class, which is my form and I put a Field list inside :
List<Field> fieldset;
void main() {
for (int i = 0; i < 10; i++) {
Field field = Field();
fieldset.add(field);
};
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final appTitle = 'Form Validation Demo';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(appTitle),
),
body: Fieldset(fieldset: List<Field>()),
),
);
}
}
As you can see, I'm just launching the app, creating some fields with the loop and putting it in Fieldset Widget go ahead :
class Fieldset extends StatefulWidget {
final List<Field> fieldset;
final state = _FieldsetState();
#override
_ FieldsetState createState() {
return _FieldsetState();
}
Fieldset({this.fieldset});
}
class _FieldsetState extends State<Fieldset> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget> [
// widget.fieldset.forEach((f) => f.build(context)) ==> THIS DOESN'T WORK
,
RaisedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text('processing')));
}
},
child: Text('Submit'),
)
]));
}
}
I really hope you can help me, Looks like i've missed something..
try this
var listOfFields = <Widget>[];
listOfFields.add(RaisedButton( onPressed : (){
addNewField();
}, child: Text("Add new Field")
));
void addNewField(){
setState((){
listOfFields.add(TextFormField());
});
}
final _formKey = GlobalKey<FormState>()
#override
Widget build(BuildContext context) {
return Form(
key : _formKey,
child :Scaffold(
body : ListView.builder(
itemCount: listOfFields.length,
itemBuilder: (context, index){
return listOfFields[index];
}
)
)
);
}