google / agera

Reactive Programming for Android
Apache License 2.0
7.2k stars 639 forks source link

How does the updateable know that himself was removed from obserable, or make others konw that #105

Closed cheneyang closed 8 years ago

cheneyang commented 8 years ago

Thanks firstly, agera is amzing, but I think there is a flaw: when we do something like network, we always cancel the behavior under some circumstances, we need a callback like onCancel, I know that the onDeactivation method may tell us all the updateable was removed, but if I use RepositoryCompiler to get a Repository, I can not know that because the onDeactivation is protected, is there anyway to achieve this?

maxtroy commented 8 years ago

I see multiple angles and am not sure which one suits your underlying requirements, so here they are in the same reply.

I need to dispose of resourceful objects (like Closeables) in the middle of the data processing flow in a CompiledRepository that could go nowhere because the flow is cancelled.

We've just commit a new feature that allows you to configure a "disposer" (of type Receiver<Object>) that will be called with the unused intermediate values from the compiled Repository. Give it a try!

I need to take care of something else when an Observable is deactivated, and I can't go into that Observable to override the observableDeactivated method.

If you have other responsibilities to take care of, chances are you might want to wrap the target Observable and these responsibilities into the same entity, such as a custom Observable that looks like this:

public final class WrappedRepository
    extends BaseObservable implements Repository<Blah> {
  private final Repository<Blah> targetRepository;
  private final Updatable targetRepositoryUpdatable;

  public WrappedRepository(... /* params needed to build targetRepository */) {
    targetRepository = Repositories.repositoryWithInitialValue(...)
        ... // prefer building the target repository here, because you can then
            // totally "own" it, rather than giving the external world a chance
            // to sneak in their updatables.
    targetRepositoryUpdatable = () -> dispatchUpdate();
  }

  protected void observableActivated() {
    targetRepository.addUpdatable(targetRepositoryUpdatable);
    // any additional activation handling?
  }

  protected void observableDeactivated() {
    targetRepository.removeUpdatable(targetRepositoryUpdatable);
    // any additional deactivation handling?
  }

  public Blah get() {
    return targetRepository.get();
  }
}

[Just so you're aware, onDeactivation is not a callback; it is a configuration method for the CompiledRepository.]

I just want to have an onCancel callback inside the updatable; you know, like a traditional callback interface with 3 methods: onResult, onError and onCancel.

Let's discuss "responsibility".

In a typical asynchronous call pattern, the callback is owned by the consumer code of the async method. The consumer code is also the entity that cancels the request -- the async method itself should not actively cancel its requests, or the design feels wrong. This means, at the time the consumer code cancels a request, it can simply handle the cancellation actively rather than using an onCancel method in the callback. (Please do compare this to the case where the async method could not complete the request due to other entities cancelling: the most proper outcome is an error along the line of a CancellationException, which the callback will receive via onError instead.)

In the Agera world, your code owns the updatable; or at least, the process to add the updatable to the observable and remove it from the observable. "Cancellation" is an internal concept only in the CompiledRepository; they only happen if the CANCEL_FLOW configuration is used in onDeactivation, and when all updatables are removed from the Repository. This typically means that, at the time of removing the updatable, your code will know that a network request is potentially cancelled, and can actively perform any cleanup work that the Repository's data processing flow might leave behind.

So please try rethinking the design and explore other ways to achieve the same result more directly.

ghost commented 8 years ago

To add to the comment by @maxtroy

If the API used for your request is asynchronous, the cancel method used can only work in a few way. Cancel could either mean

  1. Don't return the value when you're done, or
  2. Stop the execution mid way

And (3.) In both cases it's common that it also means "don't start if you haven't already started"

Using Agera the first and last will be handled for you anyway, when adding/removing updatables. The second case is handled too; For sync APIs moved to a separate thread using the goTo operator, a compiled repository can be configured to send an interrupt signal to the thread. This is done using SEND_INTERRUPT. Many standard (sync) Java IO APIs supports this mechanism already, and the operation will be interrupted.

As a last note: If your API doesn't support sync calls (and I'd def. recommend using sync APIs with Agera!), it means that you're currently forcing the thread used in goTo to wait for an async response coming back to return the value. Whatever "wait mechanism" used it most likely will throw a checked exception saying the operation has been interrupted. This is where possible cancel methods could be called. Note however that if they themselves aren't interrupting the execution (the second case of the three above) it will have very little value.