4

I'm looking for a way to return a result from a Screen using Flow without losing what's been built up in the previous Screen, similar to what I'd do with onActivityResult. For example here's a screen where I'm creating a new document:

@Layout(R.layout.new_document_view)
class NewDocumentScreen implements Blueprint {

  // Imagine some Blueprint boiler plate here

  static class Presenter implements ViewPresenter<NewDocumentView> {
    private final Flow flow;

    private Document newDocument = new Document();

    @Inject Presenter(Flow flow) { this.flow = flow; }

    @Override protected void onLoad(Bundle savedInstanceState) {
        super.onLoad(savedInstanceState);

        NewDocumentView view = getView();
        if (view == null) return;

        view.bindTo(newDocument);  // immediately reflect view changes in document
    }   

    // Imagine this is called by pressing a button in NewDocumentView
    public void chooseDocumentAuthor() {
      // What I want here is to navigate to the chooser screen, make my choice and
      // then return to this screen having set the author on the document.
      flow.goTo(new ChooseDocumentAuthorScreen());
    }
  }
}

How can I do this? I've been experimenting with Popup and PopupPresenter but there's not a lot of info on these and I'm not convinced this is the right way to go since the chooser is a screen in its own right.

Update – Potential Solution

Based on an answer below from @rjrjr I've done the following which seems to work okay:

TakesResult.java

public interface TakesResult<T> {
  // Called when receiving a result
  void onResult(T result);
}

NewDocumentScreen.java

@Layout(R.layout.new_document_view)
class NewDocumentScreen implements Blueprint, TakesResult<Author> {

  private Document newDocument = new Document();      

  @Override public void onResult(Author result) {
    newDocument.setAuthor(result);
  }

  // Imagine some Blueprint boiler plate here

  @dagger.Module(injects = NewDocumentView.class, addsTo = MainScreen.Module.class)
  class Module {
    @Provides Document provideDocument() { return newDocument; }  
    @Provides NewDocumentScreen provideScreen() { return this; }
  }

  static class Presenter implements ViewPresenter<NewDocumentView> {
    private final Flow flow;
    private final NewDocumentScreen screen
    private final Document newDocument;

    @Inject Presenter(Flow flow, NewDocumentScreen screen, Document newDocument) { 
      this.flow = flow; 
      this.screen = screen;
      this.newDocument = newDocument;
    }

    @Override
    protected void onLoad(Bundle savedInstanceState) {
      // Stuff to update view
    }

    // Imagine this is called by the view
    public void chooseDocumentAuthor() {
      // Since screen TakesResult we send it to the ChooseAuthorScreen
      flow.goTo(new ChooseDocumentAuthorScreen(screen));
    }
  }
}

ChooseAuthorScreen.java

@Layout(R.layout.choose_author_view)
class ChooseAuthorScreen implements Blueprint {
  private final TakesResult<Author> resultReceiver;

  ChooseAuthorScreen(TakesResult<Author> resultReceiver) {
    this.resultReceiver = resultReceiver;
  }

  // Imagine some Blueprint boiler plate here

  @dagger.Module(injects = ChooseAuthorView.class, addsTo = MainScreen.Module.class)
  class Module {
    @Provides TakesResult<Author> provideResultReceiver() { return resultReceiver; }  
  }

  static class Presenter implements ViewPresenter<ChooseAuthorView> {
    private final Flow flow;
    private final TakesResult<Author> resultReceiver;

    @Inject Presenter(Flow flow, TakesResult<Author> resultReceiver) { 
      this.flow = flow; 
      this.resultReceiver = resultReceiver;
    }

    // Imagine this is called by the view
    public void chooseAuthor(Author author) {
      resultReceiver.onResult(author);
      flow.goBack();
    }
  }
}
Community
  • 1
  • 1
kuhnza
  • 731
  • 1
  • 5
  • 15
  • There could be case when backstack is killed and recreated from Parcelable. In this case `resultReceiver` probably will be `null`. So I do it Like this `((TakesResult) (flow.getBackstack().reverseIterator().next().getScreen())).onResult(t);` Also I have a case when result should be delivered if user press back button so I do it in `onExitScope()` – oleg.semen Jan 29 '15 at 14:23

1 Answers1

0

Popup and PopupPresenter were probably a mistake. A better technique I've seen is to have the "dialog" screen write its result to a well known transient field on the previous screen object in the Flow backstack. On the one hand this seems pretty hacky. On the other hand, it's very simple and very readable.

rjrjr
  • 3,892
  • 1
  • 22
  • 18
  • Interesting, thanks. I saw you had said something to that effect in an issue over on GitHub. I'll give that a go and let you know how I get on. – kuhnza Nov 02 '14 at 03:05
  • Just added a potential solution to the question above, I took what you suggested but added an interface to formalise the relationship between the two. Thoughts? – kuhnza Nov 02 '14 at 03:52