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

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.

Related

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

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.

Flutter : How to show Progress indicator until the data is fetched from the server [duplicate]

This question already has answers here:
Flutter how to user setState()
(2 answers)
Closed 8 months ago.
I want to show a progress indicator until the required data is fetched from the server. Currently what I am doing is made a function getQuotes() that will fetch the data into a variable using setState(). And the Used the FutureBuilder where its future parameter is set to getQuotes(). But this approach gives me a non-ending CircularProgressIndicator. I don't why it is happening. Is ther any problem with the combination of FutureBuilder() and setState() ? Can Some one help ?
Here is my code,
Map<String, dynamic> userInfo = {};
Future<void> getQoutes() async {
var data = await FirebaseFirestore.instance.collection('user').doc(auth.currentUser!.uid).get();
setState(() {
userInfo = data.data() as Map<String, dynamic>;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Some Quotes',
),
backgroundColor: Colors.deepOrange,
),
body: FutureBuilder (
future: getQoutes(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (snapshot.hasError) {
return Text('Error : ${snapshot.error}');
}
return SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: ListView.builder(
itemCount: 3,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return Card_fun(userInfo['quotes'][index]);
}
)
)
],
),
)
);
default:
return const CircularProgressIndicator();
}
}
),
);
}
Another solution would be to make your getQuotes() function returning a Future<String> instead of a Future<void> and then access the data via the snapshot instead of accessing the state.
The Flutter docs of the FutureBuilder Flutter Docs are also doing it that way in the demo. As long as you don't need the state of userInfo in other places this should be an acceptable solution and you could also remove userInfo as variable. If you want to maintain or manipulate it later you could try to put the setState({}) statement in the ConnectionState.done switch case within an if(snapshot.hasData){} block.

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

How can i include navigation for a listview in flutter ,

enter image description here
I want each list item to have its own route .I have attached a screenshot. My list view is customized, each item has its own name
Your question doesn't explain much, but rather than listview use listview.builder and then you can return any widget and pass your route based on itemindex.
ListView.builder(
itemCount: 10, *//Number of items in your static or dynamic*
itemBuilder: (context, index) {
return YourNewCommonWidgetForEveryItem(index);
},
),
For example I've to navigate the first item to somewhere else
Widget YourNewCommonWidgetForEveryItem(int Index){
return GestureDetector(
onTap:()=> index==0 ? locationtofirstItem : defaullt,
child: yourDesign(),
),
}
OfCourse the Above method won't work if you don't know where to navigate which item.
body: new ListView(
padding: const EdgeInsets.all(20.0),
children: List.generate(choices.length, (index) {
return Center(
child: ChoiceCard(
choice: choices[index],
item: choices[index],
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Detail(choice: choices[index])),
);},),);}))