I do a remove action through RxJava2 that causes a refresh on my local cache like this:
override fun removeExperience(experienceId: String, placeId: String): Completable {
return from(placesApi.deleteExperience(experienceId, placeId))
.andThen(from(refreshPlace(placeId))
.flatMapCompletable { Completable.complete() }
)
}
so whenever the remove action is done (Completable is complete), a refresh is triggered. The problem is, sometimes this remove action takes long enough for users to just leave the screen, and then the andThen action is never executed cause there is no subscribers anymore, and thus the information on the screen is not up to date anymore.
Is there a way to enforce this action to take place?
Does this logic continue working when user open the same screen again? If so, then you only need to finish subscription from(placesApi.deleteExperience(experienceId, placeId)) on lifecycle events. The easiest way is to add the whole subscription removeExperience() to Disposable or CompositeDisposable and then trigger its .dispose() or .clear() on view stop or destroy events.
.dispose() - doesn't allow to use the same subscription stored.
.clear() - allows re-subscription without creating the new
subscription instance
Related
I am using ag-grid/ag-grid-angular to provide an editable grid of data backed by a database. When a user edits a cell I want to be able to post the update to the backend service and if the request is successful update the grid and if not undo the user's changes and show an error.
I have approached this problem from a couple different angles but have yet to find the solution that meets all my requirements and am also curious about what the best practice would be to implement this kind of functionality.
My first thought was to leverage the cellValueChanged event. With this approach I can see the old and new values and then make a call to my service to update the database. If the request is successful then everything is great and works as expected. However, if the request fails for some reason then I need to be able to undo the user's changes. Since I have access to the old value I can easily do something like event.node.setDataValue(event.column, event.oldValue) to revert the user's changes. However, since I am updating the grid again this actually triggers the cellValueChanged event a second time. I have no way of knowing that this is the result of undoing the user's changes so I unnecessarily make a call to my service again to update the data even though the original request was never successful in updating the data.
I have also tried using a custom cell editor to get in between when the user is finished editing a cell and when the grid is actually updated. However, it appears that there is no way to integrate an async method in any of these classes to be able to wait for a response from the server to decide whether or not to actually apply the user's changes. E.g.
isCancelBeforeStart(): boolean {
this.service.updateData(event.data).subscribe(() => {
return false;
}, error => {
return true;
});
}
does not work because this method is synchronous and I need to be able to wait for a response from my service before deciding whether to cancel the edit or not.
Is there something I am missing or not taking in to account? Or another way to approach this problem to get my intended functionality? I realize this could be handled much easier with dedicated edit/save buttons but I am ideally looking for an interactive grid that is saving the changes to the backend as the user is making changes and providing feedback in cases where something went wrong.
Any help/feedback is greatly appreciated!
I understand what you are trying to do, and I think that the best approach is going to be to use a "valueSetter" function on each of your editable columns.
With a valueSetter, the grid's value will not be directly updated - you will have to update your bound data to have it reflected in the grid.
When the valueSetter is called by the grid at the end of the edit, you'll probably want to record the original value somehow, update your bound data (so that the grid will reflect the change), and then kick off the back-end save, and return immediately from the valueSetter function.
(It's important to return immediately from the valueSetter function to keep the grid responsive. Since the valueSetter call from the grid is synchronous, if you try to wait for the server response, you're going to lock up the grid while you're waiting.)
Then, if the back-end update succeeds, there's nothing to do, and if it fails, you can update your bound data to reflect the original value.
With this method, you won't have the problem of listening for the cellValueChanged event.
The one issue that you might have to deal with is what to do if the user changes the cell value, and then changes it again before the first back-end save returns.
onCellValueChanged: (event) => {
if (event.oldValue === event.newValue) {
return;
}
try {
// apiUpdate(event.data)
}
catch {
event.node.data[event.colDef.Field] = event.oldValue;
event.node.setDataValue(event.column, event.oldValue);
}
}
By changing the value back on node.data first, when setDataValue() triggers the change event again, oldValue and newValue are actually the same now and the function returns, avoiding the rather slow infinite loop.
I think it's because you change the data behind the scenes directly without agGrid noticing with node.data = , then make a change that agGrid recognises and rerenders the cell by calling setDataValue. Thereby tricking agGrid into behaving.
I would suggest a slightly better approach than StangerString, but to credit him the idea came from his approach. Rather than using a test of the oldValue/newValue and allowing the event to be called twice, you can go around the change detection by doing the following.
event.node.data[event.colDef.field] = event.oldValue;
event.api.refreshCells({ rowNodes: [event.node], columns: [event.column.colId] });
What that does is sets the data directly in the data store used by aggrid, then you tell it to refresh that grid. That will prevent the onCellValueChanged event from having to be called again.
(if you arent using colIds you can use the field or pass the whole column, I think any of them work)
I want to check if a pdf file is changed or not, and if is changed i want to update the corresponding view. I don't know if it's more suitable to use a background process as a Thread or as an NSOperation to do this task. The Apple Documentation says: "Examples of tasks that lend themselves well to NSOperation include network requests, image resizing, text processing, or any other repeatable, structured, long-running task that produces associated state or data.But simply wrapping computation into an object doesn’t do much without a little oversight".
Also, if I understood correctly from the documentation, a Thread once started can't be stopped during his execution while an NSOperation could be paused or stopped and also they could rely on dependency to wait the completion of another task.
The workflow of this task should be more or less this diagram:
Task workflow
I managed to get the handler working after the notification of type .write has been sent. If i monitor for example a *.txt file everything works as expected and i receive only one notification. But i am monitoring a pdf file which is generated from terminal by pdflatex and thus i receive with '.write' nearly 15 notification. If i change to '.attrib' i get 3 notification. I need the handler to be called only once, not 15 or 3 times. Do you have any idea how can i do it or is not possible with a Dispatch Source? Maybe there is a way to execute a dispatchWorkItem only once?
I have tried to implement it like this(This is inside a FileMonitor class):
func startMonitoring()
{
....
let fileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: fileStringURL)
let fileDescriptor = open(fileSystemRepresentation, O_EVTONLY)
let newfileMonitorSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fileDescriptor,
eventMask: .attrib,
queue: queue)
newfileMonitorSource.setEventHandler(handler:
{
self.queue.async
{
print(" \n received first write event, removing handler..." )
self.newfileMonitorSource.setEventHandler(handler: nil)
self.test()
}
})
self.fileMonitorSource = newfileMonitorSource
fileMonitorSource!.resume()
}
func test()
{
fileMonitorSource?.cancel()
print(" restart monitoring ")
startMonitoring()
}
I have tried to reassign the handler in test(), but it's not working(if a regenerate the pdf file, what is inside the new handler it's not executed) and to me, doing in this way, it seems a bit boilerplate code. I have also tried the following things:
suspend the DispatchSource in the setEventHandler of startMonitoring() (passing nil), but then when i am resuming it, i get the remaining .write events.
cancel the DispatchSource object and recall the startMonitoring() as you can see in the code above, but in this way i create and destroy the DispatchSource object everytime i receive an event, which i don't like because the cancel() function shoul be called in my case only when the user decide to disable this feauture i am implementing.
I will try to write better how the workflow of the app should be so you can have an more clear idea of what i am doing:
When the app starts, a functions sets the default value of some checkboxes of the window preference. The user can modify this checkboxes. So when the user open a pdf file, the idea is to launch in a background thread the following task:
I create a new queue call it A and launch asynch an infinite while where i check the value of the UserDefault checkboxe (that i use to reload and update the pdf file) and two things could happen
if the user set the value to off and the pdf document has been loaded there could be two situations:
if there is no current monitoring of the file (when the app starts): continue to check the checkboxe value
if there is currently a monitoring of the file: stop it
if the user set value to on and the pdf document has been loaded in this background thread (the same queue A) i will create a class Monitor (that could be a subclass of NSThread or a class that uses DispatchSourceFileSystemObject like above), then i will call startMonitoring() that will check the date or .write events and when there is a change it will call the handler. Basically this handler should recall the main thread (the main queue) and check if the file can be loaded or is corrupted and if so update the view.
Note: The infinite while loop(that should be running in the background), that check the UserDefault related to the feature i am implementing it's launched when the user open the pdf file.
Because of the problem above (multiple handlers calls), i should use the cancel() function when the user set checkboxe to off, and not create/destroy the DispatchSource object everytime i receive a .write event.
In my app I use two contexts: app delegate main context and a private context.
The private context is set as follows:
var privateContext: NSManagedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
privateContext.persistentStoreCoordinator = context.persistentStoreCoordinator
I also set an observer on the private context to trigger a save via main context:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyView.managedObjectContextDidSave(_:)), name: NSManagedObjectContextDidSaveNotification, object: self.privateContext)
I trigger a data download with a callback to the current ViewController. During the download, I process all objects in:
privateContext.performBlock {
// process objects
....
// now save
if self.privateContext.hasChanges {
privateDataManager.save()
}
}
The save in the private context, triggers the observer and this code gets invoked:
dispatch_async(AppUtils.GlobalMainQueue, {
self.context.mergeChangesFromContextDidSaveNotification(notification)
})
The problem is that every now and then, not all changes get persisted. Cannot say when or why - when I debug it it always works...
How do I know I have a problem? Well, I compare object count before and after the download. To add more 'colour':
Every download of data adds some new records. The app then selects which records are out-of-date and marks them for deletion (sets a flag on a record). It then saves them (new and 'to be deleted') in the private context. The 'save' triggers a 'merge into primary context' notification.
In theory, this notification triggers the 'merge' in a synchronous manner.
After the merge, assuming it does happen in-order, there is data reload - this reload only loads records that do not have the 'deleted' flag set.
The problem I am having is that the 'merge' does not seem to always happen before I reload (that's the only way I can explain this).
My question, assuming my analysis is correct, is how to force the merge to happen before the reload? Does the notification not happen in a synchronous manner?
I guess I could always save the private context and instead of triggering the notification, simply create a notification object using private context and force trigger the merge. But I would like to understand why the above code does not work as expected.
The main source on which I based my code can be found here.
The below portion of my code caused recursive call on onEditorExit method. If I remove setData call, then no recursion occur.
What can be the workaround?
myGrid.addEditorExitHandler(new EditorExitHandler() {
public void onEditorExit(EditorExitEvent event) {
GWT.log("Hello");
myGrid.setData(new ListGridRecord());
}
});
Now check the console output -
Console Screenshot
It won't work because each time you call setData() the editor will fire an editorExit event in an infinite loop (not recursively). By the way, calling setData() in the way you are doing will replace all your records in the ListGrid with one new empty record. This seems like a disconcerting user experience.
It looks like you want to create and start editing a new record when you tab out of the last one. In order to do that in a ListGrid, you use:
grid.setListEndEditAction(RowEndEditAction.NEXT);
That's all you need to do in order to get it working.
I have a push notification mechanism (PublishSubject) which triggers http request logic (flatMap). Basic scenario is that whenever a push arrives, single http call is made and results propagated to multiple observers.
I've written a simple demo for the case but flatMap executes for each registered observer, while I would like it to be triggered just once on each push.
PublishSubject<Integer> subject = PublishSubject.create();
Observable<String> obs = subject.asObservable().flatMap(integer -> {
// this code runs for each observer, which is twice in this case
return Observable.just(String.valueOf(integer));
});
Observer mock = mock(Observer.class);
Observer mock1 = mock(Observer.class);
obs.subscribe(mock);
obs.subscribe(mock1);
subject.onNext(1);
Could you suggest a fix?
Thanks
P.S. Right now I'm using cache(1) to fix the issue but I'm not sure if it's ok to do it this way. Moreover, I can't just quite understand why a single stream of execution would depend on amount of observers attached. Can you comment on that?
You are already sort of using it, you need either publish() + connect() or publish().refCount() if you want to make the value shareable by multiple observables. The first case lets you control when to actually make the Observable go hot, while the second will go live as soon as you subscribe the first time. RxJS also has share which wraps publish().refCount() not sure if RxJava has that as well.
PublishSubject<Integer> subject = PublishSubject.create();
Observable<String> obs = subject.asObservable().flatMap(integer -> {
// this code runs for each observer, which is twice in this case
return Observable.just(String.valueOf(integer));
}).publish().refCount();
Observer mock = mock(Observer.class);
Observer mock1 = mock(Observer.class);
obs.subscribe(mock);
obs.subscribe(mock1);
subject.onNext(1);