Flutter Looking up a deactivated widget's ancestor on dialog pop - flutter

I'm having an issue trying to pop a dialog that contains a circle loader. I actually pop fine once my data is loaded, but in debug mode it's showing an exception that I can't figure out how to fix.
I have a stateful screen that on init I use the following code:
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
showLoading();
});
The method showLoading is as follows:
void showLoading() {
//let's show the loading bar
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
dialogContext = context;
return AppLoader();
},
);
}
Where AppLoader simply returns:
class AppLoader extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
backgroundColor: Colors.transparent,
body: Center(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
SizedBox(
child: new CircularProgressIndicator(),
height: 80.0,
width: 80.0,
),
],
),
),
);
}
}
dialogContent is defined in the initial of the class as:
late BuildContext dialogcontext;
The main bulk of my code looks like this:
#override
Widget build(BuildContext context) {
return Container(
color: ColorConstant.gray100,
child: Scaffold(
backgroundColor: ColorConstant.gray100,
body: Stack(
children: <Widget>[
getMainListViewUI(),
SizedBox(
height: MediaQuery.of(context).padding.bottom,
)
],
),
),
);
}
Widget getMainListViewUI() {
return FutureBuilder<bool>(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return ListView.builder(
itemCount: listViews.length,
scrollDirection: Axis.vertical,
itemBuilder: (BuildContext context, int index) {
return listViews[index];
},
);
},
);
}
Basically, the issue that I have is that when I finish getting the data from (getData()), I use:
Navigator.pop(dialogContext);
This works great: it removes the circle loader and I can see the screen behind it, no issues, no errors. However, if I run in debug mode, when I do a hotsync, it always shows me the error:
Looking up a deactivated widget's ancestor on dialog pop
I understand that this is because of the Navigator.pop that I am doing, but I don't get it. I've defined the dialogContext, which is what I am passing to the showDialog, and that's what I am popping. I've also tried setting a scheduled navigator, but again, same issue.
Any advice please?

You can check if widget is mounted or not. This problem is likely to occur when you pass a context and have async function. Can you add this before navigation and see if problem is solved
if(!mounted) return;

Problem solved. The issue is the my getData method was being called multiple times, the first time it pops the loading widget fine, after that it would throw the error which is correct since the loading widget no longer exists.

Related

ListView.builder inside FutureBuilder widget returns null even if it's populated?

So I'm new to Flutter and I keep running into the same issue.
Future<List<DonationDTO>> getUsersAppointments() async {
List<DonationDTO> AppointmentRes = [];
AppointmentRes = (await _apiClient.getDonationsOfUser(widget.user.id, widget.accesstoken));
return AppointmentRes;
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.pink,
elevation: .0,
leading: BackButton(
onPressed: () => Navigator.pop(context),
color: Colors.white),
title: Text("text"),
),
body:SingleChildScrollView(
child: FutureBuilder<List<DonationDTO>>(
future: getUsersAppointments(),
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData || snapshot.hasError) {
return new Container(child:CircularProgressIndicator());
} else {
return Container(
child: ListView.builder(
itemCount: snapshot.data.length,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
if (!snapshot.data[index].hasData || snapshot.data[index].hasError) {
return new Container(child:CircularProgressIndicator());
} else {
return Text('${snapshot.data[index].id}');}
}));
}
}
),
),
),
);
}
The problem with this widget is that while using the ListView I keep getting a null value error, more specifically when I try to get data from snapshot.data[index].
It also appears that when I display the content of snapshot.data I get this:
[Instance of 'DonationDTO', Instance of 'DonationDTO']
I don't know how to fix this, so any help would be appreciated. Thanks!
snapshot.data[index] points to a list item of DonationDTO type, which has no hasData and hasError properties. In itemBuilder it is safe to use snapshot.data[index] without checks.
Well, the other answer is correct for this case. If you want to solve this not only this time but for all future uses, you should declare your types and not use implicit dynamic that you end up with if you miss a declaration.
A FutureBuilder<List<DonationDTO>> does not receive just any type of AsyncSnapshot. It receives a AsyncSnapshot<List<DonationDTO>>. And from there on, your compiler got your back. .data is no longer of type dynamic where you have to guess (in this specific case you guesses wrong, which explains your problem) but of type List<DonationDTO> and you and your compiler can tell exactly what to do and what would be wrong.

I can't refresh the screen

I use the get package, I have a bottom navigation that is updated with Obx. There is also a search for elements, at the top level of the pages everything is updated well, but when I do push, the current page is not updated, only when you call hot reload. There are suspicions that the nested page is not updated due to the fact that it goes beyond the BottomNavigation, and there is no Obx widget.
My Page Navigation controller:
Widget build(BuildContext context) {
SizeConfig().init(context);
final BottomPageController landingPageController =
Get.put(BottomPageController(), permanent: false);
return SafeArea(
child: Scaffold(
bottomNavigationBar:
BottomNavigationMenu(landingPageController: landingPageController),
body: Obx(
() => IndexedStack(
index: landingPageController.tabIndex.value,
children: [
ProductPage(),
MapPage(),
ClientPage(),
NotePage(),
],
),
),
),
);
}
My page where you need to update the ListView, depending on the entered value in the search bar:
class Product extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetX<ProductController>(
init: ProductController(),
builder: (controller) {
return FutureBuilder<List<ProductsCombined>>(
future: controller.filterProduct.value,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Expanded(
child: Container(
child: ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) =>
ProductCards(
product: snapshot.data[index],
),
),
),
);
} else {
return Center(child: CircularProgressIndicator());
}
},
);
},
);
}
}
There is also a nested page in the Product class, which is accessed in this way:
onTap: () => {
Get.toNamed(StoresScreen.routeName, arguments: companies)}
The Product class is updated immediately, you do not need to click Hot reload to do this, and the ProductScreen class that the transition is made to can no longer be updated, these 2 classes are completely identical. The search bar works, but it filters out the elements only after Hot reload.
If you need some other parts of my code, such as a controller that handles ListView, please write, I will attach it.
EDIT: I add a link to the video, with the screen that is not updated
Obx is a little tricky to work with. I always suggest to use GetX instead.
Try this :
Obx(
() => IndexedStack(
index: Get.find<BottomPageController>().tabIndex.value,
children: [
...
],
),
),
If it didn't work use GetX<BottomPageController> for this situation too. It works for all conditions

Showing one card in flutter when no other cards are displaying

I have a group of cards that are connected to my cloud firestore which shows when the icon related to each card is chosen. However, I want a card that tells the user to choose an icon when none of the other cards are showing. This is my code:
class Tips extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream:
FirebaseFirestore.instance.collection('moodIcons').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text('Loading data... Please Wait');
var currentCards = 0;
return Container(
child: new ListView(
children: snapshot.data.documents.map<Widget>((DocumentSnapshot document) {
if (document.data()['display'] == true) {
//code to display each card when specific icon is chosen
} else {
if (currentCards < 1) {
currentCards++;
return new Card(
child: new Column(
children: <Widget>[
new Container(
width: 400.0,
height: 45.0,
child: new Text(
"Choose an icon to show a tip!",
),
],
));
Currently, the card shows all the time but I want it to disappear when an icon is clicked. When an icon is clicked, the value of display becomes true.
I tried to use a counter so that if the value of display is true then it adds one to the counter then if the counter is more than 0, the card in the else statement wont show. However, this didn't work as I couldn't return the value of the counter from within the if statement.
Any help would be appreciated!
You could conditionally add an item at the beginning of your ListView if no document is set to display: true:
class Tips extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: FirebaseFirestore.instance.collection('moodIcons').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text('Loading data... Please Wait');
return Container(
child: new ListView(children: [
if (!snapshot.data.documents
.any((doc) => doc.data()['display'] == true))
Card(child: Text("Choose an icon to show a tip!")),
...snapshot.data.documents
.where((doc) => doc.data()['display'] == true)
.map((doc) => MyWidget(doc))
.toList(),
]),
);
},
),
);
}
}
If you want this to be done by StatelessWidget you need some state management like Bloc or Riverpod, your own etc.
a good answer here
You need to abstract your stream and create an event/action when you click on the icon.
That event is to be handlined via the above mentioned approaches. where a new card will be pushed to the stream.
Or you can make your widget Stateful, create a list of Card Widgets (to provide to your list builder) and use setState to add a element to a Card to the list.

How to pass context to a child widget in flutter

I have a statefull widget W1 which calls a stateless widget W2.
W2 has onTap functionality. I want to show an alert dialog in W2's onTap().
onTap:() {Alert(context: context, title:'Hi');},
I dont get any error, but no alert is shown on tap. I tried passing context as a parameter to W2 but I still dont see any dialog box.
What is the right way to show a dialog box from W2?
I am using rflutter_alert package Link
Thanks
You have to wrap your Alert(context: context, title:'Hi'); with showDialog(context: context, builder: (BuildContext context) => Alert(context: context, title:'Hi'));
Here is the cookbook sample:
Future<void> _neverSatisfied() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text('Rewind and remember'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('You will never be satisfied.'),
Text('You\’re like me. I’m never satisfied.'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Regret'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
Anyway, for your question about how to pass context, If you are creating a Stateless or Stateful widget you dont need to pass the context, you can get it from build(BuildContext context) {}.
Adding .show() in end solved it.
onTap:() {Alert(context: context, title:'Hi').show();}
Its clearly documented in rflutter_alert package, but I somehow missed it.
Pass the context in the function call in onTap:
onTap:(context) {Alert(context: context, title:'Hi');},

How can I change the state of widget based on the action of another widget?

I am having trouble with the following problem:
In this page, I have a CheckBoxListTile and two radioButtons.
I need to implement some rules, like these one:
If any checkbox gets checked, disabled and unselect all radiobuttons
If any radiobuttons gets selected, uncheck and disable all checkboxes
However, I can't get this to work. I've tried using statefulWidgets for both checkbox and radiobutton, but the logic was getting very messy.
I've tried to use a stateful widget for the PostView class, but since I'm using a futureBuilder there, whenever I called setState(), the whole page would be rebuilt and the checkbox would not get checked.
I believe there is a cleaner solution to this, but right now I can't see it.
Here is the snippet of the code so you can see ( this is using a stateless widget, I know I cannot do it this way, but It is just to show you guys what I am trying to do ):
Widget _buildFooter(Post postInfo) {
return Container(
child: Column(
children: <Widget>[
_buildRadioButtons(postInfo),
RaisedButton(
child: Text('press'),
onPressed: () {},
),
],
),
);
}
class PostView extends StatelessWidget {
final String id;
PostView(this.id);
Post postInfo;
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(),
body: FutureBuilder(
future: ApiService.getpostInfo(id),
builder: (BuildContext context, AsyncSnapshot snapShot) {
if (snapShot.connectionState == ConnectionState.done) {
if (snapShot.hasError) {
return Center(
child: Text('error'),
);
}
postInfo = postInfo.fromJson(snapShot.data);
return ListView.separated(
separatorBuilder: (BuildContext context, int index) {
return Divider();
},
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return _buildHeader(postInfo);
}
if (index == (postInfo.authors.length + 1)) {
return _buildFooter(postInfo);
}
index = index - 1;
return _buildCheckboxListTile(postInfo, index);
},
itemCount: (postInfo.authors.length) + 2,
);
}
return CircularProgressIndicator();
},
),
);
}
}
_buildCheckboxListTile(Post postInfo, index) {
return CheckboxListTile(
title: Text(postInfo.authors[index].name),
value: postInfo.authors[index].checked,
onChanged: (bool value) {
***Here I need to disabled the radiobuttons in case I get a true
},
);
}
_buildRadioButtons(Post postInfo) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: CheckboxListTile(
value: postInfo.value1,
title: Text('value1'),
onChanged: (bool value) {
*** here I need to uncheck all the checkboxes mentioned above
in case the radio button is selected
},
),
),
Expanded(
child: CheckboxListTile(
value: postInfo.value2,
title: Text('value2'),
onChanged: (bool value) {},
),
),
],
);
}
Would this behaviour be possible without a statefulWidget?
Since I'm learning Flutter, any other advices regarding the code are welcomed!
Thanks!
You could try State Management libraries to easily manage state without the code getting messy. Here is a great article on the best state management libraries to use:
https://medium.com/flutter-community/flutter-app-architecture-101-vanilla-scoped-model-bloc-7eff7b2baf7e
In the case of Scoped Model, which for me is the simplest,and which I personally use, you can set both to be a child of a ScopedModel widget and have them notify each other once the state changes.