What is the scope of the snapshot of a StreamBuilder widget? - flutter

I have a page with a column of TextFields. If I populate the TextFields with an existing record I do it through a TextEditingController for each field. I was passing in the QueryDocumentSnapshot from another page but I don't want to do that any longer because I am adding functionality where that no longer works. I can't use StreamProvider because I need some variables that are not available at the top of the tree in order to create the StreamProvider.
I think the best place to create the stream is in the page itself.
So now, I want to get the snapshot and initialize the TextEditingControllers in a method within the class and use them in the "build" method.
I don't know what the scope is of the snapshot that I create using StreamBuilder. How do I access the snapshot in another method in the class?
This is the column code in the build method:
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
reverse: true,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: StreamBuilder(
stream: FirestoreService().getSingleAgencyTrxns(widget.trxnId),
builder: (context, trxnSnapshot) {
if (!trxnSnapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Column(
children: <Widget>[
TextField(
autofocus: true,
keyboardType: TextInputType.text,
controller: clientFNameController,
textAlign: TextAlign.center,
onChanged: (value) {
trxnProvider.changeclientFName(value);
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Client First Name',
labelText: 'Client First Name'),
),
SizedBox(
height: 8.0,
),
TextField(
keyboardType: TextInputType.text,
controller: clientLNameController,
textAlign: TextAlign.center,
onChanged: (value) {
trxnProvider.changeclientLName(value);
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Client Last Name',
labelText: 'Client Last Name'),
),
SizedBox(
height: 8.0,
),
],
);
}
},
),
),
),
),
}
I initialize the TextEditingControllers through a method call in the initState method:
#override
void initState() {
getTrxn();
super.initState();
}
This is the getTrxn() method:
if (globals.currentTrxnId == null || globals.currentTrxnId == "") {
// new record: Set the textFields to blank
clientFNameController.text = "";
clientLNameController.text = "";
} else {
// Get the transaction
clientLNameController.text = someProvider?.clientLName ?? "";
clientTypeController.text = someProvider.clientType ?? "";
}
}

The snapshot variable is visible for every child widget, down from the builder property of the StreamBuilder.
This means that if you really need that info across your app you either:
Create a StreamBuilder on a top-ish level of your app;
Create several StreamBuilder across your app, which listen for the same events and behave accordingly.
The second option might be more complicated but I think that in some real-case scenarios it may be worth it: StreamBuilder re-builds its children when anything changes. Luckily, Flutter is smart enough to not re-build every element on the tree, but it still has to evaluate which Widget is marked as "dirty".

Related

Flutter web app not showing typed password when set to show password

On my flutter webapp I am trying to show the password if the user chose to show it. If I set my bool value to false it start by showing the normal text and then if I press the button to show or hide the password it will obscure the text but it will not un-obscure the text. I added a print function in one of my try's and it does change the bool value from false to true and back to false. I have it it a setState but no luck. I tryied many different examples online but can not get it to work with the web app.
The code
#override
Widget build(BuildContext context) {
c_width = MediaQuery.of(context).size.width*0.8;
return Scaffold(
appBar: AppBar(title: Text("App"),
),
body: signUpWidget(widget.signUpValue),
);
}
Widget signUpWidget(String? rl) {
switch(rl) {
case 'User': {
return SingleChildScrollView(
child: Container (
width: c_width,
padding: const EdgeInsets.all(16.0),
child: Column(
child: Column(
children: <Widget>[
TextFormField(
obscureText: isObscure,
decoration: InputDecoration(
suffix: TextButton(
child: isPasswordObscure
? Text(
'Show',
style: TextStyle(color: Colors.grey),
)
: Text(
'Hide',
style: TextStyle(color: Colors.grey),
),
onPressed: () {
setState(() {
isObscure = !isObscure;
isPasswordObscure = !isObscure;
});
},
),
),
),
Like I say it changes the text to hide and show and if I print the bool value it changes from true to false, but it does not unObscure the text in the field to show the password. Is it something to do with web? or am I missing something?
Thank you.
Actually you dont need to have two bool,
TextFormField(
obscureText: isObscure,
decoration: InputDecoration(
suffix: TextButton(
child: isObscure //< using
? Text(
'Show',
style: TextStyle(color: Colors.grey),
)
: Text(
'Hide',
style: TextStyle(color: Colors.grey),
),
onPressed: () {
setState(() {
isObscure = !isObscure;
// isPasswordObscure = !isObscure; // here it reverse the variable isPasswordObscure
});
},
),
),
)
It might be occurring due to this problem in setState((){}).
setState(() {
// assume isObscure is true initially
isObscure = !isObscure;
// now isObscure is false
isPasswordObscure = !isObscure;
// now isPasswordObscure is true, because isObscure's value has already changed.
});
To solve this, first save isObscure to a temporary variable.
setState(() {
var temp = isObscure;
isObscure = !temp;
isPasswordObscure = !temp;
});
Or,
Entirely avoid using two bools.
setState(() {
isObscure = !isObscure;
});
It seems like this occurs only in web.
Follow these steps to fix this,
https://github.com/flutter/flutter/issues/83695#issuecomment-1083537482
Or
Upgrade to the latest Flutter version.

Why is my flutter TextField initialState not working

Here is the code on how i call my custom Widget called BodyInput
BodyInput(
label: 'Name',
readOnly: readOnly,
initialValue: name,
placeholder: name,
handleChange: (value) {
print(value);
},
),
both placeholder and initialState has the same value but for some reason instead of displaying the initialState my TextField is displaying the placeholder. results
and here is the BodyInput itself *for some reason it wont let me copy paste the code so here is an image instead i'm really sorry for the trouble, for some reason when i paste the code only first line is copied as code the rest is normal text
Well turnsout my widget.initialState on initState value was null :/, so i had to set the _controller value in the Widget and my code become like this
Widget build(BuildContext context) {
//Had to move this here instead of in the initState method.
_controller = new TextEditingController(text: widget.initialValue);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.label),
TextField(
onChanged: (value) {
widget.handleChange(value);
},
controller: _controller,
readOnly: widget.readOnly,
decoration: InputDecoration(
hintText: widget.placeholder, contentPadding: EdgeInsets.all(2)),
),
],
);
}
please let me know if there is a better answer

Flutter: Multiline TextField (TextFormField) simply won't work

I am fairly new at Flutter so I might have gotten things wrong but ... I can't get my head around this issue.
I have ready many posts on how allow multi-line text for TextField / TextFormField.
I have tried setting maxLines to null or to a big number, to enforce max lines (or not), but nothing works !
How come something as simple won't work ?
// in a Scaffold of a StatefulWidget
body: Padding(
padding: const EdgeInsets.all(50.0),
child: TextField(
keyboardType: TextInputType.multiline,
minLines: 1,
maxLines: 5,
),
I have tried on two different real Android devices and hitting the ENTER key does nothing. Unfortunately, I can't test the behavior on iOS as I don't have a Mac.
EDIT 1
Following solution offered by copsonroad, the following code
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.event.shortName == null || widget.event.shortName.isEmpty
? AppLocalizations.of(context).eventEditorTitleNewEvent
: AppLocalizations.of(context)
.eventEditorTitleEditEvent(widget.event.shortName)),
actions: <Widget>[
if (_canSave == true) ...[
IconButton(
icon: Icon(Icons.done),
onPressed: () => _saveEventAndPop(),
)
],
]),
body: Center(
child: Padding(
padding: const EdgeInsets.all(50),
child: SizedBox(
height: 5 * 50.0,
child: TextField(
decoration: InputDecoration(hintText: "Enter a message"),
keyboardType: TextInputType.multiline,
maxLines: 5,
),
),
),
),
);
}
Produce the following result:
Screenshot:
#override
Widget build(BuildContext context) {
var maxLines = 5;
return Scaffold(
appBar: AppBar(),
body: Center(
child: Padding(
padding: const EdgeInsets.all(50),
child: SizedBox(
height: maxLines * 50.0,
child: TextField(
decoration: InputDecoration(hintText: "Enter a message"),
keyboardType: TextInputType.multiline,
maxLines: maxLines,
),
),
),
),
);
}
Edit:
Testing it on Android:
There is actually a bug with TextInputType.multiline. It simply doesn't work with some custom keyboards (i.e. SwiftKey). There is an issue in flutter github where I described my workaround.
First of all, I've wrapped my TextFormField with RawKeyboardListener like this:
RawKeyboardListener(
focusNode: FocusNode(),
autofocus: false,
onKey: (rawKeyEvent) {
final isFormSkippedEnterEvent = rawKeyEvent is RawKeyDownEvent &&
rawKeyEvent.isKeyPressed(LogicalKeyboardKey.enter);
final needToInsertNewLine = isFormSkippedEnterEvent &&
keyboardType == TextInputType.multiline;
if (needToInsertNewLine) {
TextEditingControllerHelper.insertText(controller, '\n');
}
},
child: TextFormField(...)
...
When Gboard is used, RawKeyboardListener doesn't receive Enter event at all, it's sent directly to the text field. But when using for example SwiftKey, instead of inserting new line to the text, RawKeyboardListener receives an event RawKeyDownEvent#a5131(logicalKey: LogicalKeyboardKey#70028(keyId: "0x100070028", keyLabel: "Enter", debugName: "Enter"). I check if I need to insert a new line (no need for this if TextFormField is configured to be single line) and access TextEditingController to alter user's input. All the magic happens inside TextEditingControllerHelper.insertText():
class TextEditingControllerHelper {
static insertText(TextEditingController controller, String textToInsert) {
final selection = controller.selection;
final cursorPosition = selection.base.offset;
if (cursorPosition < 0) {
controller.text += textToInsert;
return;
}
final text = controller.text;
final newText =
text.replaceRange(selection.start, selection.end, textToInsert);
controller.value = controller.value.copyWith(
text: newText,
selection: TextSelection.collapsed(
offset: selection.baseOffset + textToInsert.length,
),
);
}
}
What I do is check cursor/ selection position. In case of an empty field I just attach a newLine (this helper can work with other inputs, i.e. emojis) and exit. Otherwise I insert a new line on place of existing selection (if there is no selection, text is just inserted where cursor is placed) and move cursor to be after the inserted text. Works fine in my tests (Pixel 3a with Gboard/SwiftKey, Galaxy S8 with Samsung keyboard, iPhone simulator).

Flutter TextEditingController retain value without hitting done button

Simple Add Place widget
Title Text Field
Container - Render image from camera
Button - Activates camera device
I thought having a controller connected to TextField would automatically save the state of the input value. However, from my example, if I input the text without click "done" and immediately click on "Take Picture" button. The TextField input value is cleared after coming back from camera operation.
How to Reproduce Problem:
Input text into the field
Immediately click on the Camera button without click done / check or hit enter on the keyboard
Take a picture confirm.
Come back to page the TextField is empty
Example Code:
AddPlacePage StatefulWidget
Column(
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: 'Title'),
controller: _titleController,
),
ImageInput(),
],
),
ImageInput StatefulWidget
class _ImageInputState extends State<ImageInput> {
File _storedImage;
Future<void> _takePicture() async {
final imageFile = await ImagePicker.pickImage(
source: ImageSource.camera,
maxWidth: 600,
);
setState(() {
_storedImage = imageFile;
});
...
}
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Container(
...
child: _storedImage != null
? Image.file(
_storedImage,
fit: BoxFit.cover,
width: double.infinity,
)
: Text(
'No Image Taken',
textAlign: TextAlign.center,
),
alignment: Alignment.center,
),
Expanded(
child: FlatButton.icon(
icon: Icon(Icons.camera),
label: Text('Take Picture'),
textColor: Theme.of(context).primaryColor,
onPressed: () => _takePicture(),
),
),
],
);
}
}
Question:
How can I modify TextField's controller to retain input value even after exiting the application to access device camera?
TextField(
decoration: InputDecoration(labelText: 'Title'),
controller: _titleController,
),
I did try to create a local variable and try to use onChange:
String _inputValue
build(BuildContext context){
...
TextField(
decoration: InputDecoration(labelText: 'Title'),
controller: _titleController,
onChange: (value) => _inputValue = value;
),
However the effect is the same once returning from the camera as Flutter re-reders the page, both _inputValue and _titleController.text is cleared.
Example code:
https://github.com/erich5168/flutter_camera_example
You can use share preferences to save the String to device then call it when you back to text field. This is how I implement:
class LocalStorage {
static SharedPreferences instance;
static Future init() async {
instance = await SharedPreferences.getInstance();
}
static dynamic getValue(String key, dynamic emptyValue) {
if (LocalStorage.instance.containsKey(key)) {
return LocalStorage.instance.get(key);
}
return emptyValue;
}
}
set it to text field:
TextEditingController _usernameController =
new TextEditingController(text: LocalStorage.getValue('UserName', ''));

Flutter using EditableText

I'm trying to figure out how to use the TextEditor in Flutter.
I have "Card Editor" (basically I want to be able to work on the equivalent of a paragraph of text)
new EditableText(
autofocus: true,
maxLines: null,
backgroundCursorColor: Colors.amber,
cursorColor: Colors.green,
style: TextStyle(),
focusNode: FocusNode(),
controller: controller,
onSubmitted: (val) {
_addCard(val);
Navigator.pop(context);
},
)
I adapted this from an example of a TextField.
But I have a couple of questions.
Firstly, it doesn't seem to show anything when I type. The cursor moves, but no text is visible. Is that the default when there's no explicit style?
Secondly, how do I trigger the submit? With a TextField, the CR / Enter button does this. Obviously I see why you don't necessarily want that with EditableText But what should I do instead?
Third, I need to be able to put default text into this widget. I tried adding a "value" attribute to the EditableText, but that doesn't seem to be right. What's the way to do this?
from TextField class - material library - Dart API :
EditableText, which is the raw text editing control at the heart of a TextField. The EditableText widget is rarely used directly unless you are implementing an entirely different design language, such as Cupertino.
here an example of TextField , from my app flutter listview CRUD app using nonsecure rest api
class _TaskEditPageWidgetState extends State<TaskEditPageWidget> {
TextEditingController _noteController;
#override
void initState() {
super.initState();
_noteController = TextEditingController.fromValue(
TextEditingValue(
text: widget.taskOpj.note,
),
);
}
#override
void dispose() {
_noteController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appBar(),
body: _body(),
);
}
Widget _appBar() {
return AppBar(
title: new Text("Edit Task"),
actions: <Widget>[
new IconButton(
icon: new Icon(Icons.save),
onPressed: _save,
),
],
);
}
Widget _body() {
return SingleChildScrollView(
child: Column(
children: <Widget>[
Text("Note:"),
TextField(
decoration: InputDecoration(border: InputBorder.none),
autofocus: true,
keyboardType: TextInputType.multiline,
maxLines: null,
controller: _noteController),
],
),
);
}
Future _save() async {
widget.taskOpj.note = _noteController.text;
await Tasks.updateTask(widget.taskOpj);
widget.notifyParent();
Navigator.pop(context);
}
}
Just add this line:
style: TextStyle(
decorationThickness: 0.001,
),