Skip to content

Instantly share code, notes, and snippets.

@gshackles
Created November 4, 2012 16:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gshackles/f20d5f444234c78ca81a to your computer and use it in GitHub Desktop.
Save gshackles/f20d5f444234c78ca81a to your computer and use it in GitHub Desktop.
public class SubNavigationResultMessage<TResult> : TinyMessageBase
{
public TResult Result { get; private set; }
public string MessageId { get; set; }
public SubNavigationResultMessage(object sender, string messageId, TResult result)
: base(sender)
{
Result = result;
MessageId = messageId;
}
}
public abstract class SubViewModelBase<TResult> : ViewModelBase
{
protected string MessageId { get; private set; }
protected SubViewModelBase(string messageId)
{
MessageId = messageId;
}
protected void Cancel()
{
ReturnResult(default(TResult));
}
protected void ReturnResult(TResult result)
{
var message = new SubNavigationResultMessage<TResult>(this, MessageId, result);
MessengerHub.Publish(message);
Close();
}
}
public class SubViewModelTests : ViewModelTestsBase
{
[Test]
public void RequestSubNavigate_NavigatesToChildViewModel_PassesInMessageId()
{
var parentViewModel = new ParentViewModel();
parentViewModel.GoToChildViewCommand.Execute();
Assert.AreEqual(1, Dispatcher.NavigateRequests.Count);
var request = Dispatcher.NavigateRequests.First();
Assert.That(request.ViewModelType == typeof(ChildViewModel));
Assert.That(request.ParameterValues.ContainsKey("messageId"));
}
[Test]
public void RequestSubNavigate_ResultMessageReceived_ParentIsNotifiedAndUnsubscribes()
{
var parentViewModel = new ParentViewModel();
parentViewModel.GoToChildViewCommand.Execute();
var messageId = Dispatcher.NavigateRequests.First().ParameterValues["messageId"];
MessengerHub.Publish(new SubNavigationResultMessage<string>(this, messageId, "Result 1"));
Assert.AreEqual("Result 1", parentViewModel.LastResult);
MessengerHub.Publish(new SubNavigationResultMessage<string>(this, messageId, "Result 2"));
Assert.AreEqual("Result 1", parentViewModel.LastResult);
}
[Test]
public void ChildView_ReturnsResult_ParentIsNotifiedAndUnsubscribes()
{
var parentViewModel = new ParentViewModel();
parentViewModel.GoToChildViewCommand.Execute();
var messageId = Dispatcher.NavigateRequests.First().ParameterValues["messageId"];
var childViewModel = new ChildViewModel(messageId);
childViewModel.ReturnResult("Result 1");
Assert.AreEqual("Result 1", parentViewModel.LastResult);
var secondChildViewModel = new ChildViewModel(messageId);
secondChildViewModel.ReturnResult("Result 2");
Assert.AreEqual("Result 1", parentViewModel.LastResult);
}
public class ParentViewModel : ViewModelBase
{
public string LastResult { get; private set; }
public IMvxCommand GoToChildViewCommand
{
get { return new MvxRelayCommand(() => RequestSubNavigate<ChildViewModel, string>(null, onResult)); }
}
private void onResult(string result)
{
LastResult = result;
}
}
public class ChildViewModel : SubViewModelBase<string>
{
public ChildViewModel(string messageId)
: base(messageId)
{
}
public new void ReturnResult(string result)
{
base.ReturnResult(result);
}
public new void Cancel()
{
base.Cancel();
}
}
}
public abstract class ViewModelBase
: MvxViewModel,
IMvxServiceConsumer<ITinyMessengerHub>
{
protected ViewModelBase()
{
MessengerHub = this.GetService<ITinyMessengerHub>();
}
protected ITinyMessengerHub MessengerHub { get; private set; }
protected bool RequestSubNavigate<TViewModel, TResult>(IDictionary<string, string> parameterValues, Action<TResult> onResult)
where TViewModel : SubViewModelBase<TResult>
{
parameterValues = parameterValues ?? new Dictionary<string, string>();
if (parameterValues.ContainsKey("messageId"))
throw new ArgumentException("parameterValues cannot contain an item with the key 'messageId'");
string messageId = Guid.NewGuid().ToString();
parameterValues["messageId"] = messageId;
TinyMessageSubscriptionToken token = null;
token = MessengerHub.Subscribe<SubNavigationResultMessage<TResult>>(msg =>
{
if (token != null)
MessengerHub.Unsubscribe<SubNavigationResultMessage<TResult>>(token);
onResult(msg.Result);
},
msg => msg.MessageId == messageId);
return RequestNavigate<TViewModel>(parameterValues);
}
}
@slodge
Copy link

slodge commented Nov 4, 2012

There is one problem I can see with this pattern right now.

The problem is in Tombstoning - so is an advanced scenario.

To illustrate the scenario:

  1. Parent ViewModel starts SubView - this is created with a unique Id from the Guid
  2. For whatever reason, Android kills the parent activity (which is connected to the parent viewmodel)
  3. The user completes the SubView
  4. The reply message will be sent and the old ViewModel will receive that message...
  5. But the new parent view will have a new parent viewmodel :/

There's a similar error flow in WP7 (but slightly different - as WP7's tombstoning is slightly different)

I'm sure this can be worked around with some persistence... or it could be ignored for 99% of apps.


The reason I'm so aware of tombstoning is because I ran into a problem with StartActivityForResult on Android - basically, starting a Camera activity on one test phone always caused tombstoning - which meant that state and event listeners were lost. It all got very confusing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment