I have two RxLists containing custom models:
var openDepartures = RxList<Departure>([]);
var finishedDepartures = RxList<Departure>([]);
I bind a stream to populate these RxLists with values from firebase. Since my Stream(which I am binding to the variables) changes according to the user's choice, I bind the new stream to the same variable, but since that would result in two streams controlling one variable, I "reset" the variable before that:
openDepartures = RxList<Departure>();
finishedDepartures = RxList<Departure>();
....
openDepartures.bindStream(
Database().getOpenDepartures(
userController.userLocation.value,
),
);
finishedDepartures.bindStream(
Database().getFinishedDepartures(
userController.userLocation.value,
),
);
The problem is that the UI is not refreshing, but when I do not "reset" the variables, everything works fine. What's weird as well is that the variable does get populated with the correct data. It is just not shown in the UI.
What I have tried to fix that:
update() method
Get.reloadAll()
call .refresh() on the variables
disposing and initializing the controller again
My question is now, how do I get the screen to refresh, or does anyone have an idea how to bind a new stream without "resetting" the old one and having multiple streams on one variable?
I finally solved my problem. The underlying problem was that I was binding multiple streams to one variable and therefore the variable was updated whenever one of the streams fired. The solution was the following:
Create an RxList of the variable:
final _openDepartureList = RxList<RxList<Departure>>();
Create a getter for the last element of that list:
RxList<Departure> get openDepartures => _openDepartureList.last;
Remove the old items:
if (_openDepartureList.length >= 2) {
_openDepartureList.removeRange(0, _openDepartureList.length - 1);
}
Add an empty RxList to the List
_openDepartureList.add(RxList.empty());
Bind the stream to the last element of the List, hence the one just added
_openDepartureList.last.bindStream():
That was it! I now always have the right stream bound to the variable I want and do not have multiple streams controlling one variable.
use update() like
void() { foo = newData; update(); }
or use ProgressIndicator
like
RxBool loading = false.obs
void Foo() { loading.value = true; your logic... loading.value = false; }
and use controller.loading.value in UI for check progress and show when loading is complete
Related
TLDR
I like to really focus on keeping business logic away from the view model / controller. I find this sometimes rather hard in Meteor. Maybe I'm missing the point but I am after one of two things really:
1) A really good document explaining at a really low level how reactive values are being used.
2) A package that somehow manages an object so that if any of the setters are altered, they notify all of the get functions that would change as a result.
Unfortunately I've not seen either.
My Example
I have a fair bit ob business logic sitting behind a dialog used to document a consultation. I might have an event that sets a change of state.
I'd like to do something like this in the event:
const cc = new ConsultationEditor();
cc.setChiefComplaint(event.target.value);
console.log(cc.data());
ConsultationDict.set("consEdit", cc.data() );
When the user has updated this value, I'd then like to show a number of fields, based on the change. For this I have a helper with the following:
fields: function(){
console.log("trying to get fields");
const obj = ConsultationDict.get('consEdit');
cc = new ConsultationEditor(obj);
return cc.getFields();
}
But unfortunately this does not work for me.
What is your ConsultationDict?
The way you describe it, you want it to be a ReactiveDict as in the official ReactiveDict package.
https://atmospherejs.com/meteor/reactive-dict
Check this tutorial for examples:
https://themeteorchef.com/snippets/reactive-dict-reactive-vars-and-session-variables/
If you really need more fine tuning in your reactivity, you can also set a dependency tracker tracker = new Tracker.Dependency, and then refer to it wherever you change a variable with tracker.changed() and where the data needs to be notified with tracker.depend() like this:
var favoriteFood = "apples";
var favoriteFoodDep = new Tracker.Dependency;
var getFavoriteFood = function () {
favoriteFoodDep.depend();
return favoriteFood;
};
var setFavoriteFood = function (newValue) {
favoriteFood = newValue;
favoriteFoodDep.changed();
};
getFavoriteFood();
See the full Tracker doc here:
https://github.com/meteor/meteor/wiki/Tracker-Manual
I also found this gist to be useful to build reactive objects:
https://gist.github.com/richsilv/7d66269aab3552449a4c
and for a ViewModel type of behavior, check out
https://viewmodel.meteor.com/
I hope this helps.
I understand that the return from one lambda is fed into the arguments of the next. However, what about if multiple pieces of data need to be passed, or the return type of one lambda is already set by the program structure?
Here is my working code where both of these are the case for opening up a file picker and then reading its contents as text while remembering what the file it came from was:
create_task(picker->PickSingleFileAsync())
.then([this](StorageFile^ file)
{
if (file == nullptr) cancel_current_task();
m_OpenFilename = file->Name;
return FileIO::ReadTextAsync(file);
})
.then([this](String^ fileContents)
{
//do something with the filename and file contents
});
Note that in order to make this work, I needed to add a class variable to store the filename in between the asynchronous tasks. This strikes me as bad for a number of reasons:
It is ugly having a class variable for the internal use of a single method
It this thread-safe? If someone goes nuts opening file pickers and selecting files, would these asynchronous tasks potentially clobber each other when accessing m_OpenFilename?
This is only a trivial example with one variable, but let's say I also want to keep track of the path of the file, and its file attributes, and a number of other characteristics. Now the class is looking uglier and uglier as the number of class variables increase.
My first approach was to have a variable local in scope to the function, and to pass it into each of the lambda functions by altering their capture lists to be [this, OpenFilename]. However, this would fail because by the time the lambda executed, C++/CX's background memory handlers would have already discarded Openfilename, resulting in an access violation when accessing it.
In my example, how can I pass the metadata of the file along to the results of ReadTextAsync so that I can have access to both the file and its contents at the same time?
The easiest way is to just continue building a chain of nested continuations:
auto picker = ref new FileOpenPicker();
picker->FileTypeFilter->Append(L".txt");
picker->SuggestedStartLocation = PickerLocationId::Desktop;
auto task = create_task(picker->PickSingleFileAsync()).then(
[](StorageFile^ file)
{
auto name = file->Name;
auto task = create_task(file->OpenReadAsync()).then(
[name](IRandomAccessStreamWithContentType^ iras)
{
OutputDebugString(name->Data());
});
});
If you don't want to do that (for whatever reason) another option is to use a shared_ptr to hold the value; in this case I'm going to hold on to the name and the created date in a helper file_info type:
struct file_info
{
Platform::String^ name;
Windows::Foundation::DateTime created;
};
auto picker = ref new FileOpenPicker();
picker->FileTypeFilter->Append(L".txt");
picker->SuggestedStartLocation = PickerLocationId::Desktop;
auto info = std::make_shared<file_info>();
auto task = create_task(picker->PickSingleFileAsync()).then(
[info](StorageFile^ file)
{
info->name = file->Name;
info->created = file->DateCreated;
return create_task(file->OpenReadAsync());
}).then(
[info](IRandomAccessStreamWithContentType^ iras)
{
OutputDebugString(info->name->Data());
OutputDebugString(L"\n");
wchar_t datetime[100];
_i64tow_s(info->created.UniversalTime, datetime, 100, 10);
OutputDebugString(datetime);
});
I have an AdvancedDataGrid with a HierarchicalCollectionView as its dataProvider. When I view the grid with the dataset I'm working with, and click the header of the column I wish to sort on, everything works perfectly. It sorts it hierarchically exactly how I would expect it to.
What I want to do now is have the grid already be sorted when it is shown to the user. Is there a way to do this programatically? I can't for the life of me figure out how to do this, and clearly it's possible since the AdvancedDataGrid has this built in.
Edit - BTW, I've tried this:
var myData:HierarchicalCollectionView = new HierarchicalCollectionView(theDataSource);
// Works fine using only the line above and clicking the header to sort.
// This is the part that I tried adding:
var sort:Sort = new Sort();
sort.fields = [new SortField("startDate")];
myData.sort = sort;
myData.refresh();
This appears to do something as far as sorting goes, but it doesn't sort it in the same way as clicking the column header. "startDate" is a property of an object in theDataSource by the way.
Looks like you want to sort dates. Sort can't do that out of the box. You have to use a compareFunction.
If your objects are of type Date it's quite easy:
var sortField:SortField = new SortField("startDate");
sortField.compareFunction = ObjectUtil.dateCompare;
In case your column contains dates as strings you'll have to parse them first (code example from http://blog.flexexamples.com/2007/08/12/sorting-date-columns-in-a-datagrid/):
private function date_sortCompareFunc(itemA:Object, itemB:Object):int
{
/* Date.parse() returns an int, but
ObjectUtil.dateCompare() expects two
Date objects, so convert String to
int to Date. */
var dateA:Date = new Date(Date.parse(itemA));
var dateB:Date = new Date(Date.parse(itemB));
return ObjectUtil.dateCompare(dateA, dateB);
}
var sortField:SortField = new SortField("startDate");
sortField.compareFunction = date_sortCompareFunc;
Then just use the sortField like you did in your example. That should work fine.
You can create a new advanced data grid sort event and dispatch it on the grid after the hierarchical data is set on it (unfortunately I've had to use a callLater to give the grid time to deal with the collection internally it seems assignments to the dataProvider of the ADG are sometimes asynchronous)
var advancedDataGridEvent : AdvancedDataGridEvent = new AdvancedDataGridEvent(AdvancedDataGridEvent.SORT, false, true);
advancedDataGridEvent.columnIndex = columnIndex;
advancedDataGridEvent.dataField = dataField;
dispatchEvent(advancedDataGridEvent);
This code is from an extension of ADG so you would want the dispatchEvent to actually be on your instance of the grid if you're not creating an extension.
Also a note from the code:
//setting sortDescending=true on a column does not work as expected. so, until a solution
//is found, this works just as well. the event that is dispatch just tells the column
//to reset. so, one resorts ascending (the default), while a second resorts descending.
//however, this second is only dispatched if defaultSortDesc is true on the grid.
if (defaultSortDesc)
{
dispatchEvent(advancedDataGridEvent);
}
It dispatches the event twice to flip the sort.
In flex component life cycle, after we make some change in a components property, invalidation methods schedules a call to methods like commitProperties, updateDisplayList, etc for some later time. I need to call the updateDisplayList instantaneously. Is there some direct way to do this.
Currently, both the labels are changed simultaneously after completion of the loop. Instead I need it to work like this, to first render the updated 'myButton1' label then enter the loop and then update myButton2's label. I know, it is elastic race track issue, but isn't there some way to achieve this ?
myButton1.label = 'New Label1' ;
// Some logic to forcibly make the screen reflect it
for (var i:int = 0; i < 500 ; i ++){
//a dummy loop
}
myButton2.label = 'New Label2' ;
You can use myButton1.validateNow() but it's use is discouraged, for you may end up having the same component update itself multiple times on the same frame.
Use validateNow() . But, I would use it sparingly. using invalidateDisplayList() will force updateDisplayList() to run on the next renderer event.
A render event happens on each frame. 24 frames happen each second by default for Flex. Are you sure need to change these values quicker?
I would set the label for myButton1, then put the remaining code into a separate method and call that method using callLater:
private function foo():void {
myButton1.label = 'New Label1';
this.callLater(bar);
}
private function bar():void {
for (var i:int = 0; i < 500 ; i ++){ //a dummy loop
}
myButton2.label = 'New Label2';
}
This way myButton1 will update to show the new label before going into your loop since callLater doesn't call bar until after the event queue is cleared, giving myButton1 a chance to update.
invalidateDisplayList() or valdiateNow() will do it for you however excess of using these will end up memory leaks.
I have a UI component that, for various reasons, I have to construct programatically. The component is a table of radio buttons grouped by column.
Right now, I'm constructing the column groups like so:
private function createGroupsForItemList(items: XMLList): void {
for each (var item: XML in items) {
var rbGroup: RadioButtonGroup = new RadioButtonGroup();
groups[item.#level.toString()] = rbGroup;
}
}
I'm trying to associate the RadioButton instances with the column groups like so:
private function createValueControl(item: XML): UIComponent {
var control: RadioButton = new RadioButton();
control.label = "";
control.group = groups[item.#level.toString()];
control.addEventListener(Event.SELECT, updateSelection);
return control;
}
I can see in the debugger that the control has an association to the group:
control.group == groups[item.#level.toString()]
However, I can see equally that the group does not know anything about the control:
group.radioButtons.length == 0
I imagine that this is because the setter for group in RadioButton is a dumb setter; all it does is copy to the variable, which doesn't do the magic that groupName does. However, I can't seem to find the value I should use to set the RadioButton.groupName property correctly.
So, in short, I'm stumped on how to get these bits to talk to each other. How do I do this?
-- EDIT --
It turns out that I can have the groups created and associated simply by setting the groupName property, but I can't get at the group to set up a selection listener; the group is NULL immediately after the setting process, which means that the second line below throws the Flex equivalent of an NPE:
control.groupName = groupNameForLevel(item);
control.group.addEventListener(Event.SELECT, updateSelection);
First instinct is that this issue has to do with invalidateDisplayList and when and how that is called. Of course, since issues related to that function are behind a number of Flex's quirks, I may just be scapegoating.
This is not the answer to your question per se, but it seems like it might actually work as an alternate solution.
RadioButtonGroups will initialize based on a IFlexDisplayObject. This means that you can do something like:
var c:HBox = new HBox();
var rbg:RadioButtonGroup = new RadioButtonGroup( c );
// do stuff with rbg.
c.addChild( new RadioButton() );
The problem is that it may not be the most practical answer, but it has the decided benefit of being a workable solution.
Setting groupName should work.
All I can suggest is to step through the group() getter of the RadioButton component and see where exactly it is failing. Are you programmatically creating the group too? If that's the case, maybe it isn't initialized fully yet.