How to fix 'Text is null' in flutter - flutter

I want to create an app which has a TabBarView with two tabs. On the first Tab there is a Textfield and on the other tab there is a text widget which should display the text which you entered into Textfield
but I always get an error because text is null.( I'm new in programming with flutter)
I tried to initialize the variable in TextOutput class but it didn't work because the variable is final.
TabBarView(
children: <Widget>[
TextCreatePage(), TextOutput()
],
class TextCreatePageState extends State<TextCreatePage> {
String textvalue;
#override
Widget build(BuildContext context) {
return Center(child: TextField(
onChanged: (String value) {
setState(() {
textvalue = value;
TextOutput(textvalue: textvalue,);
});
class TextOutput extends StatelessWidget {
final String textvalue;
TextOutput({this.textvalue});
#override
Widget build(BuildContext context) {
return Text(textvalue);
}
}

So, for anyone that search this and got here, I'm using this to manage null String variables.
1. Show Empty Text
String nullText; //for null-safety change to: String? nullText;
//now, inside of your widget build
Text(nullText ?? '');
2. Not show Text Widget
String nullText;
//now, inside of your widget build
if(nullText != null)
Text(nullText);
with null-safety
String? nullText;
//now, inside of your widget build
if(nullText != null)
Text(nullText!);
Also you can show like this, but this show the null word
String nullText; //for null-safety change to String? nullText;
//now, inside of your widget build
Text('$nullText');
Live Example https://dartpad.dev/faab5bc3c2df9573c0a75a5ce3d4b4b9

It's not clear from the information your provided in your question what code causes the error, but I guess it is this line:
return Text(textvalue);
If you change it to
return textvalue != null ? Text(textvalue) : Container();
your error should go away.

Hector Aguero's answer is what I've been doing to workaround the error.
I use this everywhere:
Text(nullText ?? '');
But I wonder what's stopping Flutter team to make Text() widget support null value?
// This should be acceptable and simply not rendering any text
// why they don't allow this is beyond me
Text(nullText);

Related

Flutter: Is it possible to know if you're currently off stage?

I have a number of pages in my app wrapped in Offstage widgets. Each page makes use of the provider package to render based on state updates (e.g. the user does something, we make a network call and display the result).
As the pages are wrapped in Offstage widgets, the build() methods (and subsequent network calls) are called even if it's not the current page.
Is there a way inside the build() method to know if the widget is currently off stage (and if so, skip any expensive logic)?
I'm assuming I can work something with global state etc, but I was wondering if there was anything built-in in relation to the Offstage widget itself, similar to mounted
You can try finding the parent OffStage widget and see if the offstage property is true or false
#override
Widget build(BuildContext context) {
final offstageParent = context.findAncestorWidgetOfExactType<Offstage>();
if (offstageParent != null && offstageParent.offstage == false) {
// widget is currently offstage.
print('offstaged child');
} else {
// widget is not offstage
print('non-offstaged child');
}
return const Text('Example Widget');
}
I made a custom-made mechanism for the goal you wanna achieve:
First, I am declaring a new Map<String, bool> in a separate file alone that will hold the offStage bool value with the key of each class widget.
Map<String, bool> offStageMap = {};
then in the implementation of the StatefulWidget where the offstage widget is in:
class ExampleWidget extends StatefulWidget {
ExampleWidget({super.key}) {
widgetMapKey = runtimeType.toString();
}
late final String widgetMapKey;
#override
State<ExampleWidget> createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
final bool defaultIsOffStaged = false;
bool? localStateIsOffStages;
#override
void initState() {
offStageMap[widget.widgetMapKey] ??= defaultIsOffStaged;
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
bool previousIsOffStaged = offStageMap[widget.widgetMapKey]!;
setState(() {
localStateIsOffStages =
offStageMap[widget.widgetMapKey] = !previousIsOffStaged;
});
},
child: Offstage(
offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
child: Container(),
),
);
}
} },
child: Offstage(
offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
child: Container(),
),
);
}
}
let me explain what this is about.
first I declared a defaultIsOffStaged where it should be the initial offStage value when nothing is saved in that map.
when that widget is inserted in the widget tree (initState() called), the widget.widgetMapKey of the ExampleWidget widget will be saved in that map with the value of the default one which is defaultIsOffStaged.
offStageMap[widget.widgetMapKey] ??= defaultIsOffStaged;
in the offstage property o the OffStage widget, in this line:
offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
the nullable localStateIsOffStages will be null for the first time since it has no value yet, so offStageMap[widget.widgetMapKey]! which equals to defaultIsOffStaged will be the bool value of offstage.
until now what we have, is a map containing the key that belongs only to the ExampleWidget which is its widget.widgetMapKey with its offStage value, right?
now from all places in your app, you can get the offStage value of that widget with its widgetMapKey like this:
print(offStageMap[ExampleWidget().widgetMapKey]); // true
now let's say you want to change the offstage property of that widget, in my code I used a simple example of GestureDetector, so when we tap in the Text("toggle offstage") area, it toggles offStage, here is what happens:
we got the existing value in the map:
bool previousIsOffStaged = offStageMap[widget.widgetMapKey]!;
then assign the opposite of it, to that widget key in the map, and the localStateIsOffStages bool variable which was nullable, now it has a value.
and as normal so the state updates I wrapped it in a SetState(() {})
now the widget's offstage will be toggled, and every time the widget key in the map will be updated with that new value.
the localStateIsOffStages I declared just to hold the local state when this is happening while the StatefulWidget state updates.
after the StatefulWidget is disposed of (when you pop the route as an example) and open that route again, the initState() will execute but since we have now an entry in the map, it's not null so nothing will happen inside initState().
the localStateIsOffStages will be null, so the offStage property of the Offstage widget will be the value from the map, which is the previous value before the widget is disposed.
that's it, from other places you can check for the offstage value of that specific widget like this:
print(offStageMap[ExampleWidget().widgetMapKey])
you can do it for all your widget pages, so you will have a map containing the offStage values of them all.
I take it one step up, and made those methods that I guess they will help:
this will return a List with the pages where the value is true.
List<String> offstagedPages() {
List<String> isOffStagedPages = [];
offStageMap.forEach((runtimeType, isOffStaged) {
if (isOffStaged) {
isOffStagedPages.add(runtimeType);
}
});
return isOffStagedPages;
}
this will return a true if a page is off staged and false if not:
bool isPageWidgetOffStaged(String runtimeType) {
if (offStageMap.containsKey(runtimeType)) {
return offStageMap[runtimeType]!;
}
return false;
}
Hope this helps a little.
Maybe it's not applicable to you, but you might be able to solve it by simply not using Offstage. Consider this app:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool showFirst = true;
void switchPage() {
setState(() {
showFirst = !showFirst;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Stack(children: [
Offstage(offstage: !showFirst,child: A("first", switchPage)),
Offstage(offstage: showFirst,child: A("second", switchPage)),
]))));
}
}
class A extends StatelessWidget {
final String t;
final Function onTap;
const A(this.t, this.onTap, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('$t is building');
return TextButton(onPressed: ()=> onTap(), child: Text(t));
}
}
You will notice by the prints that both pages are build. But if you rewrite it like this without Offstage, only the visible one is build:
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Stack(children: [
if (showFirst) A("first", switchPage),
if (!showFirst) A("second", switchPage),
]))));
}
If you want to just keep state alive your pages , you can use https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html , you may check this blog for example usage, https://medium.com/manabie/flutter-simple-cheatsheet-4370a68f98b3
If you are using Navigator, you can just extends NavigatorObserver. Then you will get didpush and didpop, use state to manage elementlifecycle, you will get page onPause and onResume fun.

Is there any way to generate the same key for the "same" widget in Flutter without having any values to be based on

I have a widget which can have multiple input sections from the same type. When I delete the first child then it behaves weirdly like showing still the old value from that first widget. I figured out I need to use keys for the children but then my UX gets broken. Let me show you some code snippet, please:
class _ParentState extends State<ParentWidget> {
...
#override
Widget build(BuildContext context) {
return Column(
children: stateList
.mapIndexed((index, element) => ChildWidget(key: UniqueKey(), input: element, onChanged: (text) {
setState(() {
stateList[index].text = text;
});
}))
.toList(),
);
}
}
class _ChildState extends State<ChildWidget> {
final ctrl = TextEditingController();
#override
void initState() {
super.initState();
ctrl.text = widget.input.text;
}
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: ctrl,
onChanged: (value) {
widget.onChanged(value);
}
),
...
]
);
}
}
When I wrote the user experience is broken, I was meaning that whenever the user starts to type into the text field then the focus gets loosen character-by-character since the callback is invoked and then generates a new child widget from the state because it has now "different" key... How can I refactor this? I just need a key mechanism which results the same for a child while it is not disposed yet!?
TL;DR
Solution:
remove key
by default, flutter is smart enough to determine which widget is need to rebuild or not. Thats why, key is very rarely used.
ChildWidget(input: element,
onChanged: (text) {
setState(() {
....
When you set UniquieKey() you widget will have different key every time build method called.
which is: everytime you call SetState() your apps will build NEW widget.
if you want to set key, you can use index value
ChildWidget(
key: ValueKey('$index'),
input: element,
onChanged: (text) {
what caused your "user experience broken".
it because, in your onChange function, you call setState() and also you set UniqueKey to your widget. So.. when setState() has called, it will re-execute build method, and since your ChildWidget has Unique key, it will rebuild the widget.
every time user typing, ChildWidget is rebuild. that caused lost focus

type 'text' is not a subtype of type 'string'

I am new on Flutter and i got this error.
type 'text' is not a subtype of type 'string'
I have a class
class _Message {
int whom;
String text;
_Message(this.whom, this.text);
}
and I want to convert the text to a list using this
#override
Widget build(BuildContext context) {
final List<String> led = messages.map((_message) => Text('${_message.text}')).cast<String>
().toList();
}
How should i solve this?
Text is a Widget so you can't cast it to String.
Text Widgets are used to tell flutter how to render a String.
You also need to return a single Widget in the build method.
You probably just want to do
#override
Widget build(BuildContext context) {
return Column(
children: messages.map((message) => Text(message.text)).toList(),
);
}
Optional:
To get that List<String> you do
final List<String> led = messages.map((message) => message.text).toList();
So your build method could look like this:
#override
Widget build(BuildContext context) {
final List<String> led = messages.map((_message) => _message.text).toList();
return Column(
children: led.map((message) => Text(message)).toList(),
);
}
Text is a widget and not the same as a String Object. Therefore, you cannot assign a list of type Text to a List of type String.
If you mean to return a list of Text widgets, then change List<String> to List<Text>.
And secondly, the build methods requires you return a Widget Object and nothing more.

Flutter: How to obtain text entered from Autocomplete field instead of selecting a suggestion?

Flutter provides an Autocomplete widget. If I enter "abc" instead of selecting one of the suggestions, then how can I get this text "abc"?
You can create another variable to hold the input String inside state class like String? _inputString; and update value inside optionsBuilder
class _AutocompleteBasicExampleState extends State<AutocompleteBasicExample> {
String? _inputString;
#override
Widget build(BuildContext context) {
return Autocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) {
setState(() {
_inputString = textEditingValue.text;
});

Flutter: How to insert text properly in a TextField/TextFormField

Let's say there is an empty TextFormField. After entering 2 characters manually I would like to insert a new one programmatically. So if length equals with 2 than insert a new one. It sounds really simple but strange behaviors appeared while I tried to achieve this. For example: The cursor continuously jumps back to the start and may cause this debug log:
Text selection index was clamped (-1->0) to remain in bounds. This may not be your fault, as some keyboards may select outside of bounds.
Or if I try to deal with the TextEditingController's value or selection property to place cursor at the end of the text, it causes more strange behaviors.
Can you provide me an example by using a TextField or a TextFormField with a TextEditingController and on onChanged() if text length is equals with 2 than insert a new character at the end and place back the cursor also at the end.
I tried these solutions but in this case they did not work:
How do you change the value inside of a textfield flutter?
Thank you!
EDIT: Example code:
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'example',
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final TextEditingController controller = TextEditingController(text: '');
#override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
onChanged: (value) {
if (controller.text != null && controller.text.length == 2) {
controller.text = '$value/';
controller.selection = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
setState(() {});
}
},
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
The problem: If I replace the TextFormField to a TextField, it works as expected. I guess it is a bug that should be fixed.
I also found a link that in flutter version 1.20.1 and later this is an issue with TextFormFields.
https://github.com/flutter/flutter/issues/62654
TextFormField is a FormField that contains a TextField. While a Form isn't required to be its parent, using a Form makes it easier to manage multiple fields at once. You can stick with TextField if it works better and meets the requirements.