Flutter: Use AbsorbPointer without rebuilding entire widget tree - flutter

I have a Stateful home page which has a list of Stateful widget children. When I click on a child, I'm gonna call its setState() to add a CircularProgressIndicator to it. That's all fine and dandy; clicking on a child only rebuilds that child.
However, I also have my home page wrapped inside an AbsorbPointer, and I want to set absorbing = true when I click on a child widget. The goal is to stop the user from clicking around while the app is doing some async work in the background. The problem now is that if I call setState() in the home page to set "absorbing" to true, it will rebuild all of the child widgets.
I could pass some parameters into the child widgets so that only the one I clicked on will have a CircularProgressIndicator, but even then all the other children will still be rebuilt.
I guess this boils down to the fact that I can't call setState() on a parent widget without rebuilding all the children, even though the parameter I pass to that setState() (absorbing) has nothing to do with those children.
Is there a workaround for this?
Thanks!
// home_screen.dart
class HomeScreen extends StatefulWidget {
static const String routeName = "homeScreen";
final MyUser? user;
const HomeScreen({Key? key, required this.user}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
final MyDatabase _db = MyDatabase();
MyUser? _me;
int _currentPage = -1;
bool _isLoading = false;
...
#override
Widget build(BuildContext context) {
return AbsorbPointer(
absorbing: _isLoading,
child: Container(
decoration: BoxDecoration(
// color: Color(0xFF0d0717),
image: DecorationImage(
image: Image.asset(
'assets/background.png',
).image,
fit: BoxFit.cover,
),
),
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: ...,
bottomNavigationBar: ...,
body: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 12.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
StreamBuilder<QuerySnapshot>(
stream: _db.getLiveChannels(),
builder: (_, snapshot) {
if (!snapshot.hasData) {
// print("Has no data");
return Center(
child: CircularProgressIndicator(),
);
}
_channels.addAll(List.generate(
snapshot.data!.docs.length,
(index) => Channel.fromSnapshot(
snapshot.data!.docs[index])));
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'Now Playing',
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.w700,
),
),
SizedBox(width: 8.0),
LiveIndicator(),
],
),
SizedBox(height: 8.0),
Container(
height: 250,
child: PageView.builder(
physics: PageScrollPhysics(),
scrollDirection: Axis.horizontal,
controller: PageController(
viewportFraction: .9,
),
itemCount: _channels.length,
itemBuilder: (context, index) {
Channel channel =
_channels[_channels.length - 1 - index];
return ChildWidget(
callback: _callback;
loading: (_isLoading && _currentPage == index),
key: UniqueKey(),
);
},
),
),
...,
],
);
},
),
...,
],
),
),
),
),
),
);
}
Future<void> _callback(params) async {
if (_isLoading == false) {
setState(() {
_isLoading = true;
_currentPage = index;
});
}
someAsyncMethod().then((_) => setState(() {
_isLoading = false;
_currentPage = -1;
}));
}
}
// child_widget.dart
class ChildWidget extends StatefulWidget {
final Future<void> Function(params) callback;
final bool loading;
const ChildWidget({
Key? key,
required this.callback,
required this.loading,
}) : super(key: key);
#override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
late Future<void> Function(params) callback;
late bool loading;
#override
void initState() {
super.initState();
callback = widget.callback;
loading = widget.loading;
}
#override
Widget build(BuildContext context) {
return Padding(
child: Column(
children: [
Expanded(
child: CustomClickableWidget(
onPressed: callback,
child: Expanded(
child: Container(
child: Stack(
children: [
...,
if (loading) ...[
Container(
alignment: Alignment.center,
child: CircularProgressIndicator(),
),
],
],
),
),
),
),
),
...,
],
),
);
}
}
Screenshot

The SetState function triggers the Build() function, so all the code present in the Build() function will be executed again. I don't quite see why this is a problem for you ?
On the other hand in your code I see that for your child you have defined a key: UniqueKey (). When the build function will run after SetState (), it will create a new child without keeping the state of the previous child. You shouldn't define the UniqueKey () in your function but rather as an instance variable of your state
ChildWidget(callback: _callback;
loading: (_isLoading && _currentPage == index),
key: UniqueKey(),
)
You should define you key here
class _ChildWidgetState extends State<ChildWidget> {
UniqueKey myKey = UniqueKey();
and you function
ChildWidget(callback: _callback;
loading: (_isLoading && _currentPage == index),
key: myKey,
)

Related

how to hide a widget in flutter?

I am trying to hide a widget which has List array and List array gets _images.length. For Example, if image.length is Null, like if there are no images so I want to hide the container which takes space.Not sure what I am missing. I tried the code below. Help me out pls, Thanks. or if there is any other way to do it pls let me know. I am just a beginner.
class PortfolioGallarySubPage extends StatefulWidget{
PortfolioGallarySubPage({Key key,#required this.urls,#required this.currentIndex})
:super(key:key);
#override
_PortfolioGallarySubPage createState() => _PortfolioGallarySubPage();
}
class _PortfolioGallarySubPage extends State<PortfolioGallarySubPage>
with SingleTickerProviderStateMixin{
final GlobalKey<FormState> formKey = new GlobalKey<FormState>();
final GlobalKey<ScaffoldState> key = new GlobalKey<ScaffoldState>();
List<File> _images = [];
final picker = ImagePicker();
Future getImage() async {
final pickedFile = await picker.getImage(source: ImageSource.gallery);
setState(() {
if (pickedFile != null) {
_images.add(File(pickedFile.path));
}
else {
print('No image selected.');
}
});
}
#override
void initState() {
super.initState();
}
#override void dispose()
{
super.dispose();
}
bool isVisible = true;
void changeVisibility(){
setState(() {
if(_images.length ==null ){
isVisible = !isVisible;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: key,
extendBodyBehindAppBar: true,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
actions: [
ElevatedButton(
child: Text("DONE",style: TextStyle(fontSize: 15)),
onPressed: (){
_uploadImages();
},
style: ElevatedButton.styleFrom(padding: EdgeInsets.fromLTRB(25.0, 15.0, 25.0, 10.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0))),
),
],
),
body: Container(
width: _maxScreenWidth,
child: SafeArea(
child:Form(
key: formKey,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Visibility(
visible: isVisible,
child: Container(
height: 150.0,
padding: EdgeInsets.symmetric(vertical: 15.0,horizontal: 15),
child: ListView(
scrollDirection: Axis.horizontal,
children: [
SizedBox(width: 15.0),
ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: _images.length,
itemBuilder: (BuildContext context,int index){
//return Padding(padding: const EdgeInsets.only(top: 0.0,bottom: 0.0),
return InkWell(
onTap: () => print('tapped'),
child: Card(
elevation: 10,
child: SizedBox(height:150, width: 150,child: Image.file(_images[index], fit: BoxFit.cover,)) ,
),
);
},
),
],
),
),
),
],
),
),
],
),
),
),
),
),
);
}
}
_images array length will always return 0 if the list is empty, so you need to set the condition as
setState(() {
isVisible = _images.length > 0;
});
Instead of taking variable isVisible set the _images.length > 0 like
visible: _images.length > 0
And remove the isVisible variable.... it will update the visibility when _images list is updated
there is another solution of this without using visible widget :
class Mywidget extends StatefulWidget {
#override
_MywidgetState createState() => _MywidgetState();
}
class _MywidgetState extends State<Mywidget> {
double width;
double heigth;
void changeVisibility() {
setState(() {
if (_images.length == null) {
width=any width you want ;
heigth = any height you want ;
}else{
setState(() {
width=0;
heigth=0;
});
}
});
}
#override
Widget build(BuildContext context) {
// the contanier that you want to be visble
return Container(
height: heigth,
width: width,
// the list view that has the images
child: ListView(),
);
}
}
if there is an image the height and the width of the widget will be not zero
but if not the widget will be visible because the width and the height will be equal to zero
As I can see in the snippet, you are not calling the changeVisibility method anywhere. Hence, isVisible will always remain true
So make a call to changeVisibility wherever you are calling getImage method.
Also, the logic is inherently wrong,
initialize isVisible to false initially, this way you can make it true when there is an image.

Is there a way to push the updated state of data of one stateful widget into another stateful widget?

I have been struggling with the problem of pushing updated data from one widget to another. This problem occurs when I have two Stateful widgets and the data is updated in the parent Stateful widget but is not updated in the child Stateful widget. The error occurs with the usage of the freezed package but also occurs without it as well.
I have not been able to find anything that fixes this as of yet.
Below is an example:
First Stateful Widget:
class FirstWidget extends StatefulWidget {
#override
_FirstWidgetState createState() => _FirstWidgetState();
}
class _FirstWidgetState extends State<FirstWidget> {
ItemBloc _itemBloc = getit<ItemBloc>();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
elevation: Mquery.width(context, 2.5),
backgroundColor: Colors.black
title: Text(
'First stateful widget',
style: TextStyle(fontSize: 17),
),
centerTitle: true,
),
body: BlocBuilder<ItemsBloc,ItemsState>(
cubit: _itemsBloc,
builder: (BuildContext context,ItemState state) {
return state.when(
initial: () => Container(),
loading: () => Center(child: CustomLoader()),
success: (_items) {
return AnotherStatefulWidget(
items: _items,
...
);
},
);
},
));
},
);
);
}
}
Second Stateful Widget:
class AnotherStatefulWidget extends StatefulWidget {
final List<String> items;
AnotherStatefulWidget(this.items);
#override
_AnotherStatefulWidgetState createState() => _AnotherStatefulWidgetState();
}
class _AnotherStatefulWidgetState extends State<AnotherStatefulWidget> {
final ScrollController scrollController = ScrollController();
ItemsBloc _itemsBloc = getit<ItemsBloc>();
bool _handleNotification(ScrollNotification notification, List<String> items) {
if (notification is ScrollEndNotification &&
scrollController.position.extentAfter == 0.00) {
_itemsBloc.add(ItemsLoadEvent.loadMoreItems(
categories: items, document: ...));
}
return false;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: double.infinity,
height: 280,
child: Padding(
padding: EdgeInsets.only(
right: 8,
),
child: NotificationListener<ScrollNotification>(
onNotification: (_n) =>
_handleNotification(_n, widget.items),
child: DraggableScrollbar.arrows(
alwaysVisibleScrollThumb: true,
controller: scrollController,
child: ListView.builder(
controller: scrollController,
itemCount: widget.items.length,
itemBuilder: (context, index) {
return GestureDetector(
child: Padding(
padding: EdgeInsets.all(16),
child: Align(
alignment: Alignment.center,
child: Text(
widget.items[index],
style: TextStyle(color: Colors.white),
)),
),
);
},
),
),
),
),
)
],
),
),
),
);
}
}
I would really appreciate any help!
Thank you for you time,
Matt

filtering Streambuilder/ listviewBuilder flutter

i am new to flutter and been trying to create a function that refresh the ListView.builder based on users choice.i am saving cities names as Strings inside my firestore documents in user collection.
i have multiple buttons that presents different cities and based on choice i need the ListView builder to rebuild. i have been struggling for a while trying to find the solution to this.
anyone here can help?
this is how i retrieve data from firestore
StreamBuilder(
stream: Firestore.instance.collection('users').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text('loading...');
return Container(
width: 890.0,
height: 320.0,
margin: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 00.0),
child: new ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int index) {
User user = User.fromDoc(snapshot.data
.documents[index]);
return Padding(
padding: const EdgeInsets.only(top: 0),
child: Container(
height: 300,
width: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(0),
),
child: _buildCard(user)),
);
}),
);
},
),
I just wrote this code to show the implementation for static no of cities, clicking the buttons changes the index which then changes the texts(you will change them to stream builders with custom city streams), you can also scale it to dynamic list by manipulating the city list.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key,}) : super(key: key);
​
​
#override
_MyHomePageState createState() => _MyHomePageState();
}
​
class _MyHomePageState extends State<MyHomePage> {
int stackIndex = 0;
​
final List<String> cities = ['Berlin', 'Denver', 'Nairobi', 'Tokyo', 'Rio'];
​
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample'),
),
body: Center(
child: Column(
mainAxisAlignment : MainAxisAlignment.spaceEvenly,
children : [
Row(
mainAxisAlignment : MainAxisAlignment.spaceEvenly,
mainAxisSize : MainAxisSize.max,
children : cities.map((city){
return RaisedButton(
child : Text(city),
onPressed : (){
setState((){
this.stackIndex = cities.indexOf(city);
});
}
);
}).toList()
),
IndexedStack(
index : stackIndex,
children: cities.map((city){
return yourStreamBuilder(city);
}).toList()
),
])
),
);
}
Widget yourStreamBuilder(String city){
//you can use your custom stream here
//Stream stream = Firestore.instance.collection('users').where('myCity', isEqualTo: city).snapshots();
​
​
return Text(city);//replace this with your streamBuilder
}
}
​
int stackIndex = 0;
final List<String> cities =[
'Stockholm',
'Malmö',
'Uppsala',
'Västerås',
'Örebro',
'Linköping',
'Helsingborg',
'Jönköping',
'Norrköping',
'Lund',
'Umeå',
'Gävle',
'Södertälje',
'Borås',
'Huddinge',
'Eskilstuna',
'Nacka',
'Halmstad',
'Sundsvall',
'Södertälje',
'Växjö',
'Karlstad',
'Haninge',
'Kristianstad',
'Kungsbacka',
'Solna',
'Järfälla',
'Sollentuna',
'Skellefteå',
'Kalmar',
'Varberg',
'Östersund',
'Trollhättan',
'Uddevalla',
'Nyköping',
'Skövde',
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment : MainAxisAlignment.spaceEvenly,
children: <Widget>[
Row(
mainAxisAlignment : MainAxisAlignment.spaceEvenly,
mainAxisSize : MainAxisSize.max,
children: cities.map((city) {
return OutlineButton(
child: Text(city),
onPressed: (){
setState(() {
this.stackIndex = cities.indexOf(city);
});
},
);
}).toList()
),
IndexedStack(
index: stackIndex,
children: cities.map((city){
return myStreamBuilder(city);
})
)
],
),
),
);
}
Widget myStreamBuilder(String city){
Stream stream = Firestore.instance.collection('users').where('myCity', isEqualTo: city).snapshots();
return Text(city);
}
}

How to create a card in a listview with button press on Flutter?

Listview will be empty after the click I would like to create a card in the ListView on flutter.
Is it also possible to create a dynamic home page? For example when there will be no any card on the list it is going to write on the background there is no any card yet. But if card created this indication will be deleted.
Could you please support me regarding this topic?
import 'package:flutter/material.dart';
class ListScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _ListScreenState();
}
class _ListScreenState extends State<ListScreen> {
bool _isLoading = true;
List<String> _items = [];
#override
void initState() {
super.initState();
_getListData();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
margin: EdgeInsets.all(10),
child: !_isLoading && _items.isEmpty
? Center(
child: Text("No data found"),
)
: (_isLoading && _items.isEmpty)
? Container(
color: Colors.transparent,
child: Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Colors.pink),
),
),
)
: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
return _createListRow(_items[index], index);
},
),
),
),
);
}
_createListRow(String item, int index) {
return Card(
elevation: 3,
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(item),
FlatButton(
child: Text("Delete"),
onPressed: () {
setState(() {
_items.removeAt(index);
});
},
)
],
),
);
}
_getListData() {
// Create dynamic list
Future.delayed(Duration(milliseconds: 500));
setState(() {
_items.add("First");
_items.add("Second");
_items.add("Third");
_isLoading = false;
});
}
}
You should check the official documentation. It's not so hard to learn with it :
ListView
Card
InkWell

Flutter cannot call setState to rebuild widget

I am very new to flutter and the millions of answers I have read online made me even more confusion. This is my page:
class SecondDegreePage extends StatefulWidget {
const SecondDegreePage({Key key}) : super(key: key);
#override
SecondDegreePageState createState() => SecondDegreePageState();
}
class SecondDegreePageState extends State<SecondDegreePage> {
final GlobalKey<FormState> _formKey = new GlobalKey();
final controllerParamA = TextEditingController();
final controllerParamB = TextEditingController();
final controllerParamC = TextEditingController();
Quadratic _solver = Quadratic([
(Random().nextInt(10) + 1) * 1.0,
(Random().nextInt(10) + 1) * 1.0,
(Random().nextInt(10) + 1) * 1.0
]);
void updateData() {
setState(() {
_solver = Quadratic([
(Random().nextInt(10) + 1) * 1.0,
(Random().nextInt(10) + 1) * 1.0,
(Random().nextInt(10) + 1) * 1.0
]);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalization.of(context).title),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
// Input form
Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
EquationInputField(
controller: controllerParamA,
textHint: 'a',
width: 70,
),
EquationInputField(
controller: controllerParamB,
textHint: 'b',
width: 70,
),
EquationInputField(
controller: controllerParamC,
textHint: 'c',
width: 70,
),
],
),
),
),
// Submit button
Padding(
padding: EdgeInsets.fromLTRB(0, 30, 0, 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => updateData(),
color: Colors.blue,
elevation: 6,
child: Text(
AppLocalization.of(context).solve,
style: TextStyle(
color: Colors.white
),
),
)
],
),
),
//Solutions
Expanded(
child: Padding(
padding: EdgeInsets.only(bottom: 10),
child: AlgebraicSolutions(
solver: _solver
),
),
),
],
),
);
}
#override
void dispose() {
controllerParamA.dispose();
controllerParamB.dispose();
controllerParamC.dispose();
super.dispose();
}
}
The type Quadratic is simply a class that does some math, nothing important. The problem is in this line:
RaisedButton(
onPressed: () => updateData()
}
Why nothing happens? I have read thet the call to setState calld the build method and the widget is re-built. For this reason I expect that
child: AlgebraicSolutions(
solver: _solver
),
here the _solver reference gets updated. AlgebraicSolutions is the following:
class AlgebraicSolutions extends StatefulWidget {
final Algebraic solver;
const AlgebraicSolutions({Key key, #required this.solver}) : super(key: key);
#override
AlgebraicSolutionsState createState() => AlgebraicSolutionsState();
}
class AlgebraicSolutionsState extends State<AlgebraicSolutions> {
//'Quadratic' is a subtype of Algebraic
Algebraic _solver;
#override
void initState() {
_solver = widget.solver;
super.initState();
}
#override
Widget build(BuildContext context) {
var solutions = _solver.solve();
return ListView.builder(
itemCount: solutions.length,
itemBuilder: (context, index) {
//...
}
);
}
}
Is that because I am using initState in AlgebraicSolutionsState that breaks something?
Please note that Quadratic type is subclass of Algebraic type.
Yep, saving the initial value in initState is what's giving you trouble. That lifecycle method is only called on first build but since the widget reconciles to the compatible type the same state is used. i.e. the properties change (widget instance is different) but the state is the same object. build gets called again but initState does not.
In this situation I would use a getter to give you the same convenience but always use the _solver from the current widget. Then you can discard the initState.
Algebraic get _solver => widget.solver;
The other option being didUpdateWidget but it really not necessary here for something so straightforward otherwise.