How to delete data from StreamBuilder after reading? - flutter

I want to delete data from my stream after I read it.
Basically I want the same system than channel in Go.
So, if I add 5, 3 and 2, my stream contains 5, 3 and 2.
When I start reading, I get 5, and my stream now contains 3 and 2 etc...
Is it possible?
EDIT: Here my problem with some code.
I use a StreamBuilder to receive data. When I change the state, it trigger again my function like if I'd just receive data.
child: StreamBuilder<Tag>(
stream: widget.tagStream,
initialData: Tag(),
builder: (BuildContext context, AsyncSnapshot<Tag> snapshot) {
/// This should be trigger only when I receive data
if (mapController.ready && snapshot.hasData) {
tag = snapshot.data;
mapController.move(
LatLng(tag.position.latitude, tag.position.longitude),
mapController.zoom);
}
return RubberBottomSheet(...);
),
Here some context:
I have a map with icons representing objects. When I click on an icon or if I search the item related on my search bar, a RubberBottomSheet appears to show informations about the object. To do that, I use a StreamBuilder, so I just need to put the object clicked or searched in it to make my rubber appears and fill in. I also need to centrer on my icon to let the user know where is the object. My problem is that when I open or close my keyboard or when I use a setState (for changing the appearance of my search bar for example), it automatically trigger the StreamBuilder like if it receive new data.
Sorry, I should have started here...

The answer of Amine seems to be the best, but I'd like to share my solution too, maybe it'll help some persons.
After I've executed my code, I pass an empty object to my Stream. I just have to verify that my object is not empty before executing my code and everything works like a charm.
builder: (BuildContext context, AsyncSnapshot<Tag> snapshot) {
if (mapController.ready &&
snapshot.hasData &&
snapshot.data.mobile.nid != 0) {
tag = snapshot.data;
... /// My code
widget.tagStream.sink.add(Tag());
}

I've had similar behavior with a StreamBuilder and I couldn't find a solution for days. What I did instead is use a ListView builder that takes data from an InheritedWidget instead.
So basically instead of having to put data into the stream sink, I simply wrap my data setter in the InheritedWidget in a setState() and the ListView rebuilds every time I change the data.
N.B: My StreamBuilder also involved a map, I've always thought it was the one interfering with it but I never got to solve the problem. As in your case, every time I change the state, the stream rebuilds with the same data it had before.

Have similar problem in my app.
Workaroung I found is quite simple - global variable, which by default will do nothing ("false" in this example).
But in method which need to call setState() change this variable value to block next snapshot.data when rebuilding widget (in this example value "true" will block).
Remember to change variable to default value or you won't get new stream updates.
// global variable - doing nothing by default
bool _clear = false;
(...)
/// Add additional condition
if (mapController.ready && snapshot.hasData && !_clear) {
tag = snapshot.data;
mapController.move(
LatLng(tag.position.latitude, tag.position.longitude),
mapController.zoom);
}
/// change variable to default value
_clear = false;
return RubberBottomSheet(...);
),
(...)
// some method calling setState
void _clearMethod() {
_clear = true;
setState(() {});
}

Related

Change a dropdown's items when another dropdown's value is chosen in flutter (UI wont update)

I have two drop downs, and I want to do things when they get values selected. One of those is to change the second buttondrop items based on what's selected in the first dropdown.
For example:
Dropdown1 is a list of car manufactuers
Dropdown2 is a list of their models
Dropdown1 selects mercedes
Dropdown2 gets "E Class, S Class" etc
Dropdown1 selects lexus
Dropdown2 gets "ES, LS", etc
(Eventually the second drop down will update a listview as well, but haven't gotten to that yet.)
Data wise, it works, I update the list. The problem is the UI won't update unless I do a hot reload
Currently I am just having the dropdowns fetch their data and using Future builders
Future? data1;
Future? data2;
void initState(){
super.initState();
data1 = _data1AsyncMethod();
data2 = _data2AsyncMethod();
}
_data2AsyncMethod([int? item1_id]) async{
if(item1_id == null){
item2Classes = await DefaultItems().getAllItem2Classes();
listOfItem2ClassNames = DefaultItems().returnListOfItemClassNames(item2Classes);
}
else{
// The methods below calls the DefaultItems methods which have Futures all in them.
// The getAllItems calls a network file with GET methods of future type to get data and decodes them, etc.
// They build a list of the object type, ex List<Item2>
item2Classes = await DefaultItems().getAllItem2Classes(item1_id);
listOfItem2ClassNames = DefaultItems().returnListOfItemClassNames(item2Classes);
}
}
I have this Future Builder nested in some containers and paddings
FutureBuilder{
future: data2,
builder: (context, snapshot){
if(snapshot.connectionState != done...)
// return a circle progress indictator here
else{
return CustomDropDown{
hintText: 'example hint'
dropDownType: 'name'
dropDownList: listOfItem2ClassNames
dropDownCallback: whichDropDown,
}
The onChanged in CustomDropDown passes the dropDownType and the dropDownValue
The callback
whichDropDown(String dropDownType, String dropDownValue){
if(dropDownType == 'item1'){
//so if the first dropdown was used
// some code to get item_1's id and I call the data2 method
_data2AsyncMethod(item1_id);
}
Again the data updates (listOfItem2ClassNames) BUT the UI won't update unless I hot reload. I've even called just setState without any inputs to refresh but doesn't work
So how do I get the UI to update with the data, and is my solution too convoluted in the first place? How should I solve? StreamBuilders? I was having trouble using them.
Thanks
If you do a setState in the whichDropDown function, it will rebuild the UI. Although I'm not exactly sure what you want, your question is really ambiguous.
whichDropDown(String dropDownType, String dropDownValue){
if(dropDownType == 'item1'){
//so if the first dropdown was used
// some code to get item_1's id and I call the data2 method
_data2AsyncMethod(item1_id).then((_) {
setState(() {});
});
}
}
I notice a couple things:
nothing is causing the state to update, which is what causes a rebuild. Usually this is done explicitly with a call to setState()
in whichDropdown(), you call _data2AsyncMethod(item1_id), but that is returning a new Future, not updating data2, which means your FutureBuilder has no reason to update. Future's only go from un-completed to completed once, so once the Future in the FutureBuilder has been completed, there's no reason the widget will update again.
You may want to think about redesigning this widget a bit, perhaps rather than relying on FutureBuilder, instead call setState to react to the completion of the Futures (which can be done repeatedly, as opposed to how FutureBuilder works)

Return data when a screen is closed

I have several sub-screens which give the user the option to save some data. When that screen is closed, I want the parent screen, which pushed the sub-screen, to know whether data was saved or not. The sub-screens maintain a didSave flag and are set to true when data is saved.
There are several ways for the sub-screens to be closed:
hardware/software back button.
The close button on the AppBar.
A button on the screen itself.
I can handle the 3rd case using Navigator.pop(context, didSave) and in the parent that didSave flag is captured using final didSave = await Navigator.push<bool>(context, myRoute).
However, for the first 2 cases, the result will obviously be null.
I have looked into WillPopScope but that only is used to determine whether the screen should be closed or not. It doesn't seem that I can set any data to be returned for the push call.
I have also looked into wrapping the parent screen in a Provider where it can listen to didSave states but that will trigger immediately when emitted which is not desirable for my use case. I only want to act when the sub-screen is closed not when data is saved.
I can potentially use WillPopScope and emit an event there if a save operation has occurred but I would like a simpler solution if available.
Is there a way for this that I am missing?
Thanks!
as you said the Provider will listen to didSave and this doesn't fit in your case you can use just a simple inheritedWidget:
wrapping the parent like this:
InheritedExampleWidget(
didSave: false,
child: Parent(),
),
you need to set a setter to didSave
then on the ascendant widgets on the widget tree, you can:
InheritedExampleWidget.of(context).didSave = true;
this will not trigger it immediately, which the Provider package solves.
then you can manage the state however you want
Did you try to create the variable with state management? And there is method with the push that do task after user come from the child screen. So, when they come you can checkout the use have saved data or not.
For EG:
saved = false; //State management variable
//We are pushing on another screen.
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) =>
new ScreenName(),
),
).then((val) {
//This part with work when user popped the screen on which we have pushed
if (!saved) {//Do Something when not saved}
});
Try above code and let me know if you get any error or you're facing any issue.
when you push a new route, using StatefulWidget, it will have a lifecycle starting from an createState, when the widget isn't there on the widget tree ( when we pop ), the dispose method will be called.
those cases of popping the route:
hardware/software back button.
The close button on the AppBar.
A button on the screen itself.
will trigger the dispose method to execute.
so you can put inside it your logic that you want.
exmaple :
class classTest {
bool didSave = false;
}
then when on the property where you want to push the screen set it to that classTest's didSave, as an example:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const YourRouteWidget(didSave: classTest.didSave,
),
),
);
on that point it's false, then when the user will complete using the screen, going back with any king of popping the route (with Navigator.pop(context), or with back button...), the dispose method will be called, so you can :
void dispose() {
if(/* your conditions*/ ) {
classTest.didSave = true;
}
super.dispose();
}
it will be back with a true value to the parent page.

How to modify current bloc state like adding, updating, and deleting?

I have 2 screens, the first screen is to display a list of items. The second one is a form for creating a new item. On the server side, after each store, update, destroy action, I returns its value back to the client as a response. The goal here is to update the state without having to make a new request over the network and showing a loading screen over and over every time the user creating a new resource and going back to the screen containing the list.
But since the BlocBuilder is only depends on a specific event and state.data is only available inside an if (event is NameOfEvent) statement, I couldn't modify the current state to push the new value to it.
I found that using BlocListener combined with InitState and setState is somehow working, but I think it makes the state.data is useless inside the BlocBuilder also making the algorithm complicated for such a small task.
Is there any simpler way to achieve this condition?
child: BlocBuilder<EducationBloc, EducationState>(
builder: (context, state) {
if (state is EducationLoaded) {
return ListEducationData(educations: state.data);
}
if (state is EducationCreated) {
final education = state.data;
// i want to push [education] to <ListEducationData>
}
...
...
Create list to store data and use BlocListener so you don't need to change ui every state changes.
var _listEducations = List<EducationData>();
child : BlocListener<EducationBloc,EducationState>(
listener: (_,state){
if (state is EducationLoaded) {
setState(() { //update _listEducations
_listEducations.addAll(state.data);
});
}
},
child : Container()
)

Using FutureBuilder inside Build method

I am using FutureBuilder inside my Build method and the FutureBuilder keeps firing up again and again. I only want to call FutureBuilder once until my future is loaded and then call another function as soon as it is done. This is my Build function -
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: parsings(),
builder:
(BuildContext context, AsyncSnapshot<dynamic> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return LoadingState(context);
default:
if (snapshot.hasError)
return new Text(
'Error in snapshot: \n${snapshot.error}');
else {
return func1();
}
}
},
);
}
As i said, my Build function keeps on building again and again.
Before this, i was using my future inside initState but because of that, my actual page that i wanted to return gave null values all over until the API was called. I dont want to see null values at all so i wanted to use FutureBuilder and display LoadingState() until the API was called and then show my page which has all the called values and doesnt show null values. Any help would be appreciated.
EDIT: The detailed issue i am facing is as the build function is being called again and again, i am seeing my LoadingState() again and again, that is, LoadingState() is appearing and then func1() is appearing then again, LoadingState(), then func1() and so on. this process does not stop at all. Also, i do not want to use parsings() in initState because on doing so, before the API is called, the func1() shows null values in my data and as soon as it is called, the build function changes its values to the values called from API. I don't want to see it like that which is why i wanted to use FutureBuilder.

Difference between flutter onTap() and javascript/jquery $(this) in click listener

I'm still in the process of learning dart/flutter. I'm experimenting with ListView/ListTile and specifically onTap() from ListTile.
I'm somewhat familiar with JS/Jquery and am having trouble understanding the differences in approach between JS and flutter.
Please try to ignore any semantic or technical errors in the code below. I'm more concerned with theory/approach than if this is a perfectly formatted and syntactically correct example.
In JS, it's common to do something like make an AJAX call to an API to get a list of items for sale from a database. Assume the API returned 4 fields (primary key ID, isAvailable, item name, and item price). You then process the list and create/insert DOM elements for display.
<div id="234" data-isavailable="y" class=".itemsForSale">TV: $800</div>
<div id="345" data-isavailable="y" class=".itemsForSale">Microwave: $200</div>
<div id="456" data-isavailable="n" class=".itemsForSale">Book: $30</div>
<div id="567" data-isavailable="y" class=".itemsForSale">Sofa: $450</div>
You can then set listeners arbitrarily. For instance, you could do...
$( ".itemsForSale" ).click(function() {
// do something
});
The click handler gets applied in "real-time". The code executed is aware of what was clicked and can analyze/interact with that item in the list. So, you can do something like:
$( ".itemsForSale" ).click(function() {
var isAvailable = $(this).attr('data-isavailable');
if( isAvailable == 'n' )
alert('sorry but that item is out of stock!');
else
addItemToCart( $(this).attr('id') );
});
The point being, that the click handler doesn't necessarily know or care about the underlying data of each item. It becomes "context" aware during runtime and then pulls the relevant values out of the UI/etc.
With flutter, I'm looking to recreate similar functionality using a ListView and ListTile. Specifically, ListTile's onTap(). I'm getting confused because it seems like everything is coded "ahead of time".
Here is a basic example (ignore for now I'm not displaying price/etc)...
import 'package:flutter/material.dart';
class SecondScreen extends StatelessWidget {
var mapper = {
'234': 'TV',
'345': 'Microwave',
'456': 'Book',
'567': 'Sofa'
};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body:
ListView.builder(
padding: const EdgeInsets.all(8.0),
itemCount: mapper.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
key: new Key(mapper.keys.elementAt(index).toString()),
title: Text('${mapper.values.elementAt(index)}'),
onTap: () {
print('you clicked: ' + mapper.keys.elementAt(index).toString() );
addToShoppingCart( mapper.keys.elementAt(index).toString() ); // this doesnt exist yet and is just an example
}
);
}
)
);
}
}
First of all, I'm not even sure I need to assign a custom key to each ListView item (based on the database's primary key), but that's irrelevant for now.
What I'm getting stuck on is that the onTap handler doesn't seem to have an equivalent of "$(this)". Does flutter give each ViewTile it's "own" copy of onTap() and that each relevant key info is "hardcoded" into each one (for each itemBuilder loop, the current value of mapper.keys.elementAt(index).toString() is substituted into onTap() )?
I'm probably not describing my confusion properly and I'm not even sure all of the issues I'm going to run into. I'm sure my way of doing this is very "old" and not aligned with react and other newer approaches/frameworks (combining data within the UI elements).
I'm just having trouble seeing how I'm going to be able to add more complex logic (like checking availability of what was clicked).
Does anyone have any references or explanations that help me bridge this gap? I don't even know how to describe the concept of $(this) and how I would search for it in comparison to flutter/etc. I just know that something feels very different.
Thanks!
I understand your confusion probably because I'd a similar question when I started with Flutter just a few months back. And here is what I think -
It doesn't really matter whether the ListTile item has it's own copy of onTap() or not. A. It does not have it's own copy. (Following code snippet will explain) and B. Every programming language / SDK / whatever has its own way of working. What you are trying to say, probably, is that you've a BMW. It has got certain type of breaks. And you are trying to make exact type of break in Audi. It may not be right to do it as the other systems related to the breaks may not work in the optimised way.
Now, look at the following code snippet :
#override
Widget build(BuildContext context) {
return ListView.separated(
separatorBuilder: (context, index) => ListViewDivider(),
itemCount: faceRecTransactions.length,
itemBuilder: (BuildContext context, int index) {
return FacerecTransactionListItem(facerecTransaction: faceRecTransactions[index], onTap:()=> _onTap(faceRecTransactions[index],context),);
},
);
}
void _onTap(FacerecTransaction facerecTransaction, BuildContext context) {
print('Status : ${facerecTransaction.userId}');
Navigator.push(context, MaterialPageRoute(builder: (context) => FacerecDetailPage(
facerecTransaction: facerecTransaction,
criminalList: this.criminalList,)));
}
There's no copy of onTap for every list item. It just 'feels' as it has because we write onTap inside ListView. If you look at the example above, when the user taps on certain item, then the onTap receives the information. We don't create array of onTap as such.
Since, there's no copy of onTaps, it's not really ahead of time code. It's pretty much works like in Ajax where the onTap doesn't really care about the payload. It just does the action(s) specified.
Now, in the above example, the Detailed Page can check the availability of the particular item. And if the item is not available then either appropriate message can be shown. Depending on the requirement, we can either write this code within onTap or on the next page. In short, the onTap can handle the complex logic as you need.