Showing one card in flutter when no other cards are displaying - flutter

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.

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.

What widget should I use?

This is what I am doing now.
Main Page:
I would like to make it same like this picture.Example:
I have tried couple ways and widget to build it but couldn't figure it out. Also, I want to retrieve the data from the Firebase and show them as the content.
Code 1: https://pastebin.com/A0nK1riQ
Code 2: https://pastebin.com/i1T7gBNy
Widget build(BuildContext context) {
return Container(
child: StreamBuilder(
stream: _products.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (streamSnapshot.hasData) {
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot =
streamSnapshot.data!.docs[index];
return Container(
margin: const EdgeInsets.all(10),
child: ListTile(
title: Text(documentSnapshot['name']),
subtitle: Text(documentSnapshot['price'].toString()),
trailing: SizedBox(
width: 100,
),
),
);
});
}
return SizedBox.shrink();
}),
);
}
You may use GridView.builder
https://api.flutter.dev/flutter/widgets/GridView-class.html
and in gridview build use column
According to me, first you have to check which NavigationRail icon clicked then put the condition on it's GestureDetector like
// global variable
String itemClickedValue = "";
then set the value in it according to user click
itemClickedValue = "first";
then check the condition while fetching data like
if(itemClickedValue.compareTo("first") == 0){
// pass that documentId or api and then show in list
}

Dart - Re-using Flutter widgets sometime contains wrong data

I have a Flutter web application that displays multiple user profiles on a card within a Row. The cards can each flip over to reveal more information via this library:
https://pub.dev/packages/flip_card
The application uses WebSockets and receives a JSON list of user details which maps to a User dart class, and as soon as new list arrives on a socket, we create a widget and add it to a widgetList and wrap it in a setState():
webSocket.onMessage.listen((e) {
final List receivedJsonUserList = json.decode(e.data);
final List<User> userListFromSocket =
receivedJsonUserList.map((item) => User.fromJson(item)).toList();
userListFromSocket.forEach((newUser) {
setState(() {
widgets[newUser.user.id] = UserDetails(user: newUser);
widgetList = widgets.entries.map((entry) => entry.value).toList();
});
});
}
}
});
The widget is drawn like this:
#override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) => Scaffold(
drawer: sizingInformation.deviceScreenType == DeviceScreenType.mobile
? NavigationDrawer()
: null,
backgroundColor: Colors.white,
body: Scrollbar(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: widgetList),
],
),
),
),
),
);
}
The code works 90% of the time, but occasionally the wrong data is on the back of a card. So User 1 will have User 2's data on the back, etc.
Am I doing this correctly? Is there an obvious issue with this implementation? I tried to create a seperate widget for each user and it seems to resolve the issue but re-using widgets surely has to be possible.

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 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.