I am new to flutter and I am sure there is a simple way of doing this. Let me first give you a background. I have 2 tables(collections). The first one store a mapping. Therefore it returns a key based on an id which will be used to query the second table and retrieve the data from firebase.
I have written 2 data models and 2 functions which return Future<> data. They are as follows-
Future<SpecificDevice> getSpecificDevice(String deviceId) {
Future<SpecificDevice> obj =_database.reference().child("deviceMapping").orderByChild("deviceId").equalTo(deviceId).once().then((snapshot) {
SpecificDevice specificDevice = new SpecificDevice(deviceId, "XXXX", new List<String> ());
if(snapshot.value.isNotEmpty){
print(snapshot.value);
snapshot.value.forEach((key,values) {
if(values["deviceId"] == deviceId) {
specificDevice.deviceKey = values["deviceDesc"];
specificDevice.vendorList = List.from(values["vendorList"]);
}
});
}
return specificDevice;
});
return obj;
}
This function gets the mapping deviceId -> deviceKey.
This is the key of record stored in another table. Following is the function for it.
Future<Device> getDeviceDescription(String deviceKey) {
Future<Device> device = _database.reference().child("deviceDescription").once().then((snapshot) {
Device deviceObj = new Device("YYYY", "YYYY", "YYY", "YYYY", "YYYY");
if(snapshot.value.isNotEmpty){
print(snapshot.value);
//Future<SpecificDevice> obj = getSpecificDevice(deviceId);
//obj.then((value) {
snapshot.value.forEach((key,values) {
if(key == deviceKey) { // compare with value.deviceKey instead
print(values["deviceDescription"]); // I get the correct data here.
deviceObj.manual = values["deviceManual"];
deviceObj.deviceType = values["deviceType"];
deviceObj.description = values["deviceDescription"];
deviceObj.brand = values["deviceBrand"];
deviceObj.picture = values["devicePicture"];
}
// });
});
}
return deviceObj;
});
return device;
}
Now both of these functions work. I want to make it work one after the other. In the above function, if I uncomment the lines of code, the data is retrieved properly in the inner function but it returns initial default values set because the values get returned before setting the obj of SpecificDevice.
Here is where I am getting the error. I am calling the second function in FutureBuilder<> code with the above lines uncommented and taking input param as deviceId.
return FutureBuilder<Device>(
future: getDeviceDescription(deviceId),
builder:(BuildContext context,AsyncSnapshot snapshot){... // using snapshot.data in its child.
Here in snapshot.data. would give me YYYY. But it should get me the value from the database.
I am stuck with this for a while. Any help in fixing this? or if what I am trying to do is clear then please suggest me a better way to approach this. Thanks in advance!
The answer is rather simple:
first and foremost - you forgot to use async / await keywords, which will guarantee synchronous data retrieval from the database. Always use them, if you are connecting to any network service
to make one command work after another - use .then((value) {}). It will get data from the first function (which you pass using return) and use it in the second function.
Solved the problem by changing the calling function to -
return FutureBuilder<Device>(
future: getSpecificDevice(deviceId).then((value){
return getDeviceDescription(value.deviceKey);
}),
builder:(BuildContext context,AsyncSnapshot snapshot){
I want to load all the items on start without showing any message, but once after loaded. I want to capture any new row in subscriber and show it to the desktop notification.
The problem is, I'm not sure how to check if all the previous items are loaded and if the row is new item or is it from previous existing item.
this.items = this.af.database.list('notifications/'+this.uid+'/');
this.items.subscribe(list => {
list.forEach(row => {
// doing something here...
});
// once all the rows are finished loading, then any new row, show desktop notification message
});
I have user lodash for the minimal code.
// this varible holds the initial loaded keys
let loadedKeys = [];
this.items = this.af.database.list('notifications/'+this.uid+'/');
this.items.subscribe((list)=>{
// we skip it while initial load
if(!_.isEmpty(loadedKeys)){
// different is the new keys
let newKeys = _.difference(_.map(list, "$key"), loadedKeys);
if(!_.isEmpty(newKeys)){
// ... notification code here
}
}
loadedKeys = _.map(list, "$key");
});
The behave you are looking for is the default Subject approach in RxJS.
Check this reactiveX url to follow the marble diagram of Publish Subject (the equivalent for Subject in RxJS).
So you have two easy options:
1) manually index witch rows you want to display like #bash replied
2) create a Rx.Subject() and assign only the newest's rows to it. Then you subscribe to this subject in your app workflow.
The advantage of method 2 is when a new .subscribe occur, it will not retrieve previous data.
Edit: I wrote this codepen as a guide to implement your custom RxJS Subject. Hope it helps.
Assuming your rows have something unique to match with previous rows you can do the following:
// A Row item has a unique identifier id
interface Row {
id: number;
}
this.rows: Row[];
this.items$ = this.af.database.list(`notifications/${this.uid}/`).pipe(
tap(list => {
// if rows is not array, first time...
if(!Array.isArray(this.rows)) {
// first time nothing to do
return;
}
// returns true if some item from list is not found in this.rows
const foundNewRow = list.some(item => {
return ! this.rows.find(row => row.id === item.id);
});
if (foundNewRow) {
// call method to show desktop message here
}
}
);
I used a pipe and a tap operator (that you will have to import). If you subscribe to this.items$ the tap operator will do the work:
this.items$.subscribe((items => this.rows = items));
If you do not want to set this.rows when normally subscribing than you can also do this in the tap operator. But that would assume you only use it for checking difference between existing and new items.
I am trying to implement a filter on my custom datasource for a FuelUX datagrid.
It filters the data properly but leaves it paginated as though it was not filtered. I.E. I have to either increase the results per page or go to the next page to see the results.
How do I get the grid to update to display the filtered results properly?
Here is my custom filter function:
if (options.filter) {
data = data.filter(function (item) {
switch( options.filter.value )
{
case "all":
return true;
break;
default:
return item.contentID == options.filter.value;
break;
}
});
}
Good catch! I have entered an issue for this at https://github.com/ExactTarget/fuelux/issues/143
In the meantime, just add this logic after you load Fuel UX but before you initialize your datagrid.
$.fn.datagrid.Constructor.prototype.filterChanged = function (e, filter) {
this.options.dataOptions.filter = filter;
this.options.dataOptions.pageIndex = 0;
this.renderData();
};
Thank you for the report.
I can't make my multiple-check-box filtering system to work. I'll explain the problem, the research I've done here on stackoverflow, and why I still need help after that.
My problem is that my check boxes can't bring back the markers when I gradually unselect them. These said filters work well when I click them, because they incrementally fade away the markers associated with them. However, after just unselecting a couple of these checkboxes, all the markers are back on screen, and the last boxes don't do anything when they are finally unclicked.
This is the temporary URL of the project: http://www.lcc.gatech.edu/~amartell6/php/main12.php
This is the code where I'm getting stuck:
//this getJson function exists within an init funciton where a map
//has already been called
$.getJSON(theUrl,function(result){
$.each(result, function(i, item){
//get Longitude
var latCoord = item.coordinate;
var parenthCoord = latCoord.indexOf(",");
var partiaLat = latCoord.substr(1,parenthCoord-1);
var lat = parseFloat(partiaLat);
//alert(lat);
//get Latitude
var lngCoord = item.coordinate;
var commaCoord = lngCoord.indexOf(",");
var partiaLng = lngCoord.substr(commaCoord+1);
var lng = parseFloat(partiaLng);
//alert(lng);
// display ALL the story markers
var storyMarker;
storyMarker = new google.maps.Marker({
position: new google.maps.LatLng(lat, lng),// ----- > whithin the mutidimentional array,
map: map
});
//display the stories by clicking on the markers
google.maps.event.addListener(storyMarker, 'click', function() {
var from = "From ";
if(item.end_date != ""){
item.end_date = " to " + item.end_date;
}
else{
from = "";
}
$('#output').html(
'<p><span class="selected">Type of Entry: </span>' +
item.entry_type + ' <br/><br/>'+
'<span class="selected">Title: </span>'+ item.entry_title + '<br/><br/>' +
'<span class="selected">Date(s):</span><br/>'+ from +item.start_date+
//' to '+item.end_date+'<br/><br/>'+
item.end_date+'<br/><br/>'+
'<span class="selected">Content:</span><br/><br/> '+ item.entry
+'</p>'
);
});// end of story displays
//call filters from filter funciton
filter('#evacuation-filter',item.evacuation,"Yes");
filter('#evacuation-order-filter',item.evacuation_order,"Yes");
filter('#w-nearby-filter',item.w_nearby,"Yes");
filter('#hurricane-reached-filter',item.hurricane_reached,"Yes");
filter('#outdoors-filter',item.in_out_doors,"Outdoors Most of the Time");
filter('#indoors-filter',item.in_out_doors,"Indoors Most of the Time");
filter('#food-filter',item.food,"Yes");
filter('#windows-filter',item.windows,"Yes");
filter('#power-filter',item.power,"Yes");
filter('#wounded-filter',item.wounded,"Yes");
filter('#looting-filter',item.looting,"Yes");
filter('#blackouts-filter',item.blackouts,"Yes");
filter('#trees-filter',item.trees,"Yes");
filter('#powerlines-filter',item.powerlines,"Yes");
filter('#light-filter',item.light,"Yes");
filter('#sidewalks-filter',item.sidewalks,"Yes");
filter('#buildings-filter',item.buildings,"Yes");
filter('#flooding-filter',item.flooding,"Yes");
//FILTER FUNCTION
//first parameter is the checkbox id, the second is the filter criteria
//(the filter function has to be called within the $.each loop to be within scope)
var otherFilter = false;
function filter(id, criterion1, value){
var activeFilters = [];
$(id).change(function() {
//evalute if the checkbox has been "checked" or "unchecked"
var checkBoxVal = $(id).attr("checked");
//if it's been checked:
if(checkBoxVal=="checked"){
//1 - Get markers that don't talk about the filter
if(criterion1!=value && storyMarker.getVisible()==true){
//2 - fade them away, and leave only those meet the criteria
storyMarker.setVisible(false);
otherFilter = true;
activeFilters.push(criterion1);
//document.getElementById("text3").innerHTML=activeFilters+"<br/>";
//alert(activeFilters.push(criterion1) +","+criterion1.length);
}
}
//if it's been unchecked:
else if(checkBoxVal==undefined){
//1 - Get markers that don't talk about the filter
if(criterion1!=value && storyMarker.getVisible()==false){
//2 - Show them again
storyMarker.setVisible(true);
otherFilter = false;
activeFilters.pop(criterion1);
//alert(activeFilters.pop(criterion1) +","+criterion1.length);
} //end of if to cancel filter and bring markers and stories back
}
}); // end of change event
} // end of filter function
//var otherDropDown = false;
filter2("#media-filter",item.media);
filter2("#authorities-filter",item.authorities);
//---------------
function filter2(id2,criterion2){
$(id2).change(function() {
//get the value of the drowpdown menu based on its id
var dropDownVal = $(id2).attr("value");
var all="All";
//if the value isn't "All", other filters have not been applied, and marker is on screen
if(dropDownVal!=all && otherFilter==false){
//1 - check if the marker doesn't comply with filter
if(criterion2!=dropDownVal){
//2 - fade them away if not, and leave only those meet the criteria
storyMarker.setVisible(false);
//3 - If the marker does comply with it
}else if(criterion2==dropDownVal){
//4 - keep it there
storyMarker.setVisible(true);
}//end of filter applier
//else if if the value IS "All", filters have not been applied, and marker is faded
}else if(dropDownVal==all && otherFilter==false){
//select all the possible values for the cirterion
if(criterion2!=undefined){
//and show all those markers
storyMarker.setVisible(true);
}
}
});
} //end of function filter2
}); // end of $.each
}); // end of $.getJSON
I found one related blog post. This one suggests adding a category to the markers. However, when I do that, the filters keep working the same way. I think this happens because each filter is programmed to hide every single marker that meets their selecting criteria, but each marker has more than one property they can be filtered with.
Do you know if there is a way to make the script detect how many filters point towards the same marker, and only show it back if no filters are pointing at it? This is my guess on how to solve it, even though I don't know how to make it happen in code.
Finally, if you know of alternate ways to make the filters work, let me know.
I created an application with similar logic several years ago http://www.ioos.gov/catalog/ But it was for GMap 2.0 but I think the logic would be the same.
My approach was to extend the Google maps Marker object (already bloated) with features I wanted to filter them on.
These would be all the properties you're storing in your 'click' listener and perhaps more: e.g. item.title, item_start_date, etc. whatever you eventually want to filter your marker by.
var all_markers = [];
storyMarker.end_date = item.end_date;
storMarker.title = item.title;
...
all_markers.push(storyMarker);
Then when you want to filter loop thru all the markers, check the marker value against the filter condition and setVisible(true) or false as need.
Erik already provided a solution to my problem. However, I think the community may benefit from reading other options, and I want to share the solution I came up with. Even if it may not be the most effective, it works.
In the code I just mentioned, I declared all the storyMarkers at once when the map initializes:
// display ALL the story markers
var storyMarker;
storyMarker = new google.maps.Marker({
position: new google.maps.LatLng(lat, lng),// ----- > whithin the mutidimentional array,
map: map
});
Now, I added a new argument to the markers, but instead of creating a variable as in the example I had found in other post, this argument was an empty array:
storyMarker.pointer = [];
The previous filter function had three levels. The first level detected a change in the check box. The second one verified whether the check box had been checked or unchecked. The third level ran the filter on e-v-e-r-y marker, either to show it or hide it.
This is where my solution began. Within the most inner if statement of the filter function, I added a discretionary element within the pointer array:
storyMarker.pointer.push("element");
Right after this, I nested a new if statement to check if the array is not empty. If it indeed isn't empty, the program hides the marker that this array belongs to.
The program inverses the logic when a box is unchecked. It calls-off the filter, subtracts one element from the array associated with that marker, and then checks if there are other markers associated with it. The system now only shows up markers whose arrays are empty.
//alert(storyMarker.pointer);
function filter(id,criterion,value){
$(id).change(function() {
var checkBoxVal = $(id).attr("checked");
if(checkBoxVal=="checked"){
if(criterion!=value){
storyMarker.pointer.push("element");
//alert("array length: "+storyMarker.pointer.length);
if(storyMarker.pointer.length>0){
storyMarker.setVisible(false);
}
}
}
else if(checkBoxVal!="checked"){
if(criterion!=value){
storyMarker.pointer.pop("element");
//alert("array length: "+storyMarker.pointer.length);
if(storyMarker.pointer.length<=0){
storyMarker.setVisible(true);
}
}
}
});
}
In summary, the script is still clicking a marker more multiple times if the user clicks on more than one marker. The system can now recognize how many times is one marker pointed out, and only show the one that has no pointers at all.
I've got a backbone collection and I'm trying to filter by an id within the attributes
basically, a user has classes, and the class has a location_id, and I want to filter by the location id. My collection looks like this to give you an idea.
-user
-models
-0
-attributes
-location_id
-1
-attributes
-location_id
-2
-attributes
-location_id
I thought I could filter this by using
var get_locations = user_class_collection.filter(function(classes){
console.log(classes);
return classes.get(location_id)==location.id;
});
console.log(get_locations);
but that is returning an empty array, when I know the location_id is in the collection.
Any idea why this isn't working? I've also tried to grab classes.attributes.get, but it wasn't any better
In the first few responses, it was properly mentioned that I had to quote the get('location_id'). I've now done that, but unfortunately, I'm still getting an empty array. I thought that the filter would loop through the classes and I would get a console output for each class, but the console.log(classes) is only getting triggered once. Is that a hint? Or a red-herring?
you are trying to get a property from classes that is named as the value of the location_id parameter
you should instead make that a string (in fact you can choose how you make it a string, single or double quotes both work)
user_class_collection.filter(function(classes){
return classes.get('location_id') == location.id;
});
For filtering collection using backbone the best approach is to use a filtered function in your collection
var UserCollection = Backbone.Collection.extend ({
filtered : function ( id ) {
I suggest to use UnderScore filter which will return true for valid and false for invalid where true is what you are looking for. use this.models to get the current collection models use model.get( '' ) to get the element you want to check for
var results = _.filter( this.models, function ( model ) {
if ( model.get('location_id') == id )
return true ;
return false ;
});
Then use underscore map your results and transform it to JSON like
results = _.map( results, function( model ) { return model.toJSON() } );
Finally returning a new backbone collection with only results
return new Backbone.Collection( results ) ;
Optionally if you don't want to keep all the data in the collection but just the filtered one you should reset the collection and skip the above return like
this.reset( results ) ;
View call the filtered collection and the render the result
Try this:
user_class_collection.select(function(classes){
return classes.get("location_id")==location.id;
});