So I am developing this app, where this code represents one screen of it. Basically it has a graph and displays some information related to it. What I am doing is that in the tab bar, based on the tab I have selected, I am updating the height of the parent container.
I have done this using SetState() where the UI is rendered again based on the new value for the container height. Everything seems fine, but on every SetState() call, the chart gets rendered again, which makes it disappear from the screen for about a second.
What I want to do is let the chart be where it is and not let anything happen to it at all. When I am switching tabs I want the chart to be visible.
I thought of separating the chart elements state from the rest of the elements and created a stateful widget for that part but it doesn't seem to be working.
Here is the code :
class IndChartWidget extends StatefulWidget {
const IndChartWidget({super.key, required this.id});
final String id;
@override
State<IndChartWidget> createState() => _IndChartWidgetState();
}
class _IndChartWidgetState extends State<IndChartWidget> {
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
height: 250,
child: ChartWidget(id: widget.id),
);
}
}
class CoinScreen extends StatefulWidget {
final String id;
final String name;
final String sym;
final double price;
final double per;
final String img;
const CoinScreen({super.key, required this.id, required this.name, required this.sym, required this.price, required this.per, required this.img});
static Map<String, String> descData = {};
@override
State<CoinScreen> createState() => _CoinScreenState();
}
class _CoinScreenState extends State<CoinScreen> {
String? desc = '';
double containerHeight = 300;
@override
void initState() {
super.initState();
if (CoinScreen.descData.containsKey(widget.id)) {
desc = CoinScreen.descData[widget.id];
} else {
API().getDesc(widget.id).then((value) {
setState(() {
CoinScreen.descData[widget.id] = value;
desc = value;
});
});
}
}
void containerheight(index) {
setState(() {
containerHeight = index == 0 ? 300 : 800;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
centerTitle: true,
leading: IconButton(
icon: const Icon(FontAwesomeIcons.backward),
onPressed: () {
Navigator.pop(context);
},
),
),
body: ListView(
children: [
Padding(
padding: const EdgeInsets.only(left: 10, right: 10, bottom: 40),
child: Column(
children: [
Container(
margin: const EdgeInsets.only(top: 60),
width: double.infinity,
height: 450,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.black.withOpacity(0.2),
Colors.deepPurple.withOpacity(0.2),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [0.1, 0.7],
),
border: Border.all(
width: 3,
color: Colors.deepPurple.withOpacity(0.2),
),
borderRadius: BorderRadius.circular(10),
),
child: Column(
children: [
SizedBox(
height: 70,
child: Image.network(widget.img),
),
SizedBox(
child: Text(
'\$${widget.price.toString()}',
style: const TextStyle(
fontSize: 40,
),
),
),
SizedBox(
child: Text(
widget.per.toStringAsFixed(4),
style: Theme.of(context).textTheme.bodySmall,
),
),
IndChartWidget(id: widget.id)
],
),
),
DefaultTabController(
length: 2,
child: Container(
width: double.infinity,
height: containerHeight,
margin: const EdgeInsets.only(
top: 30,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TabBar(
indicatorColor: Colors.transparent,
labelColor: const Color.fromRGBO(127, 157, 255, 1),
unselectedLabelColor: Colors.white.withOpacity(0.8),
splashFactory: NoSplash.splashFactory,
onTap: (value) {
containerheight(value);
},
tabs: const [
Tab(
child: Text(
'About',
style: TextStyle(fontSize: 18),
),
),
Tab(
child: Text(
'News',
style: TextStyle(fontSize: 18),
),
),
],
),
const Divider(
color: Color.fromRGBO(127, 157, 255, 0.3),
thickness: 2,
),
Expanded(
child: TabBarView(
children: [
Container(
margin: const EdgeInsets.only(top: 20),
child: Text(
desc!,
maxLines: 11,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
),
SizedBox(
child: ListView.builder(
itemBuilder: (context, index) {
return Container(
margin: const EdgeInsets.only(bottom: 50),
child: Column(
children: [
Container(
width: double.infinity,
height: 200,
color: Colors.grey,
),
Container(
height: 50,
width: double.infinity,
margin: const EdgeInsets.only(top: 10),
child: Row(
children: [
Flexible(
flex: 1,
child: Container(
color: Colors.grey,
),
),
Flexible(
flex: 4,
child: Container(
color: Colors.grey,
margin: const EdgeInsets.only(left: 5),
),
),
],
),
),
],
),
);
},
itemCount: 4,
),
),
],
),
),
],
),
),
)
],
),
),
],
),
);
}
}
Any ideas on this? All help would be much appreciated. Thank You.
Have you considered wrapping your DefaultTabController inside a StatefulBuilder? You can use a StatefulBuilder when you only want to update part of a widget and not the entire widget.
StatefulBuilder(
builder: (context, innerSetState) {
return DefaultTabController(
length: 2,
child: Container(
...
child: Column(
...
children: [
TabBar(
...
onTap: (value) {
innerSetState(() => containerHeight = index == 0 ? 300 : 800);
},
...
),
...
],
),
),
)
}
)
see StatefulBuilder and Flutter 'setState' redraws the entire screen instead of just the widget