Last active
February 26, 2016 13:49
-
-
Save softlion/d73a11abac5348f40569 to your computer and use it in GitHub Desktop.
ShowViewModelAsync and InvokeOnMainThreadAsync for MvvmCross. Also adds OnBack/OnBack2 methods on viewmodels.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class BaseViewModel : MvxViewModel, IBackViewModel | |
{ | |
#region InvokeOnMainThreadAsync | |
protected Task InvokeOnMainThreadAsync(Action action) | |
{ | |
var tcs = new TaskCompletionSource<bool>(); | |
if (Dispatcher != null) | |
{ | |
Dispatcher.RequestMainThreadAction(() => | |
{ | |
action(); | |
tcs.TrySetResult(true); | |
}); | |
} | |
else | |
tcs.TrySetCanceled(); | |
return tcs.Task; | |
} | |
protected Task<T> InvokeOnMainThreadAsync<T>(Func<T> action) | |
{ | |
var tcs = new TaskCompletionSource<T>(); | |
if (Dispatcher != null) | |
{ | |
Dispatcher.RequestMainThreadAction(() => | |
{ | |
tcs.TrySetResult(action()); | |
}); | |
} | |
else | |
tcs.TrySetCanceled(); | |
return tcs.Task; | |
} | |
#endregion | |
#region Infrastructure to handle return values from a viewmodel | |
protected class ReturnedResult<T> | |
{ | |
public T Result; | |
public Guid MessageId; | |
} | |
public const string ReturnedMessageIdKey = "returnedMessageId"; | |
private readonly ConcurrentDictionary<Guid, MvxSubscriptionToken> tokens = new ConcurrentDictionary<Guid, MvxSubscriptionToken>(); | |
/// <summary> | |
/// Store viewmodel parameters | |
/// </summary> | |
private IMvxBundle viewModelParameters; | |
protected void SetReturnResult<T>(T result) | |
{ | |
Guid messageId; | |
string messageIdstring; | |
if (viewModelParameters == null || viewModelParameters.Data == null || !viewModelParameters.Data.TryGetValue(ReturnedMessageIdKey, out messageIdstring) || !Guid.TryParse(messageIdstring, out messageId)) | |
throw new Exception(String.Format("Can not find {0} in the viewmodel's parameters {1}. Did you override InitFromBundle without calling base ?", ReturnedMessageIdKey, this.GetType().Name)); | |
SetReturnResult(messageId, result); | |
} | |
/// <summary> | |
/// For unit tests | |
/// </summary> | |
protected void SetReturnResult<T>(Guid messageId, T result) | |
{ | |
var messenger = Mvx.Resolve<IMvxMessenger>(); | |
messenger.Publish(new MvxMessage<ReturnedResult<T>>(this, new ReturnedResult<T> { Result = result, MessageId = messageId })); | |
} | |
protected void Close<T>(T result) | |
{ | |
if(result is IMvxViewModel) | |
base.Close((IMvxViewModel)result); | |
else | |
{ | |
SetReturnResult(result); | |
base.Close(this); | |
} | |
} | |
/// <summary> | |
/// TViewModel must call SetReturnResult in any method that closes the viewmodel. | |
/// This includes BackCommand (OnBack), BackCommand2 (OnBack2), this.Close() and calls to ShowViewModel which replaces the back stack. | |
/// | |
/// NOTE: if the app is tombstoned, the task is lost. | |
/// </summary> | |
protected Task<TResult> ShowViewModelAsync<TViewModel, TResult>(object parameterValuesObject = null, IMvxBundle presentationBundle=null) | |
where TViewModel: IMvxViewModel | |
{ | |
var tcs = new TaskCompletionSource<TResult>(); | |
var messenger = Mvx.Resolve<IMvxMessenger>(); | |
var messageId = Guid.NewGuid(); | |
var token = messenger.Subscribe<MvxMessage<ReturnedResult<TResult>>>(message => | |
{ | |
if (message.Data.MessageId != messageId) | |
return; | |
MvxSubscriptionToken token2; | |
if (tokens.TryRemove(messageId, out token2)) | |
token2.Dispose(); | |
else | |
Mvx.Error("Result message from {0} with same id received 2 times. Remove your multiple calls to ReturnResult in {0}.", typeof(TViewModel).Name); | |
tcs.TrySetResult(message.Data.Result); | |
}); | |
tokens.TryAdd(messageId, token); | |
var parameters = parameterValuesObject.ToSimplePropertyDictionary(); | |
if(parameters.ContainsKey(ReturnedMessageIdKey)) | |
throw new ArgumentException(String.Format("parameters can not include a {0} key when using a return value", ReturnedMessageIdKey), "parameterValuesObject"); | |
parameters.Add(ReturnedMessageIdKey, messageId.ToString()); | |
ShowViewModel<TViewModel>(parameters, presentationBundle); | |
return tcs.Task; | |
} | |
protected override void InitFromBundle(IMvxBundle parameters) | |
{ | |
viewModelParameters = parameters; | |
base.InitFromBundle(parameters); | |
} | |
#endregion | |
#region handle "back" events | |
/* | |
Handling of back events requires the use of a BaseView<TViewModel> class in android and iOS. | |
This BaseView class will correctly bind mvvm actions BackCommand and BackCommand2 | |
*/ | |
protected CancellationTokenSource BackCancel = new CancellationTokenSource(); | |
/// <summary> | |
/// A cancellable back command (Android, and some ios screens where back is replaced) | |
/// </summary> | |
public virtual ICommand BackCommand2 | |
{ | |
get | |
{ | |
return new MvxCommand(async () => | |
{ | |
Mvx.Trace("BackCommand2 detected on {0}", GetType().Name); | |
if (await OnBack2().ConfigureAwait(false)) | |
{ | |
BackCancel.Cancel(); | |
Close(this); | |
} | |
else | |
Mvx.Trace("BackCommand2 NOT cancelled on {0}", GetType().Name); | |
}); | |
} | |
} | |
/// <summary> | |
/// A non cancellable back command (ios back navigation button) | |
/// </summary> | |
public virtual ICommand BackCommand | |
{ | |
get | |
{ | |
return new MvxCommand(async () => | |
{ | |
Mvx.Trace("BackCommand detected on {0}", GetType().Name); | |
if (await OnBack().ConfigureAwait(false)) | |
{ | |
BackCancel.Cancel(); | |
} | |
else | |
Mvx.Trace("BackCommand NOT cancelled on {0}", GetType().Name); | |
}); | |
} | |
} | |
/// <summary> | |
/// A cancellable back command (Android, and some ios screens where back is replaced). Return false to prevent the back command. | |
/// </summary> | |
/// <returns></returns> | |
public virtual Task<bool> OnBack2() | |
{ | |
return Task.FromResult(true); | |
} | |
/// <summary> | |
/// A non cancellable back command (ios back navigation button). Return false to prevent the "BackCancel" cancellationtoken from being cancelled. | |
/// </summary> | |
/// <returns></returns> | |
public virtual Task<bool> OnBack() | |
{ | |
return Task.FromResult(true); | |
} | |
#endregion | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Preserve(AllMembers = true)] | |
[Activity(ScreenOrientation = ScreenOrientation.Portrait)] | |
public class CustomBaseActivity<T> : BaseActivity where T : class, IBackViewModel | |
{ | |
private CancellationTokenSource backPressedSource = new CancellationTokenSource(); | |
protected new T ViewModel => base.ViewModel as T; | |
protected CancellationToken BackPressed { get { return backPressedSource.Token; } } | |
public ICommand BackPressedCommand { get; set; } | |
protected override void OnCreate(Bundle bundle) | |
{ | |
base.OnCreate(bundle); | |
BindBackPressed(); | |
backPressedSource.Token.Register(() => | |
{ | |
var backPressedCommand = BackPressedCommand; | |
backPressedCommand?.Execute(null); | |
}); | |
} | |
public override bool OnOptionsItemSelected(IMenuItem item) | |
{ | |
switch (item.ItemId) | |
{ | |
//Action bar: home button will back | |
case Android.Resource.Id.Home: | |
OnBackPressed(); | |
return true; | |
} | |
return base.OnOptionsItemSelected(item); | |
} | |
public override void OnBackPressed() | |
{ | |
backPressedSource.Cancel(); | |
backPressedSource = new CancellationTokenSource(); | |
backPressedSource.Token.Register(() => | |
{ | |
var backPressedCommand = BackPressedCommand; | |
if (backPressedCommand != null) | |
backPressedCommand.Execute(null); | |
}); | |
} | |
/// <summary> | |
/// Required to put that here for correct linking in release mode | |
/// </summary> | |
protected void BindBackPressed() | |
{ | |
var set = this.CreateBindingSet<CustomBaseActivity<T>, IBackViewModel>(); | |
set.Bind(this).For(s => s.BackPressedCommand).To(vm => vm.BackCommand2); | |
set.Apply(); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CustomBaseView<T> : MvxViewController where T: MvxViewModel, IBackViewModel | |
{ | |
private readonly CancellationTokenSource backPressedSource = new CancellationTokenSource(); | |
protected CancellationToken BackPressed => backPressedSource.Token; | |
public ICommand BackPressedCommand { get; set; } | |
protected new T ViewModel | |
{ | |
get { return base.ViewModel as T; } | |
set { base.ViewModel = value; } | |
} | |
public override void ViewDidLoad() | |
{ | |
base.ViewDidLoad(); | |
backPressedSource.Token.Register(() => | |
{ | |
var backPressedCommand = BackPressedCommand; | |
backPressedCommand?.Execute(null); | |
}); | |
var set = this.CreateBindingSet<MvxSignupBaseView<T>, T>(); | |
set.Bind(this).For(t => t.BackPressedCommand).To(vm => vm.BackCommand); | |
set.Apply(); | |
} | |
/// <summary> | |
/// Not called if vc is popped up | |
/// </summary> | |
public override void WillMoveToParentViewController(UIViewController parent) | |
{ | |
if (parent == null) | |
{ | |
#if DEBUG | |
Mvx.Trace("{0}: back detected on WillMoveToParentViewController", GetType().Name); | |
#endif | |
backPressedSource.Cancel(); | |
} | |
base.WillMoveToParentViewController(parent); | |
} | |
public override void DidMoveToParentViewController(UIViewController parent) | |
{ | |
if (parent == null) | |
{ | |
#if DEBUG | |
Mvx.Trace("{0}: back detected on DidMoveToParentViewController", GetType().Name); | |
#endif | |
backPressedSource.Cancel(); | |
} | |
base.DidMoveToParentViewController(parent); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface IBackViewModel : IMvxViewModel | |
{ | |
ICommand BackCommand { get; } | |
ICommand BackCommand2 { get; } | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
anyway good job ! I like these extensions 👍