Changing an attribute in a parent widget from a child widget - android

I am trying to create an app. I want to change an attribute in a parent class (so it can display a Visibility item, SlidingUpPanel) after submitting a query in the child class (which is a search bar).
I have tried using a callback function and followed How can I pass the callback to another StatefulWidget? but it doesn't seem to do anything when I submit my query in the search bar. Previously, I have also tried to use Navigator.push to call SlidingUpPanel directly but it resulted in a black screen with only SlidingUpPanel.
Parent class (Map):
class MapPage extends StatefulWidget {
const MapPage({Key? key}) : super(key: key);
#override
_MapPageState createState() => _MapPageState();
}
class _MapPageState extends State<MapPage> {
bool IsVisible = false;
void callbackFunction() {
setState(() {
IsVisible = true;
});
}
...
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(
children: [
...
Visibility(
visible: IsVisible,
child: SlidingUpPanel()),
SearchPage(callbackFunction),
]),
);
}
}
Child class (Search Bar):
class SearchPage extends StatefulWidget {
final Function callback;
SearchPage(this.callback);
#override
_SearchPageState createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
...
#override
Widget build(BuildContext context) {
return FloatingSearchBar(
...
onSubmitted: (query) {
// callback here, but doesn't seem to do anything
widget.callback;
controller.close();
},
...
);
}
}

To actually call a function (execute it) you must use ().
Try calling the callback function like this:
onSubmitted: (query) {
// use () to actually call the function
widget.callback();
controller.close();
},

Related

passing value to the classes without navigating

For the code below, on the press of the icon of the first class, I want to send data to the second class. First-class just contains the data that needs to pass to the second class. There is no way or not allowed to navigate from first to second class. The only option to navigate to the second class is from the third class. I tried creating a constructor in the second class and implemented
second page(data:_getData.text). But there is no way i am getting the value. I am just getting null.
**First class**
onPressed: () { setState(() => _getData.text);
**SecondPage(data:_getData.text);**
}
**Second class**
class SecondPage extends StatefulWidget {
String? data;
SecondPage({this.data});
#override
_SecondPageState createState() => _SecondPageState(data);
}
class _SecondPageState extends State<SecondPage> {
String? data;
_SecondPageState(this.data);
**Third class**
Get.to(SecondPage)
if you change a lot of states, you would use bloc or any state management. because it is good for the app. if you want only this thing, you use the below code.
First Screen
class FirstScreen extends StatefulWidget {
const FirstScreen({Key? key}) : super(key: key);
#override
State<FirstScreen> createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
String title = "no value_f";
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
ElevatedButton(
onPressed: () {
setState(() {
title = "value";
});
},
child: Text("Press")),
SecondScreen(title: title),
],
),
);
}
}
Second Screen
class SecondScreen extends StatefulWidget {
String title;
SecondScreen({Key? key, required this.title}) : super(key: key);
#override
State<SecondScreen> createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
#override
void initState() {
widget.title = "no value";
super.initState();
}
#override
Widget build(BuildContext context) {
return Text(widget.title);
}
}

How to send a "button is pressed signal" to a higher class in widget tree hierarchy in flutter?

In my app tree hierarchy, I have a Stateful widget that represents the elements of a list. Let's call this class Item. This Widget contains an Image, some text, and a remove button that should remove the element when it's pressed. Later in the code, I have another Stateful class that holds the ListView and a List of Item which the ListView is created based on it.
My problem is that the onPressed() attribute of every Item is placed inside the item's class and not the upper class (the ListView) in which the remove operation should be handled. How can I fix this? How can I send a signal whenever the button is pressed to the parent widget in order to initiate removing the item?
import 'package:flutter/material.dart';
class Item extends StatefulWidget {
const Item({ Key? key }) : super(key: key);
#override
_ItemState createState() => _ItemState();
}
class _ItemState extends State<Item> {
#override
Widget build(BuildContext context) {
return Container(
child: Row(children: [
Image(),
Text('abc'),
Text('abc'),
Text('abc'),
ElevatedButton(onPressed: ????? , child: Text('RemoveButton'))
],),
);
}
}
class ListOfItems extends StatefulWidget {
const ListOfItems({ Key? key }) : super(key: key);
#override
_ListOfItemsState createState() => _ListOfItemsState();
}
class _ListOfItemsState extends State<ListOfItems> {
List<Item> ls = [];
int numOfItems = 0;
#override
Widget build(BuildContext context) {
return ListView(children: ls,)
}
}
Try like this
import 'package:flutter/material.dart';
class Item extends StatefulWidget {
final void Function(dynamic data) onPressedHandler;
const Item({ #required this.onPressedHandler, Key? key }) : super(key: key);
#override
_ItemState createState() => _ItemState();
}
class _ItemState extends State<Item> {
#override
Widget build(BuildContext context) {
return Container(
child: Row(children: [
Image(),
Text('abc'),
Text('abc'),
Text('abc'),
ElevatedButton(onPressed: () {
final data = 'Any additional data you want to paas';
widget.onPressedHandler(data);
} , child: Text('RemoveButton'))
],),
);
}
}
class ListOfItems extends StatefulWidget {
const ListOfItems({ Key? key }) : super(key: key);
#override
_ListOfItemsState createState() => _ListOfItemsState();
}
class _ListOfItemsState extends State<ListOfItems> {
List<Item> ls = [];
int numOfItems = 0;
onPressedHandler(data) {
}
#override
Widget build(BuildContext context) {
return ListView(children: [
Item(onPressedHandler: onPressedHandler),
],);
}
}

Pass value from state of stateful class to different widget?

I have a function:
class LoginPage extends StatefulWidget {
static const routeName = '/auth';
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
Inside class _LoginPageState extends State I have a bool variable 'registered' that changes within setState to true when user registers for the first time. How can I pass this value to my main function whenever it gets changed? Additionally, this variable needs to be changed from different place as well to true. Based on it I wanna show different screen to my user - something like below:
home: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
child: registered ? OnboardingPage() : HomePage(),
),
),
Could you show me some code or guide me? Thanks for any help!
Your Login page
class LoginPage extends StatefulWidget {
LoginPage({Key key}) : super(key: key);
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
bool registered;
// ...
void gotoSecondPage() {
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => SecondPage(registered),));
}
}
Your other page
class SecondPage extends StatefulWidget {
bool registered;
SecondPage(this.registered);
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
child: widget.registered ? OnboardingPage() : HomePage(),
),
);
}
}
If this variable is changed inside _LoginPageState you can redirect the user according to its value as below:
// BEGIN: TODO
// registered = true;
// END: TODO
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => registered ? OnboardingPage() : HomePage()),
);
You can use a combination of StreamBuilder and Provider to first get the data asynchronously, and then send that data to different places in your widget tree. Something like:
final StreamController _controller = StreamController();
// Provide this _controller in your top level widget tree
return Provider<StreamController>(
create: (context) => _controller,
child: MaterialApp(...),
);
In your _LoginPageState, whenever you get a new registered value:
// ... other lines
var registered = async getUserRegisterStatus() // sample method
// Add the new value to Stream
Provider.of<StreamController>.add(registered);
You can then use this controller to notify any below widget of the change of value:
// Sample place that needs the value
home: Scaffold(
resizeToAvoidBottomInset: false,
body: StreamBuilder<bool>(
initialValue: false
stream: Provider.of<StreamController>.stream,
builder: (context, snapshot) {
if (!snapshot.hasData) return Container(child: CircularProgressIndicator());
var registered = snapshot.data;
return Container(
child: registered ? OnboardingPage() : HomePage(),
);
}
),
),

how to use setState() from another class or file?

I would like to change the body of HomeScreen from CustomDialog and it is another class. how can I do this ? I tried it in anyways but I can't do this.
this is the main file
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: HomeScreen(),));
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return SafeArea(child:Column(
children: <Widget>[
Text( Global.number.toString() ),
RaisedButton(child: Text("Click"),
onPressed: (){
showDialog(context: context,builder: (context){
return CustomDialog();
});
},)
],
));
}
}
And this is the Another File to Store global variable
class Global {
static double number = 10.0;
}
And this file is for Dialog
class CustomDialog extends StatefulWidget {
#override
_CustomDialogState createState() => _CustomDialogState();
}
class _CustomDialogState extends State<CustomDialog> {
#override
Widget build(BuildContext context) {
return Dialog(child: FlatButton(
child: Icon(Icons.add_circle,size: 30,),
onPressed: (){
setState(() {
Global.number++;
});
},
),);
}
}
You can pass the setState method of HomeScreen down to CustomDialog. I have shared a full working example based on the code you provided below.
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: HomeScreen(),));
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
void state() {
setState((){});
}
#override
Widget build(BuildContext context) {
return SafeArea(child:Column(
children: <Widget>[
Text( Global.number.toString() ),
RaisedButton(child: Text("Click"),
onPressed: (){
showDialog(context: context,builder: (context){
return CustomDialog(state);
});
},)
],
));
}
}
class Global {
static double number = 10.0;
}
class CustomDialog extends StatefulWidget {
final Function state;
CustomDialog(this.state);
#override
_CustomDialogState createState() => _CustomDialogState();
}
class _CustomDialogState extends State<CustomDialog> {
#override
Widget build(BuildContext context) {
return Dialog(child: FlatButton(
child: Icon(Icons.add_circle,size: 30,),
onPressed: (){
Global.number++;
widget.state();
},
),);
}
}
As you can see, I create a method parameter for CustomDialog and call that method following the change of Global.number. I created a wrapper to the setState function
void state() {
setState((){});
}
in HomeScreen and passed that method as the parameter to CustomDialog.
The state you set in CustomDialog is, well... the state of the dialog, not of the home screen.
To notify the home screen that some data changed, you can use a ChangeNotifierProvider to provide this data in a common super widget of home screen and the dialog, subscribe the data in the home screen, then access the data in the dialog and change it, then home screen will rebuild automatically.
Please check out Simple app state management for more detail.

Flutter setState of child widget without rebuilding parent

I have a parent that contain a listView and a floatingActionButton i would like to hide the floatingActionButton when the user starts scrolling i have managed to do this within the parent widget but this requires the list to be rebuilt each time.
I have moved the floatingActionButton to a separate class so i can update the state and only rebuild that widget the problem i am having is passing the data from the ScrollController in the parent class to the child this is simple when doing it through navigation but seams a but more awkward without rebuilding the parent!
A nice way to rebuild only a child widget when a value in the parent changes is to use ValueNotifier and ValueListenableBuilder. Add an instance of ValueNotifier to the parent's state class, and wrap the widget you want to rebuild in a ValueListenableBuilder.
When you want to change the value, do so using the notifier without calling setState and the child widget rebuilds using the new value.
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
ValueNotifier<bool> _notifier = ValueNotifier(false);
#override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(onPressed: () => _notifier.value = !_notifier.value, child: Text('toggle')),
ValueListenableBuilder(
valueListenable: _notifier,
builder: (BuildContext context, bool val, Widget? child) {
return Text(val.toString());
}),
],
);
}
#override
void dispose() {
_notifier.dispose();
super.dispose();
}
}
For optimal performance, you can create your own wrapper around Scaffold that gets the body as a parameter. The body widget will not be rebuilt when setState is called in HideFabOnScrollScaffoldState.
This is a common pattern that can also be found in core widgets such as AnimationBuilder.
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: MyHomePage()));
class MyHomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
ScrollController controller = ScrollController();
#override
Widget build(BuildContext context) {
return HideFabOnScrollScaffold(
body: ListView.builder(
controller: controller,
itemBuilder: (context, i) => ListTile(title: Text('item $i')),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
controller: controller,
);
}
}
class HideFabOnScrollScaffold extends StatefulWidget {
const HideFabOnScrollScaffold({
Key key,
this.body,
this.floatingActionButton,
this.controller,
}) : super(key: key);
final Widget body;
final Widget floatingActionButton;
final ScrollController controller;
#override
State<StatefulWidget> createState() => HideFabOnScrollScaffoldState();
}
class HideFabOnScrollScaffoldState extends State<HideFabOnScrollScaffold> {
bool _fabVisible = true;
#override
void initState() {
super.initState();
widget.controller.addListener(_updateFabVisible);
}
#override
void dispose() {
widget.controller.removeListener(_updateFabVisible);
super.dispose();
}
void _updateFabVisible() {
final newFabVisible = (widget.controller.offset == 0.0);
if (_fabVisible != newFabVisible) {
setState(() {
_fabVisible = newFabVisible;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: widget.body,
floatingActionButton: _fabVisible ? widget.floatingActionButton : null,
);
}
}
Alternatively you could also create a wrapper for FloatingActionButton, but that will probably break the transition.
I think using a stream is more simpler and also pretty easy.
You just need to post to the stream when your event arrives and then use a stream builder to respond to those changes.
Here I am showing/hiding a component based on the focus of a widget in the widget hierarchy.
I've used the rxdart package here but I don't believe you need to. also you may want to anyway because most people will be using the BloC pattern anyway.
import 'dart:async';
import 'package:rxdart/rxdart.dart';
class _PageState extends State<Page> {
final _focusNode = FocusNode();
final _focusStreamSubject = PublishSubject<bool>();
Stream<bool> get _focusStream => _focusStreamSubject.stream;
#override
void initState() {
super.initState();
_focusNode.addListener(() {
_focusStreamSubject.add(_focusNode.hasFocus);
});
}
#override
void dispose() {
_focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
_buildVeryLargeComponent(),
StreamBuilder(
stream: _focusStream,
builder: ((context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData && snapshot.data) {
return Text("keyboard has focus")
}
return Container();
}),
)
],
),
);
}
}
You can use StatefulBuilder and use its setState function to build widgets under it.
Example:
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int count = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
// put widget here that you do not want to update using _setState of StatefulBuilder
Container(
child: Text("I am static"),
),
StatefulBuilder(builder: (_context, _setState) {
// put widges here that you want to update using _setState
return Column(
children: [
Container(
child: Text("I am updated for $count times"),
),
RaisedButton(
child: Text('Update'),
onPressed: () {
// Following only updates widgets under StatefulBuilder as we are using _setState
// that belong to StatefulBuilder
_setState(() {
count++;
});
})
],
);
}),
],
);
}
}

Categories

Resources