我正在使用Prism MVVM框架构建一个Xamarin.Forms应用程序。我有一个案例,我的视图模型可以接收将在视图模型中用于更新列表视图或其他UI元素的数据的完整对象,或者它将只接收我可以使用它们并检索完整的那些对象的ID来自Web服务的对象,但是,这是异步操作,并且在加载视图并且列表视图准备好接收数据时,可能尚未设置这些对象。我以这样的方式编写了一个viewmodel,我认为这将确保listview不会尝试刷新,直到设置了这些对象。我想过使用async void(fire和forget)方法来检索对象,但这意味着如果加载listview时,web请求可能仍在后台等待,然后刷新方法将尝试检索同样的对象再次导致重复请求。
//ViewModelBase implements INaviagtionAware and INotifyPropertyChanged
public class MainPageViewModel : ViewModelBase
{
private string _object1Id;
private string _object2Id;
private Task _getObjectsFromIdsTask;
private Object1 _object1;
private Object2 _object2;
private DelegateCommand _refreshListViewSourceCommand;
private bool _isIsRefreshing;
public Object1 Object1
{
get => _object1;
set => SetProperty(ref _object1, value);
}
public Object2 Object2
{
get => _object2;
set => SetProperty(ref _object2, value);
}
public DelegateCommand RefreshListViewSourceCommand => _refreshListViewSourceCommand ?? (_refreshListViewSourceCommand = new DelegateCommand(RefreshListViewSource, CanExecuteRefreshListViewSource).ObservesProperty(() => IsRefreshing));
public bool IsRefreshing
{
get => _isIsRefreshing;
set => SetProperty(ref _isIsRefreshing, value);
}
public MainPageViewModel ( INavigationService navigationService )
: base ( navigationService )
{
Title = "Main Page";
}
//Called when the page is first loadded and on each pull-to-refresh.
private async void RefreshListViewSource ()
{
IsRefreshing = true;
try
{
//If this is truen then objects where set from either another viewmodel or the task has completed succesfully before this method was called from the view.
if ( Object1 != null &&
Object2 != null )
{
//Populate the listview.
IsRefreshing = false;
return;
}
else if ( _getObjectsFromIdsTask != null )
{
switch ( _getObjectsFromIdsTask.Status )
{
case TaskStatus.Created :
case TaskStatus.WaitingForActivation :
case TaskStatus.WaitingToRun :
case TaskStatus.Running :
case TaskStatus.WaitingForChildrenToComplete :
//If any of the above then we wait for its completion.
await _getObjectsFromIdsTask;
break;
case TaskStatus.Faulted :
case TaskStatus.Canceled :
//If an error occurs then we restart the task.
_getObjectsFromIdsTask = GetObjectsFromIds ();
await _getObjectsFromIdsTask;
break;
case TaskStatus.RanToCompletion :
//If completed then do nothing.
break;
default :
throw new ArgumentOutOfRangeException ();
}
}
else
{
//If it is null then this method was called from the view before the task is set.
//This branch should never be reached, should I delete it.
_getObjectsFromIdsTask = GetObjectsFromIds ();
await _getObjectsFromIdsTask;
}
//If we get to here then the objects must be set
//Populate the listview.
}
catch ( Exception e )
{
Debug.WriteLine ( e );
}
IsRefreshing = false;
}
private bool CanExecuteRefreshListViewSource () => !IsRefreshing;
//Gets the objects from a web service by thier ids.
private async Task GetObjectsFromIds ()
{
//Uses _objectId1 and _objectId2 to get the objects. This just simulates delay.
Task < Object1 > object1 = Task.Delay ( 100 ).ContinueWith ( task => new Object1 () );
Task < Object2 > object2 = Task.Delay ( 100 ).ContinueWith ( task => new Object2 () );
Object1 = await object1;
Object2 = await object2;
}
//called when navigation to the page. I used OnNavigatingTo and not OnNavigatedTo to reduce the chance that the refresh method would be called befor the objects are set.
public override void OnNavigatingTo ( NavigationParameters parameters )
{
base.OnNavigatingTo ( parameters );
//If Ids are sent from another viewmodel then I have to get their objects from a web service. This would happen in case of deep linking from notification or a similar scenario.
if ( parameters.ContainsKey ( "Object1Id" ) &&
parameters.ContainsKey ( "Object2Id" ) )
{
_object1Id = parameters.GetValue < string > ( "Object1" );
_object2Id = parameters.GetValue < string > ( "Object2" );
_getObjectsFromIdsTask = GetObjectsFromIds ();
}
//If the full objects are sent, then I am setting the objects right here.
else if ( parameters.ContainsKey ( "Object1" ) &&
parameters.ContainsKey ( "Object2" ) )
{
Object1 = parameters.GetValue < Object1 > ( "Object1" );
Object2 = parameters.GetValue < Object2 > ( "Object2" );
}
}
}
我做错了吗?有更简单的方法吗?