why does printing the text value in here brings up null? - flutter

So i'm trying to print out the value that's newTaskTitle when i press the flat button but it always brings up null , although when i print it in the textField it shows correctly , any idea why ?
for example on the onChanged for the text field , i try to print after assigning the newTaskTitle value to the newText , it prints the value
if I then try to print the value on the onPressed in the flat button , it print null .
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:todoey/models/task.dart';
class AddTaskScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
String newTaskTitle;
return Container(
color: Color(0xff757575),
child: Container(
padding: EdgeInsets.all(20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30.0),
topRight: Radius.circular(30.0),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Add Task',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.lightBlueAccent,
fontSize: 30.0,
),
),
TextField(
autofocus: true,
textAlign: TextAlign.center,
onChanged: (newText) {
newTaskTitle = newText;
},
),
SizedBox(
height: 10.0,
),
FlatButton(
color: Colors.lightBlueAccent,
onPressed: () {
//addTaskCallback(newTaskTitle);
print(newTaskTitle);
},
child: Text(
'Add',
style: TextStyle(
color: Colors.white,
),
),
)
],
),
),
);
}
}

You need to update the state of the widget.
The widget you are using is stateless, that is, it does not manipulate states, a solution (There are many) is to transform your widget from stateless to stateful.
Then you could use a SetState or ValueNotifier to modify the value.
Example:
import 'package:flutter/material.dart';
class AddTaskScreen extends StatefulWidget {
AddTaskScreen({Key key}) : super(key: key);
#override
_AddTaskScreenState createState() => _AddTaskScreenState();
}
class _AddTaskScreenState extends State<AddTaskScreen> {
String newTaskTitle = '';
#override
Widget build(BuildContext context) {
return Container(
color: Color(0xff757575),
child: Container(
padding: EdgeInsets.all(20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30.0),
topRight: Radius.circular(30.0),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Add Task',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.lightBlueAccent,
fontSize: 30.0,
),
),
TextField(
autofocus: true,
textAlign: TextAlign.center,
onChanged: (newText) {
newTaskTitle = newText;
},
),
SizedBox(
height: 10.0,
),
FlatButton(
color: Colors.lightBlueAccent,
onPressed: () {
setState(() {});
},
child: Text(
'Add',
style: TextStyle(
color: Colors.white,
),
),
)
],
),
),
);
}
}

You are trying to change the state of stateless widget. Convert your widget to StatefulWidget by clicking on Stateless and then pressing Ctrl + ., select convert to Stateful widget and then test app again.

Related

Placing dropdownbutton selections into a to-do list

I am very new to Flutter/Dart, so please forgive my ignorance and please explain everything to me like I am a five year old.
I have a screen on my app where I would like users to have two options: enter text using a textfield or select an option from a dropdown menu. Whichever they choose will be placed on a to-do list.
However, I can't seem to figure out how to get the selection from the DropdownButton widget to be saved to the to-do list the same way a textfield entry would be.
Below is my code for the screen where new items are entered.
import 'package:provider/provider.dart';
import 'task_data.dart';
class AddTaskScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
String newTaskTitle = '';
String _dropdownValue = '';
return Container(
color: const Color(0xFF757575),
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0)),
),
padding: const EdgeInsets.symmetric(horizontal: 60.0, vertical: 30.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const Text(
'Add a Task',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.blue,
fontSize: 30.0,
fontWeight: FontWeight.w500,
),
),
TextField(
autofocus: true,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 25.0,
),
onChanged: (newText) {
newTaskTitle = newText;
},
),
TaskSelect(),
const SizedBox(
height: 20.0,
),
MaterialButton(
onPressed: () {
Provider.of<TaskData>(context, listen: false)
.addTask(newTaskTitle);
Navigator.pop(context);
},
color: Colors.blue,
child: const Text(
'Add',
style: TextStyle(
fontSize: 20.0,
),
),
textColor: Colors.white,
height: 50.0,
)
],
),
),
);
}
}
class TaskSelect extends StatefulWidget {
const TaskSelect({Key? key}) : super(key: key);
#override
State<TaskSelect> createState() => _TaskSelectState();
}
class _TaskSelectState extends State<TaskSelect> {
String dropdownValue = 'Select a task';
String newTaskTitle = '';
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: dropdownValue,
icon: const Icon(Icons.arrow_downward),
elevation: 16,
style: const TextStyle(color: Colors.blueAccent),
underline: Container(
height: 2,
color: Colors.blueAccent,
),
onChanged: (String? newValue) {
setState(() {dropdownValue = newValue!;});
newTaskTitle = dropdownValue;
},
items: <String>[
'Task 1',
'Task 2',
'Task 3',
]
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
}
}
Your problem is how to access the value selected from the other class
you can achieve that in many ways
one is to make the widget accept a variable (which is your titleText) and when it's changed in that class (widget) (TaskSelcted) it will change the variable value in the other class (widget)(AddTaskScreen)
class TaskSelect extends StatefulWidget {
//Telling the class you will get a value of string when ever someone used ue
String? taskTitle;
TaskSelect({Key? key, required this.taskTitle}) : super(key: key);
#override
State<TaskSelect> createState() => _TaskSelectState();
}
.
//Telling the class here you are being used, this is the value you are promised to get. you can change its value as i tell you
TaskSelect(newTaskTitle),
const SizedBox(
height: 20.0,
),
.
onChanged: (String? newValue) {
setState(() {dropdownValue = newValue!;});
//Change the variable you took to this value
widget.textTitle = dropdownValue;
}

I cant even print a nullable value

basically the problem is clear :
here is my code :
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class AddTaskScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
late String newTaskTitle;
return Container(
color: Color(0xFF757575),
child: Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.limeAccent[400],
borderRadius: BorderRadius.only(
topLeft: Radius.circular(60),
topRight: Radius.circular(60),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Add Task',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 30, color: Colors.brown[900]),
),
SizedBox(
height: 12,
),
TextField(
onChanged: (newText) {
newTaskTitle = newText;
},
autocorrect: false,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.brown[800]!,
width: 2,
),
borderRadius: BorderRadius.circular(30),
),
hintText: 'Type Your Task ...',
labelStyle: TextStyle(
color: Colors.green[900],
),
helperStyle: TextStyle(
color: Colors.brown[900],
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(60),
borderSide: BorderSide(
color: Colors.brown[900]!,
width: 2,
),
),
),
),
SizedBox(
height: 12,
),
ElevatedButton(
onPressed: () {
// Provider.of<TaskData>(context, listen: false)
// .addTask(newTaskTitle);
// Navigator.pop(context);
print(newTaskTitle);
// Provider.of<TaskData>(context, listen: false)
// .addTask(newTaskTitle);
// Navigator.pop(context);
},
child: Text(
'Add',
style: TextStyle(color: Colors.brown[900]),
),
style: ButtonStyle(
backgroundColor: MaterialStateColor.resolveWith(
(states) => Colors.lightGreen),
elevation: MaterialStateProperty.resolveWith((states) => 6),
shadowColor:
MaterialStateColor.resolveWith((states) => Colors.green),
minimumSize: MaterialStateProperty.resolveWith(
(states) => Size.square(40.67)),
),
),
],
),
),
);
}
}
please help me ...at this stage I just want to print the value that the user enters in the text field in the console ...but it gives me error :
LateInitializationError: Local 'newTaskTitle' has not been initialized.
i have also changed it to a stateful widget to check if it works with setstate but it didnt ..
i also made it nullable like this => String? newTaskTitle; and Ofcourse it made to change a lot of things but at last the null value was passed ...there is a problem whithin the onChange callback of the textfield that doesnt assign the new value of user input to the variable i have created ...
How can I fixe this problem ?
change
late String newTaskTitle;
to
ValueNotifier<String> newTaskTitle = ValueNotifier("");
and put ElevatedButton inside ValueListenableBuilder
like this
ValueListenableBuilder<String>(
listenable : newTaskTitle,
builder : (ctx,taskt,_){
return ElevatedButton(
onPressed : taskt.isEmpty ? null : (){
// put you logic
}
.....
);
}
);
change onChanged of textField like this
onChanged: (newText) {
newTaskTitle.value = newText;
},

On printing the value of 'newTaskTitle' inside TextField, it works fine. But when I try to print it from the FlatButton it shows 'null'

Why is the String variable newTaskTitle not updating across the entire class when I update it inside the text field? when printing it from the flat button it gets backs to its default null value. Anything i change on it inside the onChange brackets doesn't reflect across the entire code.
class AddTaskScreen extends StatelessWidget {
Color themeColor = TasksScreen().themeColor;
Function addTaskCallback;
AddTaskScreen(this.addTaskCallback);
#override
Widget build(BuildContext context) {
**String newTaskTitle;**
return Container(
color: Color(0xFF757575),
child: Container(
padding: EdgeInsets.all(40),
decoration: BoxDecoration(
color: themeColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
)),
child: Column(
children: [
Text(
'ADD TASK',
style: TextStyle(
color: Colors.white,
fontSize: 30,
fontWeight: FontWeight.bold),
),
TextField(
textAlign: TextAlign.center,
onChanged: (value) {
newTaskTitle = value;
print('print from textfield=$newTaskTitle');
},
),
SizedBox(
height: 20,
),
FlatButton(
onPressed: () {
print('from flatbutton = $newTaskTitle');
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
color: Colors.white,
child: Text(
'Add',
style: TextStyle(
color: themeColor,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
),
)
],
),
),
);
}
}
String newTaskTitle; is defined in the build function. Each time build is called newTaskTitle is re-declared and is an entirely new variable.
You'd want to declare it outside the function scope and in the class scope. Like where themeColor is declared.
However, after that you'll find that when you set newTaskTitle nothing happens. You will need to define a Stateful class that you can setState newTaskTitle.
I'll leave that as an exercise for you.

Flutter bottom sheet changing background heigth

I am using bottom sheet in my app but its changing the background screen height. I need my background page fixed. And need to increase bottom sheet height
Here is my bottom sheet code
import "package:flutter/material.dart";
import 'package:flutter/services.dart';
import 'package:flutter/cupertino.dart';
import 'dart:io';
class addQuestion extends StatefulWidget {
#override
_addQuestionState createState() => _addQuestionState();
}
class _addQuestionState extends State<addQuestion> {
void _submitData(){
Navigator.of(context).pop();
}
#override
Widget build(BuildContext context) {
double stackHeight = (MediaQuery.of(context).size.height);
return Container(
decoration: BoxDecoration(
color: Color(0xff404040),
borderRadius: BorderRadius.only(topLeft: Radius.circular(15.0), topRight: Radius.circular(15.0))
),
child: SingleChildScrollView(
child: Card(
elevation: 5,
color: Color(0xff404040),
child: Container(
padding: EdgeInsets.only(top: 10, left: 10, right:10,
bottom: MediaQuery.of(context).viewInsets.bottom + 10 ,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
TextField(
maxLength: 56,
style: TextStyle(
color: Colors.white,
),
textAlign: TextAlign.center,
decoration: InputDecoration(
counterText: '',
labelText: 'Would Question',
labelStyle: TextStyle(
color: Colors.blue
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.blue),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.blue),
),
),
onSubmitted: (_) => _submitData(),
),
TextField(
maxLength: 56,
style: TextStyle(
color: Colors.white,
),
textAlign: TextAlign.center,
decoration: InputDecoration(
counterText: '',
labelText: 'Rather Question',
labelStyle: TextStyle(
color: Colors.red
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
),
keyboardType: TextInputType.number,
onSubmitted: (_) => _submitData(),
),
RaisedButton(onPressed: () {
_submitData();
}, child: Text('Add Question', style: TextStyle(
color: Colors.white
),),color: Theme.of(context).primaryColor,
),
],
),
),
),
),
);
}
}
As you see when keyboard open the background page is moving on the upside. And in rounded orders of bottom sheet, it's showing white corner don't know why its showing the need to transparent or remove this.
There are two kinds of bottom sheets in material design:
Persistent. A persistent bottom sheet shows information that supplements the primary content of the app. A persistent bottom sheet remains visible even when the user interacts with other parts of the app. Persistent bottom sheets can be created and displayed with the ScaffoldState.showBottomSheet function or by specifying the Scaffold.bottomSheet constructor parameter.
Modal. A modal bottom sheet is an alternative to a menu or a dialog and prevents the user from interacting with the rest of the app. Modal bottom sheets can be created and displayed with the showModalBottomSheet function.
showBottomSheet method
class MyStatelessWidget extends StatelessWidget {
MyStatelessWidget({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: const Text('showBottomSheet'),
onPressed: () {
Scaffold.of(context).showBottomSheet<void>(
(BuildContext context) {
return Container(
height: 200,
color: Colors.amber,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('BottomSheet'),
RaisedButton(
child: const Text('Close BottomSheet'),
onPressed: () => Navigator.pop(context),
)
],
),
),
);
},
);
},
),
);
}
}
**showModalBottomSheet function **
class MyStatelessWidget extends StatelessWidget {
MyStatelessWidget({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: const Text('showModalBottomSheet'),
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
color: Colors.amber,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Modal BottomSheet'),
RaisedButton(
child: const Text('Close BottomSheet'),
onPressed: () => Navigator.pop(context),
)
],
),
),
);
},
);
},
),
);
}
}
The BottomSheet widget itself is rarely used directly. Instead, prefer to create a persistent bottom sheet with ScaffoldState.showBottomSheet or Scaffold.bottomSheet, and a modal bottom sheet with showModalBottomSheet.

Why does a change in state of a child bottom sheet trigger a rebuild of parent widget?

I have a Scaffold screen (ListsScreen).
Which has a Button(AddNewListButton) that opens up a Modal Bottom Sheet (ListScreenBottomSheetWidget).
Bottom Sheet has TextField (ListTitleInputTextFieldWidget).
When i tap the TextField to open the keyboard, the parent screen rebuilds itself, due to which ofcourse all its child widgets are rebuilt as well.
Why is this happening? I was under the impression that state changes only rebuild themselves or their children, not their parents. And i also added const constructors almost everywhere to avoid rebuilds but this is still happening.
The Parent ListsScreen:
class ListsScreen extends StatelessWidget {
const ListsScreen();
static const routeName = '/lists-screen';
#override
Widget build(BuildContext context) {
final user = Provider.of<AuthProvider>(context, listen: false).getUser;
print('stateless rebuilding');
return Scaffold(
appBar: AppBar(
centerTitle: false,
title: Text(
'${user['name']}\'s Lists',
style: TextStyle(
color: Theme.of(context).primaryColorLight,
),
),
actions: <Widget>[
const SignOutButton(),
],
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
SizeConfig.smallDevice
? const SizedBox(
height: 30,
)
: const SizedBox(
height: 40,
),
SizeConfig.smallDevice
? Text(
'Welcome to TODOS',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
color: Colors.grey[700],
),
)
: Text(
'Welcome to TODOS',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
color: Colors.grey[700],
),
),
SizeConfig.smallDevice
? const SizedBox(
height: 30,
)
: const SizedBox(
height: 40,
),
const AddNewListButton(),
SizeConfig.smallDevice
? const SizedBox(
height: 30,
)
: const SizedBox(
height: 40,
),
const UserListsListViewWidget(),
],
),
),
),
);
}
}
class SignOutButton extends StatelessWidget {
const SignOutButton();
Future<void> _submitRequest(BuildContext context) async {
_showLoadingAlert(context);
try {
await Provider.of<AuthProvider>(context, listen: false)
.submitLogOutRequest();
Navigator.of(context).pop();
Navigator.of(context).pushReplacementNamed(LoginScreen.routeName);
} on HttpExceptions catch (error) {
Navigator.of(context).pop();
_showErrorDialogue(error.getErrorList, context);
}
}
void _showErrorDialogue(List<dynamic> errorMessages, BuildContext context) {
showDialog(
context: context,
builder: (ctx) => Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: ErrorListWidget(errorMessages: errorMessages),
),
);
}
void _showLoadingAlert(BuildContext context) {
showDialog(
context: context,
builder: (ctx) => const LoadingWidget(),
);
}
#override
Widget build(BuildContext context) {
return FlatButton(
onPressed: () => _submitRequest(context),
child: Row(
children: <Widget>[
Text(
'Sign Out',
style: TextStyle(
color: Theme.of(context).primaryColorLight,
),
),
Icon(
Icons.exit_to_app,
color: Theme.of(context).primaryColorLight,
),
],
),
);
}
}
class AddNewListButton extends StatelessWidget {
const AddNewListButton();
void _modalBottomSheetMenu(BuildContext context) {
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (builder) {
return const ListScreenBottomSheetWidget();
},
);
}
#override
Widget build(BuildContext context) {
return RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
elevation: 10,
color: Theme.of(context).primaryColor,
onPressed: () => _modalBottomSheetMenu(context),
child: Text(
'+ Add List',
style: TextStyle(
color: Colors.white,
fontSize: SizeConfig.smallDevice ? 10 : 15,
),
),
);
}
}
The Modal Bottom Sheet:
import 'package:flutter/material.dart';
import 'package:todo_spicotech/helpers/size_config.dart';
class ListScreenBottomSheetWidget extends StatelessWidget {
const ListScreenBottomSheetWidget();
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
currentFocus.unfocus();
},
child: Container(
margin: const EdgeInsets.all(20.0),
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: Material(
borderRadius: BorderRadius.all(Radius.circular(15)),
elevation: 10,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizeConfig.smallDevice
? Text(
'Create a new List',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
color: Colors.grey[700],
),
)
: Text(
'Create a new List',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
color: Colors.grey[700],
),
),
SizeConfig.smallDevice
? const SizedBox(
height: 20,
)
: const SizedBox(
height: 30,
),
const ListTitleInputTextFieldWidget(),
SizeConfig.smallDevice
? const SizedBox(
height: 20,
)
: const SizedBox(
height: 30,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
InkWell(
borderRadius: BorderRadius.circular(5),
onTap: () {
Navigator.of(context).pop();
},
child: Ink(
padding: EdgeInsets.all(10),
child: const Text('CANCEL'),
),
),
RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
elevation: 10,
color: Theme.of(context).primaryColor,
onPressed: () {},
child: Text(
'Create',
style: TextStyle(
color: Colors.white,
fontSize: SizeConfig.smallDevice ? 10 : 15,
),
),
),
],
),
],
),
),
),
),
);
}
}
class ListTitleInputTextFieldWidget extends StatefulWidget {
const ListTitleInputTextFieldWidget();
#override
_ListTitleInputTextFieldWidgetState createState() => _ListTitleInputTextFieldWidgetState();
}
class _ListTitleInputTextFieldWidgetState extends State<ListTitleInputTextFieldWidget> {
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: const InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.lightBlue,
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.lightBlue,
),
),
labelText: 'List Title',
contentPadding: EdgeInsets.all(10),
),
);
}
}
when you call showModalBottomSheet , it actually use Navigator inside
return Navigator.of(context, rootNavigator: useRootNavigator).push(_ModalBottomSheetRoute<T>(
builder: builder,
source code of showModalBottomSheet https://github.com/flutter/flutter/blob/17079f26b54c8517678699a0cefe5f7bfec67b3f/packages/flutter/lib/src/material/bottom_sheet.dart#L635
Flutter teams' reply of issue Pages on Navigator stack rebuild when a new page is pushed https://github.com/flutter/flutter/issues/11655#issuecomment-348287396
This is working as intended. In general, you should assume that all widgets can rebuild at any time, that they don't is mostly just an optimisation.
In particular, routes will rebuild because their navigator state has changed so they might need to update how they draw back buttons and the like.