How to get the height of widget built recursively? - flutter

I have a widget which is built recursively and i need to find its height for another widget in the same row containing the recursively called widget.
I tried getting the height of the parent using LayoutBuilder but it doesnt work because of the expanded widget.
I have tried to use this method to find the height using key before the widget is build but it doesnt work for the recursive case.
CODE
class MyWidget extends StatefulWidget {
const MyWidget({
Key key,
}) : super(key: key);
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
TextEditingController _textEditingController;
GlobalKey _keyRed = GlobalKey();
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
_textEditingController = TextEditingController();
super.initState();
}
_afterLayout(_) {
_getSizes();
}
#override
void dispose() {
_textEditingController.dispose();
super.dispose();
}
_getSizes() {
final RenderBox renderBoxRed = _keyRed.currentContext.findRenderObject();
final sizeRed = renderBoxRed.size;
print("SIZE of Red: $sizeRed");
return sizeRed.height;
}
#override
Widget build(BuildContext context) {
// condition for recursion to stop avoided here for brevity
return Container(
child: Row(
children: [
getLeadingWidget(),
Expanded(
key: _keyRed,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
getTextField(note),
Column(
children:
getChildren()
)
],
),
),
],
),
);
}
getLeadingWidget() {
return LayoutBuilder(
builder: (context, constraints) => Container(
height: constraints.constrainHeight(),
child: Container(...)
));
}
getTextField(NotesModel note) {
return TextFormField(
keyboardType: TextInputType.multiline,
maxLines: null,
controller: _textEditingController..text = 'TEST',
);
}
getChildrenNotes(int noteId) {
List<Widget> childrenNotes = [];
for (...) {
// widget called recursively
childrenNotes.add(MyWidget());
}
return childrenNotes;
}
}
Diagram with relevant widgets:

Related

How to make SliverAppBar overgrow inside NestedScrollView in Flutter?

I am using a NestedScrollView with a SliverAppBar and a TabBar. The scrolling works well when scrolling down scrollable content like ListView or SingleChildScrollView, but whenever I try to scroll up and get the SliverAppBar to overgrow, nothing happens.
In the SliverPersistentHeaderDelegate of the SliverAppBar I am overwriting the following method like so:
#override
OverScrollHeaderStretchConfiguration get stretchConfiguration =>
OverScrollHeaderStretchConfiguration();
This is the class of the screen I am talking about:
class ProfileScreen extends ConsumerStatefulWidget {
const ProfileScreen({
super.key,
required this.userId
});
final String userId;
#override
ConsumerState<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends ConsumerState<ProfileScreen> with
TickerProviderStateMixin {
late final TabController tabController;
#override
void initState() {
super.initState();
tabController = TabController(length: 3, vsync: this);
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final AsyncValue<List<BasicEventModel>> eventsAsync =
ref.watch(profileEventsProvider(widget.userId));
return AnnotatedRegion<SystemUiOverlayStyle>(
value: Themes.systemUiOverlayStyle(context),
child: Scaffold(
backgroundColor: Themes.color(context, light: Palette.white, dark:
Palette.gray900),
body: ExtendedNestedScrollView(
pinnedHeaderSliverHeightBuilder: () => topPadding + kToolbarHeight,
headerSliverBuilder: (context, innerBoxIsScrolled) => [
ProfileAppBar()
],
body: Column(
children: [
ProfileTabBar(controller: tabController),
Expanded(
child: TabBarView(
controller: tabController,
children: [
eventsAsync.when(
data: (List<BasicEventModel> events) =>
ProfileEventsList(events: events),
error: (Object e, StackTrace s) =>
_eventsText('profile_events_error'.tr()),
loading: () => const ProfileEventsShimmer()
),
// other Widgets for the other tabs
]
)
)
]
)
)
)
);
}
}

how to call child widget function from parent widget

All data is contains in Parent widget and showing in child widget
I want to call function in child widget when parent state change
child widget function which is stateFull widget
void changeSelectedJodi(i) {
_jodiScrollController.animateTo(50.0 * i,
duration: Duration(seconds: 2), curve: Curves.fastOutSlowIn);
}
Parent widget
child: JodiDataWidget(
this._jodies, <= data is here
this.selectedJodi, <= call function changeSelectedJodi in child widget when change
),
how to achieve this method
You still don't want to access your child from your parent. Flutter control flow goes only one way and there are good reasons for that, which your should respect.
The question is, then, how can my child know that my parent has changed? For this, you have to update the child from the parent (as always) then use didUdpdateWidget in the child to catch a widget change and react to it.
Here is a small example:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: ParentWidget()));
}
class ParentWidget extends StatefulWidget {
#override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
/// The item of the list to display
///
/// This will be changed randomly by clicking the button
int selectedIndex = 0;
#override
Widget build(BuildContext context) {
return Material(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Center(
child: ChildWidget(
selectedIndex: selectedIndex,
),
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: ElevatedButton(
onPressed: () => setState(() => selectedIndex = Random().nextInt(100)),
child: Center(
child: Text('Press my to move the list'),
),
),
),
],
),
);
}
}
class ChildWidget extends StatefulWidget {
/// The item of the list to display
///
/// Changed randomly by the parent
final int selectedIndex;
const ChildWidget({
Key? key,
required this.selectedIndex,
}) : super(key: key);
#override
State<ChildWidget> createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
/// The colors of the items in the list
final _itemsColors = List.generate(
100,
(index) => getRandomColor(),
);
static Color getRandomColor() =>
Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
final _controller = PageController();
void functionOfChildWidget() {
_controller.animateToPage(
widget.selectedIndex,
duration: Duration(milliseconds: 200),
curve: Curves.easeIn,
);
}
/// Here is the important part: When data is set from the parent,
/// move this widget
#override
void didUpdateWidget(covariant ChildWidget oldWidget) {
// If you want to react only to changes you could check
// oldWidget.selectedIndex != widget.selectedIndex
functionOfChildWidget();
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
height: 200,
child: PageView.builder(
controller: _controller,
padEnds: false,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.all(50),
color: _itemsColors[index],
width: 100,
);
},
itemCount: _itemsColors.length,
),
);
}
}
You can use something like this:
import 'package:flutter/widgets.dart';
class ChangeCallWidget extends StatefulWidget {
final dynamic value;
final VoidCallback onChange;
final Widget child;
const ChangeCallWidget({
super.key,
required this.value,
required this.onChange,
required this.child,
});
#override
State<ChangeCallWidget> createState() => _ChangeCallWidgetState();
}
class _ChangeCallWidgetState extends State<ChangeCallWidget> {
#override
void didUpdateWidget(oldWidget) {
if (oldWidget.value != widget.value) widget.onChange();
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) => widget.child;
}
And use it like this to monitor changes of the _jodies and trigger the onChange if it changes:
ChangeCallWidget(
value: _jodies,
onChange: selectedJodi,
)

how to change the state of one statefulwidget from another in flutter?

I have a stateful widget which is conditionally rendering two childs inside stack, and i want to change the condition of the rending from a third child . any idea ?
Parent code :
class MapPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body:Body()
);
}
}
class Body extends StatefulWidget {
final String showScreen;
const Body({
Key key,
this.showScreen="post",
}) : super(key:key);
#override
_BodyState createState() => _BodyState();
}
class _BodyState extends State<Body> {
Widget _conditionedWidget(){
if(this.widget.showScreen=="map"){
return MapScreen();
}else if(this.widget.showScreen == "post"){
return PostScreen();
}
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
DrawerScreen(),
_conditionedWidget(),
],
);
}
}
child code
class DrawerScreen extends StatefulWidget {
#override
_DrawerScreenState createState() => _DrawerScreenState();
}
class _DrawerScreenState extends State<DrawerScreen> {
#override
Widget build(BuildContext context) {
return Container(
color:kPrimaryColor,
padding:EdgeInsets.only(top:70),
child:Column(
children: <Widget>[
Row(
children: <Widget>[
SizedBox(width:20.0),
CircleAvatar(),
SizedBox(width:10.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Biswas Sampad',style:TextStyle(
color:Colors.white,
fontWeight: FontWeight.bold,
fontSize: 20.0,
)),
Text('#biswassampad',style:TextStyle(
color:Colors.grey[200],
fontSize: 15.0,
))
],
)
],
),
Container(
padding: EdgeInsets.symmetric(horizontal: 20,vertical:20),
margin: EdgeInsets.symmetric(vertical:30),
child: Column(
children: <Widget>[
MenuButton(icon:Icons.style, name:'Explore',action:(){
print('showing maop');
}),
MenuButton(icon:Icons.tag_faces, name:'Profile',action:(){
print('showing profile');
}),
MenuButton(icon:Icons.people, name:'People',action:(){
print('showing People');
}),
MenuButton(icon:Icons.speaker_notes, name:'Messages',action:(){
print('showing messages');
}),
MenuButton(icon:Icons.notifications, name:'Notifications',action:(){
print('showing Notifications');
}),
MenuButton(icon:Icons.satellite,name:'Settings',action:(){
print('showing settings');
})
],
),
),
LogoutSection()
],
)
);
}
}
So basically i want to change the showScreen value of the parent widget from DrawerScreen>MenuButton>action ?
any idea how to do it !! Thanks in advance.
You can use the Function in "DrawerScreen" widget like this :
write this code into the header of the class :
final Function onChangeState = Function();
DrawerScreen({#rquired onChangeState});
and in MenuButton call onChangeState function , like this:
MenuButton(icon:Icons.satellite,name:'Settings',action:(){
widget.onChangeState("Settings");
})
and change old code in Body widget to :
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
DrawerScreen(onChangeState : (newState){
setState(){
this.widget.showScreen = newState;
};
}),
_conditionedWidget(),
],
);
}

change variables with setState in Flutter

I have an issue with setState() in Flutter. I just write a simple program that have a container and a button , the color of container is global variable mycolor and i change it in on_pressed function of button with setState but its doesn't change.
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: _Home(),));
Color bgColor = Colors.red;
class _Home extends StatefulWidget {
#override
__HomeState createState() => __HomeState();
}
class __HomeState extends State<_Home> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
//First Widget
Container(
width: 200,
height: 200,
color: bgColor,
),
//Second Widget
SecondWidget()
],
);
}
}
class SecondWidget extends StatefulWidget {
#override
_SecondWidgetState createState() => _SecondWidgetState();
}
class _SecondWidgetState extends State<SecondWidget> {
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("Change color"),
onPressed: (){
setState(() {
bgColor = Colors.green;
});
},
);
}
}
image of my program
You are calling setState in _SecondWidgetState not in __HomeState, so only SecondWidget redraws and it does not depend on bgColor.
What you can do here: the easiest option would be to pass a callback function from __HomeState to SecondWidget, which will call setState inside __HomeState.
Example code:
class __HomeState extends State<_Home> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
//First Widget
Container(
width: 200,
height: 200,
color: bgColor,
),
//Second Widget
SecondWidget(callSetState);
],
);
}
void callSetState() {
setState((){}); // it can be called without parameters. It will redraw based on changes done in _SecondWidgetState
}
}
class SecondWidget extends StatefulWidget {
final Function onClick;
SecondWidget(this.onClick);
#override
_SecondWidgetState createState() => _SecondWidgetState();
}
class _SecondWidgetState extends State<SecondWidget> {
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("Change color"),
onPressed: (){
bgColor = Colors.green;
widget.onClick();
},
);
}
}
This is simple solution for two widgets, but you will have problems if you will try to manage state on larger scale. I recommend you to read articles about state management in flutter. This one can be a good start.
You need to pass that variable to your sibling widget SecondWidget().
First you declare it on your SecondWidget like this:
class SecondWidget extends StatefulWidget {
Color backgroundColor;
SecondWidget({Key key, #required this.backgroundColor}) : super(key: key);
#override
_SecondWidgetState createState() => _SecondWidgetState();
}
You need to pass that color from _HomeState to SecondWidget, you do it like this:
class __HomeState extends State<_Home> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
//First Widget
Container(
width: 200,
height: 200,
color: bgColor,
),
//Second Widget
SecondWidget(backgroundColor: bgColor) // Here you pass that color
],
);
}
}
Then on your SecondWidgetState, you can update your other widget color using setState(), like this:
setState(() {
widget.backgroundColor = Colors.blue;
});
Hope this helps fix your issue.

Flutter: Dissmissible widgets disable Tabview drag detection

I have two tabs, the left tab having a list of tiles and the right tab having nothing. The user can drag the screen from right-to-left or left-to-right to get from one tab to the other.
The left tab has a list of dismissible tiles that only have "direction: DismissDirection.startToEnd" (from left-to-right) enabled so that the user can still theoretically drag (from right-to-left) to go to the right tab.
However, I believe the Dismissible widget still receives the right-to-left drag information which is disabling the TabView drag to change tabs.
In essence, how do I allow the right-to-left drag to be detected by only the TabView and not the Dismissible item?
If an explicit solution/example with code snippets can be given, I would very very much appreciate the help!
Here's a paste for your main.dart file:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/semantics.dart';
void main() {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: MainPage(),
);
}
}
class MainPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
_tabController = TabController(vsync: this, length: 2, initialIndex: 1);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
color: Colors.black,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: TabBarView(
controller: _tabController,
children: <Widget>[
TabWithSomething(),
TabWithNothing(),
],
),
),
],
),
),
),
);
}
}
class TabWithNothing extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Container(
child: Text("Swipe from left-to-right!"),
),
);
}
}
class TabWithSomethingItem implements Comparable<TabWithSomethingItem> {
TabWithSomethingItem({this.index, this.name, this.subject, this.body});
TabWithSomethingItem.from(TabWithSomethingItem item)
: index = item.index,
name = item.name,
subject = item.subject,
body = item.body;
final int index;
final String name;
final String subject;
final String body;
#override
int compareTo(TabWithSomethingItem other) => index.compareTo(other.index);
}
class TabWithSomething extends StatefulWidget {
const TabWithSomething({Key key}) : super(key: key);
static const String routeName = '/material/leave-behind';
#override
TabWithSomethingState createState() => TabWithSomethingState();
}
class TabWithSomethingState extends State<TabWithSomething> {
List<TabWithSomethingItem> TabWithSomethingItems;
void initListItems() {
TabWithSomethingItems =
List<TabWithSomethingItem>.generate(10, (int index) {
return TabWithSomethingItem(
index: index,
name: 'Item $index',
subject: 'Swipe from left-to-right to delete',
body: "Swipe from right-to-left to go back to old tab");
});
}
#override
void initState() {
super.initState();
initListItems();
}
void _handleDelete(TabWithSomethingItem item) {
setState(() {
TabWithSomethingItems.remove(item);
});
}
#override
Widget build(BuildContext context) {
Widget body;
body = ListView(
children:
TabWithSomethingItems.map<Widget>((TabWithSomethingItem item) {
return _TabWithSomethingListItem(
item: item,
onDelete: _handleDelete,
dismissDirection: DismissDirection.startToEnd,
);
}).toList());
return body;
}
}
class _TabWithSomethingListItem extends StatelessWidget {
const _TabWithSomethingListItem({
Key key,
#required this.item,
#required this.onDelete,
#required this.dismissDirection,
}) : super(key: key);
final TabWithSomethingItem item;
final DismissDirection dismissDirection;
final void Function(TabWithSomethingItem) onDelete;
void _handleDelete() {
onDelete(item);
}
#override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Semantics(
customSemanticsActions: <CustomSemanticsAction, VoidCallback>{
const CustomSemanticsAction(label: 'Delete'): _handleDelete,
},
child: Dismissible(
key: ObjectKey(item),
direction: dismissDirection,
onDismissed: (DismissDirection direction) => _handleDelete(),
background: Container(
color: theme.primaryColor,
child: const ListTile(
leading: Icon(Icons.delete, color: Colors.white, size: 36.0))),
child: Container(
decoration: BoxDecoration(
color: theme.canvasColor,
border: Border(bottom: BorderSide(color: theme.dividerColor))),
child: ListTile(
title: Text(item.name),
subtitle: Text('${item.subject}\n${item.body}'),
isThreeLine: true),
),
),
);
}
}
UPDATE:
I'm thinking we could change the "dismissible.dart" file to change the "TabControlller", but i'm not sure how I might do that.
In the "dismissible.dart" file:
...
void _handleDragUpdate(DragUpdateDetails details) {
if (!_isActive || _moveController.isAnimating)
return;
final double delta = details.primaryDelta;
if (delta < 0) print(delta); // thinking of doing something here
...