How to access attributes of the widgets children - flutter

I am opening a new activity with the startTimer method How do I set the cyclesVal variable to an attribute of my custom NumberSelector widget so I can pass it to the next activity. Any way to access an internal variable of the NumberSelector widget would work too since I have set the attribute to the variable.
class ExcersizeInput extends StatelessWidget {
const ExcersizeInput({super.key});
void startTimer(BuildContext context) {
int cyclesVal = 1;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TimerPage(
cycleCount: cyclesVal,
),
));
}
#override
Widget build(BuildContext context) {
return Column(children: [
Column(
children: [
NumberSelector(
title: "Cycles",
),
],
),
ElevatedButton.icon(
onPressed: (() => {startTimer(context)}),
label: const Text("Start"),
icon: const Icon(Icons.start_outlined),
)
]);
}
}
Here's the NumberSelector code:
class NumberSelector extends StatefulWidget {
final String title;
const NumberSelector(
{super.key, required this.title});
#override
State<NumberSelector> createState() => _NumberSelectorState();
}
class _NumberSelectorState extends State<NumberSelector> {
int selectorValue = 1;
void updateValue(ifAdd) {
setState(() {
if (ifAdd) {
if (selectorValue < 9999) {
selectorValue++;
}
} else {
if (selectorValue > 1) {
selectorValue--;
}
}
});
}
#override
Widget build(BuildContext context) {
final ColorScheme colors = Theme.of(context).colorScheme;
return Column(
children: [
Text(widget.title,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
Row(
children: [
GestureDetector(
child: IconButton(
onPressed: () => updateValue(false),
icon: const Icon(Icons.remove),
style: styleContainedButton),
},
),
Text(selectorValue.toString()),
style: const TextStyle(fontSize: 16)),
GestureDetector(
child: IconButton(
onPressed: () => updateValue(true),
icon: const Icon(Icons.add),
style: styleContainedButton),
},
),
],
),
],
);
}
}

To get a variable from a stateful widget in flutter it needs a key that is linked to the state of the widget. Via the key, the variables can be accessed.
Define a widget you want to access from its parent and that contains any value:
class TestWidget extends StatefulWidget {
TestWidget({Key? key}) : super(key: key);
#override
State<TestWidget> createState() => TestWidgetState();
}
class TestWidgetState extends State<TestWidget> {
int? anyvalue;
#override
Widget build(BuildContext context) {
return Container();
}
}
Declare the key in the parent widget. Make sure the name of the state does not start with an underscore:
GlobalKey<TestWidgetState> _widget_key = GlobalKey();
Give the key to the widget in the build method of the parent widget:
TestWidget(
key: _widget_key,
)
Now the value of the child widget can be accessed in the parent widget via the key:
void afunction() {
print(_widget_key.currentState!.anyvalue);
}

Related

Unable to call a method in another dart file

I have a dart file with IndexedStack and the following function in the same file to change the stacks.
The file with method as follows-
class RootApp extends StatefulWidget with selectedTab {
#override
_RootAppState createState() => _RootAppState();
}
class _RootAppState extends State<RootApp> {
int pageIndex = 0;
List<Widget> pages = [
......
];
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
....
return AnimatedBottomNavigationBar(
.......
onTap: (index) {
selectedTab(index);
},
);
}
selectedTab(index) {
setState(() {
pageIndex = index;
});
}
}
There is this other dart file from which i would like to call selectedTab method with value of 0. The other file is as follows---
class CreatBudgetPage extends StatefulWidget {
#override
_CreatBudgetPageState createState() => _CreatBudgetPageState();
}
class _CreatBudgetPageState extends State<CreatBudgetPage> {
.......
FirebaseFirestore.instance
.collection('expenses/' + userId + '/' + todayDate)
.add({
....
}).then((_) {
print("collection created");
void rootApp() => selectedTab(0);
}).catchError((error) {
print(error);
});
}
How can i call this method from the other dart file?
P.S: I am a Newbie
You can pass the function callback as parameters from the source class to the intended class and invoke the function as you want.
here is a simple example.
class Example extends StatefulWidget {
const Example({Key? key}) : super(key: key);
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
int pageIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: IndexedStack(
index: pageIndex,
children: [
Tab1Page(),
Tab2Page(
/// solution 1
onPressed1: () {
selectedTab(0);
},
/// solution 2
// onPressed2: selectedTab,
),
],
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'TAB 1',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'TAB 2',
),
],
currentIndex: pageIndex,
selectedItemColor: Colors.amber[800],
onTap: selectedTab,
),
);
}
selectedTab(index) {
setState(() {
pageIndex = index;
});
}
}
class Tab1Page extends StatelessWidget {
const Tab1Page({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: Center(
child: Text(
"Tab1",
style: TextStyle(fontSize: 20),
),
),
);
}
}
class Tab2Page extends StatelessWidget {
final VoidCallback? onPressed1;
// final Function(int)? onPressed2;
const Tab2Page({
Key? key
/// solution 1
,
this.onPressed1,
/// solution 2
// this.onPressed2
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Tab2",
style: TextStyle(fontSize: 20),
),
ElevatedButton(
onPressed: () {
/// solution 1
if (onPressed1 != null) {
onPressed1!();
}
/// solution 2
// if(onPressed2!=null){
// onPressed2!(0);
//
// }
},
child: Text("click to navigate to another tab",
style: TextStyle(fontSize: 20)),
),
],
),
),
);
}
}

Changing the state Widget of one through another Widget

MyHomePageState:
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: bgColor,
body: ListView(
children: <Widget>[
Stack(
alignment: Alignment.topCenter,
children: <Widget>[
mainWidget(),
],
),
connectedStatusText(),
],
));
}
I'm trying to change the status of connectedStatusText() from mainWidget()!
My connectedStatus:
class connectedStatusText extends StatefulWidget
{
State<connectedStatusText> createState() {
return connectedStatus();
}
}
class connectedStatus extends State<connectedStatusText> {
String status = "IDLE";
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(text: 'Status:', style: connectedStyle, children: [
TextSpan(text: status, style: disconnectedRed)
]),
),
);
}
}
I want to change the $status text to "connected" through ontap of mainWidget().
mainWidget:
....
class mainWidget extends StatefulWidget
{
MyED createState() => new MyED();
}
class MyED extends State<mainWidget> {
child: new GestureDetector(
onTap: () => setState(() {
//change here
}
tried to set a global variable to connectedStatus:
GlobalKey<connectedStatus> key = GlobalKey<connectedStatus>();
and change by ontap...
child: new GestureDetector(
onTap: () => setState(() {
//change here
key.currentState.status = "CONNECTED";
}
)
}
but it does not work!
Any help for me to change this text through another place?
Please refer to below example code to update state using ValueNotifier and ValueListenableBuilder.
ValueNotifer & ValueListenableBuilder can be used to hold value and update widget by notifying its listeners and reducing number of times widget tree getting rebuilt.
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Screen2(),
);
}
}
class Screen2 extends StatefulWidget {
final String userId; // receives the value
const Screen2({Key key, this.userId}) : super(key: key);
#override
_Screen2State createState() => _Screen2State();
}
class _Screen2State extends State<Screen2> {
final ValueNotifier<bool> updateStatus = ValueNotifier(false);
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: Colors.blue,
body: ListView(
children: <Widget>[
Stack(
alignment: Alignment.topCenter,
children: <Widget>[
mainWidget(
updateStatus: updateStatus,
),
],
),
connectedStatusText(
updateStatus: updateStatus,
),
],
),
); // uses the value
}
}
class connectedStatusText extends StatefulWidget {
final ValueNotifier<bool> updateStatus;
connectedStatusText({
Key key,
this.updateStatus,
}) : super(key: key);
State<connectedStatusText> createState() {
return connectedStatus();
}
}
class connectedStatus extends State<connectedStatusText> {
String status = "IDLE";
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: /*
In order update widget we can use ValueListenableBuilder which updates the particular widget when the value changes (ValueNotifier value)
*/
ValueListenableBuilder(
valueListenable: widget.updateStatus,
builder: (context, snapshot, child) {
return RichText(
textAlign: TextAlign.center,
text: TextSpan(text: 'Status:', children: [
TextSpan(
text: (widget.updateStatus.value == true)
? "Active"
: status,
)
]),
);
}),
);
}
}
class mainWidget extends StatefulWidget {
final String userId; // receives the value
final ValueNotifier<bool> updateStatus;
mainWidget({
Key key,
this.userId,
this.updateStatus,
}) : super(key: key);
#override
_mainWidgetState createState() => _mainWidgetState();
}
class _mainWidgetState extends State<mainWidget> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
widget.updateStatus.value = !widget.updateStatus.value;
},
child: ValueListenableBuilder(
valueListenable: widget.updateStatus,
builder: (context, snapshot, child) {
return Text(snapshot.toString());
}));
// uses the value
}
}

Flutter - How to Extract Widget with onPressed setState inside?

I want to Extract a Widget with onPressed setState inside but I get the Message "Reference to an enclosing class method cannot be extracted."
Is there a way to do that?
I would like to divide my code into different widgets so that it remains clear. Here is simplified an example of the code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Calculator(),
);
}
}
class Calculator extends StatefulWidget {
#override
_CalculatorState createState() => _CalculatorState();
}
class _CalculatorState extends State<Calculator> {
var myValue = 0;
void calculate() {
myValue = 12;
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: TextButton(
onPressed: () {
setState(() {
calculate();
});
},
child: Text(
'Button 001',
),
),
),
TextOutput(myValue: myValue),
],
),
);
}
}
class TextOutput extends StatelessWidget {
const TextOutput({
Key key,
#required this.myValue,
}) : super(key: key);
final int myValue;
#override
Widget build(BuildContext context) {
return Container(
child: Text(
myValue.toString(),
),
);
}
}
The part I want to extract into a separate widget:
Container(
child: TextButton(
onPressed: () {
setState(() {
calculate();
});
},
child: Text(
'Button 001',
),
),
),
Flutter offers VoidCallback and Function(x) (where x can be a different type) for callback-style events between child and parent widgets.
Simply You can pass Function onPressed; via constructor
Here is your Extracted Container widget:
class ExtractedContainer extends StatelessWidget {
final Function onPressed;
const ExtractedContainer({
Key key, #required this.onPressed,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: TextButton(
onPressed: () {
onPressed();
},
child: Text(
'Button 001',
),
),
);
}
}
And Here How to use it:
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ExtractedContainer(onPressed: calculate,),
TextOutput(myValue: myValue),
],
),
);
}
Your full code example
import 'package:flutter/material.dart';
class MyApp2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Calculator(),
);
}
}
class Calculator extends StatefulWidget {
#override
_CalculatorState createState() => _CalculatorState();
}
class _CalculatorState extends State<Calculator> {
var myValue = 0;
void calculate() {
myValue = 12;
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ExtractedContainer(onPressed: calculate,),
TextOutput(myValue: myValue),
],
),
);
}
}
class ExtractedContainer extends StatelessWidget {
final Function onPressed;
const ExtractedContainer({
Key key, #required this.onPressed,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: TextButton(
onPressed: () {
onPressed();
},
child: Text(
'Button 001',
),
),
);
}
}
class TextOutput extends StatelessWidget {
const TextOutput({
Key key,
#required this.myValue,
}) : super(key: key);
final int myValue;
#override
Widget build(BuildContext context) {
return Container(
child: Text(
myValue.toString(),
),
);
}
}
Setstate is related to the widget you want to refresh its state. If you extract it to another place, then setState refers to the state of the new widget.
In your case, the setState will only change the state of the container encapsulating your widget which you are trying to extract and its children, it doesn't migrate upward.
Unless, you look for the state of the widget you want, using exact type, and then trigger the state there, but this is overkill, a lot harder, requires more code, than what you currently have.
You can use VoidCallback on extract widget to get onPressed event
class MyContainer extends StatelessWidget {
final VoidCallback onTap;
const MyContainer({
Key? key,
required this.onTap,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: TextButton(
onPressed: onTap,
child: Text(
'Button 001',
),
),
);
}
}
And use like
MyContainer(
onTap: () {
print("tapped");
setState(() {
calculate();
});
},
),

Cannot retrieve text from textfield with Riverpod

My code
class _GenericTextFieldState extends State<GenericTextField> {
#override
Widget build(BuildContext context) {
return CupertinoTextField(
controller: textFieldController,
padding: EdgeInsets.all(8),
prefix: Icon(Icons.email_outlined),
placeholder: widget.hint,
);
}
}
final textFieldController = TextEditingController();
final textFieldProvider = Provider<String> ( (_) => textFieldController.text);
the textFieldController is supplying the string to the textFieldProvider.
I am trying to get the string in another file using the consumer widget like so
class LoadingButton extends ConsumerWidget {
LoadingButton(this.buttonName);
final String buttonName;
#override
Widget build(BuildContext context,ScopedReader watch) {
String textInput = watch(textFieldProvider);
return RoundedLoadingButton(
successColor: mainColor,
errorColor: Colors.orange,
height: 40,
color: mainColor,
child: Text(buttonName, style: TextStyle(color: Colors.white)),
controller: _btnController,
onPressed: (){
mLog("Input from provider username: $textInput");
},
);
}
}
However the textInput variable is always empty.
What am I missing.
you can use onChanged with StateProvider
something like this
Full Example
final textFieldProvider = StateProvider<String>((ref) => "");
class Main extends StatelessWidget {
const Main({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ProviderScope(
child: Scaffold(
appBar: AppBar(
title: Text('Title'),
),
body: Column(
children: [
Container(),
_GenericTextFieldState(),
LoadingButton("Test")
],
),
),
);
}
}
class _GenericTextFieldState extends StatelessWidget {
const _GenericTextFieldState({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return CupertinoTextField(
padding: EdgeInsets.all(8),
prefix: Icon(Icons.email_outlined),
placeholder: "Input text",
onChanged: (text) {
context.read(textFieldProvider).state = text;
},
);
}
}
class LoadingButton extends ConsumerWidget {
LoadingButton(this.buttonName);
final String buttonName;
#override
Widget build(BuildContext context, ScopedReader watch) {
String textInput = watch(textFieldProvider).state;
return RaisedButton(
child: Text(buttonName, style: TextStyle(color: Colors.white)),
onPressed: () {
print("Input from provider username: $textInput");
},
);
}
}

Consumer not updating the state?

I am trying to create an Icon with a number indicator on top of it and the number indicator receives its data via a Consumer provider. The problem is that the state is not being updated by the consumer function and I don't understand why (if I update the state with a hot reload everything works just fine).
Here is the code for my main file:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => TestData())
// I use more providers but deleted them here for brevity
],
child: TestScreen3(),
),
);
}
}
The test screen 3 widget
class TestScreen3 extends StatefulWidget {
#override
_TestScreen3State createState() => _TestScreen3State();
}
class _TestScreen3State extends State<TestScreen3> {
#override
Widget build(BuildContext context) {
final testData = Provider.of<TestData>(context);
return Scaffold(
appBar: AppBar(
title: Text('Test app 3'),
actions: [
Consumer<TestData>(builder: (_, data, __) {
return IconButton(
icon: Badge(num: data.items.length.toString()),
onPressed: () => print(data.items.length));
})
],
),
body: Center(
child: ElevatedButton(
child: Text('Increase'),
onPressed: () {
testData.addItem();
},
),
),
);
}
}
The badge widget
class Badge extends StatelessWidget {
Badge({#required this.num});
final String num;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Icon(Icons.assessment),
Positioned(
child: Container(
padding: EdgeInsets.all(2),
child: Text(
num,
style: TextStyle(fontSize: 8),
textAlign: TextAlign.center,
),
constraints: BoxConstraints(
minHeight: 12,
minWidth: 12,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.red,
),
),
),
],
);
}
}
and the data model I am using
class Item {
Item(this.id);
final String id;
}
class TestData with ChangeNotifier {
List<Item> _items = [];
List<Item> get items => [..._items];
void addItem() {
_items.add(Item(DateTime.now().toString()));
}
notifyListeners();
}
The imports work just fine, I left them out for brevity. I followed along a this tutorial: https://www.udemy.com/course/learn-flutter-dart-to-build-ios-android-apps/ and it uses a key argument for the badge that looks like this:
class Badge extends StatelessWidget {
const Badge({
Key key,
#required this.child,
#required this.value,
this.color,
}) : super(key: key);
final Widget child;
final String value;
final Color color;
However, the use of key or super is not explained in the tutorial and when I add these parameters to my code they don't seem to make a change.
Many thanks in advance, I probably missed something super obvious...
Add notifyListeners(); inside addItem() method
void addItem() {
_items.add(Item(DateTime.now().toString()));
notifyListeners();
}