Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
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 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