{"action":"created","issue":{"url":"https://api.github.com/repos/reactiveui/ReactiveUI/issues/1055","labels_url":"https://api.github.com/repos/reactiveui/ReactiveUI/issues/1055/labels{/name}","comments_url":"https://api.github.com/repos/reactiveui/ReactiveUI/issues/1055/comments","events_url":"https://api.github.com/repos/reactiveui/ReactiveUI/issues/1055/events","html_url":"https://github.com/reactiveui/ReactiveUI/pull/1055","id":128505385,"number":1055,"title":"Improve interactions implementation","user":{"login":"kentcb","id":1901832,"avatar_url":"https://avatars.githubusercontent.com/u/1901832?v=3","gravatar_id":"","url":"https://api.github.com/users/kentcb","html_url":"https://github.com/kentcb","followers_url":"https://api.github.com/users/kentcb/followers","following_url":"https://api.github.com/users/kentcb/following{/other_user}","gists_url":"https://api.github.com/users/kentcb/gists{/gist_id}","starred_url":"https://api.github.com/users/kentcb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kentcb/subscriptions","organizations_url":"https://api.github.com/users/kentcb/orgs","repos_url":"https://api.github.com/users/kentcb/repos","events_url":"https://api.github.com/users/kentcb/events{/privacy}","received_events_url":"https://api.github.com/users/kentcb/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/reactiveui/ReactiveUI/labels/enhancement","name":"enhancement","color":"207de5"},{"url":"https://api.github.com/repos/reactiveui/ReactiveUI/labels/rxui","name":"rxui","color":"fef2c0"}],"state":"open","locked":false,"assignee":{"login":"kentcb","id":1901832,"avatar_url":"https://avatars.githubusercontent.com/u/1901832?v=3","gravatar_id":"","url":"https://api.github.com/users/kentcb","html_url":"https://github.com/kentcb","followers_url":"https://api.github.com/users/kentcb/followers","following_url":"https://api.github.com/users/kentcb/following{/other_user}","gists_url":"https://api.github.com/users/kentcb/gists{/gist_id}","starred_url":"https://api.github.com/users/kentcb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kentcb/subscriptions","organizations_url":"https://api.github.com/users/kentcb/orgs","repos_url":"https://api.github.com/users/kentcb/repos","events_url":"https://api.github.com/users/kentcb/events{/privacy}","received_events_url":"https://api.github.com/users/kentcb/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/reactiveui/ReactiveUI/milestones/2","html_url":"https://github.com/reactiveui/ReactiveUI/milestones/ReactiveUI%207.0","labels_url":"https://api.github.com/repos/reactiveui/ReactiveUI/milestones/2/labels","id":1469357,"number":2,"title":"ReactiveUI 7.0","description":null,"creator":{"login":"ghuntley","id":127353,"avatar_url":"https://avatars.githubusercontent.com/u/127353?v=3","gravatar_id":"","url":"https://api.github.com/users/ghuntley","html_url":"https://github.com/ghuntley","followers_url":"https://api.github.com/users/ghuntley/followers","following_url":"https://api.github.com/users/ghuntley/following{/other_user}","gists_url":"https://api.github.com/users/ghuntley/gists{/gist_id}","starred_url":"https://api.github.com/users/ghuntley/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ghuntley/subscriptions","organizations_url":"https://api.github.com/users/ghuntley/orgs","repos_url":"https://api.github.com/users/ghuntley/repos","events_url":"https://api.github.com/users/ghuntley/events{/privacy}","received_events_url":"https://api.github.com/users/ghuntley/received_events","type":"User","site_admin":false},"open_issues":14,"closed_issues":21,"state":"open","created_at":"2015-12-21T10:54:22Z","updated_at":"2016-01-25T10:27:47Z","due_on":null,"closed_at":null},"comments":5,"created_at":"2016-01-25T10:27:47Z","updated_at":"2016-01-25T19:57:20Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/reactiveui/ReactiveUI/pulls/1055","html_url":"https://github.com/reactiveui/ReactiveUI/pull/1055","diff_url":"https://github.com/reactiveui/ReactiveUI/pull/1055.diff","patch_url":"https://github.com/reactiveui/ReactiveUI/pull/1055.patch"},"body":"This PR improves the interactions implementation by:\r\n\r\n* generalizing the name to \"interactions\" rather than \"user interactions\"\r\n* splitting the responsibility for the distribution of interactions into a separate, non-static, class\r\n* lots of smaller improvements to round edges in the API\r\n\r\nSee the documentation for this feature [here](http://docs.reactiveui.net/en/user-guide/interactions/index.html).\r\n\r\nTODO:\r\n- [ ] Decide on naming"},"comment":{"url":"https://api.github.com/repos/reactiveui/ReactiveUI/issues/comments/174640203","html_url":"https://github.com/reactiveui/ReactiveUI/pull/1055#issuecomment-174640203","issue_url":"https://api.github.com/repos/reactiveui/ReactiveUI/issues/1055","id":174640203,"user":{"login":"mteper","id":1029340,"avatar_url":"https://avatars.githubusercontent.com/u/1029340?v=3","gravatar_id":"","url":"https://api.github.com/users/mteper","html_url":"https://github.com/mteper","followers_url":"https://api.github.com/users/mteper/followers","following_url":"https://api.github.com/users/mteper/following{/other_user}","gists_url":"https://api.github.com/users/mteper/gists{/gist_id}","starred_url":"https://api.github.com/users/mteper/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mteper/subscriptions","organizations_url":"https://api.github.com/users/mteper/orgs","repos_url":"https://api.github.com/users/mteper/repos","events_url":"https://api.github.com/users/mteper/events{/privacy}","received_events_url":"https://api.github.com/users/mteper/received_events","type":"User","site_admin":false},"created_at":"2016-01-25T19:57:20Z","updated_at":"2016-01-25T19:57:20Z","body":"So, I was thinking about interactions, and in particular about the long-winded declaration statement that we currently have (`Interaction<InteractionData<blah>>`) , and like Kent, was trying to categorize the moving pieces in my head to better frame the problem.\r\n\r\nFor reference, this is Kent's categorization lifted from the Slack RxUI channel:\r\n> 1. An abstract base class for all individual interactions (currently `InteractionData`). Has an `IsHandled` property.\r\n> 2. A class for individual interactions with a specific result type (currently `InteractionData<TResult>`. Extends 1 and adds `GetResult` and `SetResult` methods. Application developers are encouraged to subclass this class if they need to add extra data to interactions.\r\n> 3. A class that provides the coordination and distribution mechanism for interactions (currently `Interaction`). Has `RegisterHandler` methods (for views) as well as `Handle` method (for view models)\r\n\r\nI came up with a slightly different set:\r\n a. interaction “broker” -- this matches Kent's no. 3, the class that provides coordination\r\n b. request data -- information that the ViewModel sends along for the ride to interaction handler(s)\r\n c. result of the interaction -- something concrete that the ViewModel expects back from the interaction\r\n d. metadata about the handling of the interaction (e.g. was it handled? how? by whom? etc.)\r\n\r\nWhat we have today combines items (b)-(d) into one class, `InteractionData<TResult>`, where only the result is variable, and user is expected to subclass it if they need something different (e.g. sending an Exception along for the ride to handler(s) or getting more context than just `IsHandled`). It feels to me, however, that there's another way to dice this up:\r\n\r\n - `TInput`: the input to the interaction (for example, this could be a `string` or `Exception` or custom class)\r\n - `TResult`: the result coming back from the interaction (for example, a `FileInfo` from file picker). I was initially thinking that the interaction handling state would have to be a separate, third, generic type, but as I thought through actual scenarios, I concluded we don't need it to be separate. Anything the developer wants to send back, they can include in the result. The only thing the interaction needs them to say is whether the given handler has completed handling of this interaction. Which brings us to the last new component,\r\n - `InteractionResponse<TResult>`: the type that is returned from the interaction handlers. Now that we've separated out the interaction input, this type can be fixed and no longer needs subclassing.\r\n - `Interaction<TInput, TResult>`: this is the interaction itself. Maybe we can even throw in a couple of simpler concrete implementations,\r\n  - `InputInteraction<TInput>` that maps to `Interaction<TInput, object>` where we don't care about the result.\r\n  - `ResultInteraction<TResult>` that maps to `Interaction<object, TResult>` where we don't care about input to the interaction and just want the result back.\r\n\r\nI see the following benefits to this approach:\r\n - we no longer need to argue over naming of `InteractionData` :)\r\n - we have a more concise declaration for the interaction itself\r\n - we no longer require the user to subclass anything to implement interactions, ever\r\n - we are clear and explicit about what goes in and what comes out\r\n\r\nTo wrap it all up, here's a usage example:\r\n```cs\r\npublic class MyViewModel : ReactiveObject\r\n{\r\n  private string myFile = \"My.precious\";\r\n\r\n  public ReactiveCommand<Unit, Unit> UpdateFileContents;\r\n  public Interaction<string, FileInfo> PromptForMissingFile => new Interaction<String, FileInfo>();\r\n\r\n  public MyViewModel()\r\n  {\r\n    UpdateFileContents = ReactiveCommand.Create(async () =>\r\n        {\r\n          // we expect my file to always exist, but just in case, we sanity check and\r\n          // prompt the user if it's missing\r\n          if (!File.Exists(myFile))\r\n          {\r\n            var response = await PromptForMissingFile.Handle(\"Darn! There was supposed to be this file, but it's missing, please tell me where it went!\");\r\n            if (response.IsHandled) myFile = response.Result.FullName;\r\n            else { /* beats me, this is just an example! */ }\r\n          }\r\n\r\n          // go on with updating the file\r\n        });\r\n  }\r\n}\r\n\r\npublic class View\r\n{\r\n  // wherever you are doing view setup\r\n  {\r\n    ViewModel.PromptForMissingFile.RegisterHandler(input =>\r\n       {\r\n          // throw up a file selector with my input message and capture the result\r\n          var fileInfo = // ...\r\n\r\n          return new InteractionResponse(fileInfo, /* handled */ true);\r\n       }\r\n  }\r\n}\r\n```\r\n\r\nP.S. I am still partial to `interaction.Invoke()` over `interaction.Handle()`, but only a little bit. I understand the explanation Kent put forward and I can learn to live with it if I have to. :)"}}