我有一个片段,用于返回从REST API收到的文章列表。 REST API为async
,我使用await
等待它完成。完成后,我将数据(即List<Article>
)传递给名为listofarticles
的变量,并使用它创建ArrayAdapter
。
我在这个片段的OnCreate
方法中执行上述代码,据我所知,它应该在OnCreateView
之前运行。
我的代码适用于活动,但不适用于片段。
问题: 不等待对等待REST方法的调用。
逐行调试时,它是这样的:
OnCreate
被调用。处理行,我调用等待的方法(应该填充listofarticles
)并简单地跳到OnCreateView
。因此listofarticles
为空。
OnCreateView
被调用并处理。此时,程序只会回到它所调用的活动。
我无法再调试它,因为Xamarin Studio认为我已经完成但是在模拟器上它只是在所有这些之后开始调用REST方法,因此listofarticles
内的数据是无用的,因为我现在无法将它传递给适配器。
为什么会发生这种情况以及如何解决?
public class LatestNewsFragment : Android.Support.V4.App.Fragment, ListView.IOnItemClickListener
{
private Context globalContext = null;
private List<Article> listofarticles;
private ArrayAdapter<Article> listAdapter;
public LatestNewsFragment()
{
this.RetainInstance = true;
}
public async override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
globalContext = this.Context;
//Create a progress dialog for loading
ProgressDialog pr = new ProgressDialog(globalContext);
pr.SetMessage("Loading data");
pr.SetCancelable(false);
var rest = new RestAccess();
pr.Show();
//Download the data from the REST API
listofarticles = await rest.ListArticlesAsync ("SomeSource");
pr.Hide();
Console.WriteLine (listofarticles.Capacity);
listAdapter = new ArrayAdapter<Article>(globalContext, Android.Resource.Layout.SimpleListItem1, listofarticles);
}
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.Fragment_LatestNews, null);
ListView listView = view.FindViewById<ListView>(Resource.Id.list_latestnews);
listView.Adapter = listAdapter;
listView.OnItemClickListener = this;
return view;
}
}
答案 0 :(得分:7)
正如本回答https://stackoverflow.com/a/32656807/1230302中所解释的那样,在片段中执行OnCreate()相关视图并不是一个好主意。
此外,如果您将OnCreate()设置为异步方法,则将在await行之后立即调用其他片段生命周期事件(请参阅http://developer.android.com/guide/components/fragments.html)。现在的问题是你不能依赖await之后的代码运行的顺序,如果你的REST API很慢OnCreateView()可能已经完成了你会很好,但是如果API很快OnCreateView()可能仍然会运行你的应用程序将崩溃。
为什么不使用OnActivityCreated()来加载数据?您可以确定所有内容都已在该内容中初始化,例如:
private ListView listView;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.Fragment_LatestNews, null);
listView = view.FindViewById<ListView>(Resource.Id.list_latestnews);
listView.OnItemClickListener = this;
return view;
}
public override async void OnActivityCreated(Bundle savedInstanceState)
{
base.OnActivityCreated(savedInstanceState);
//Download the data from the REST API
var rest = new RestAccess();
var listofarticles = await rest.ListArticlesAsync("SomeSource");
listView.Adapter = new ArrayAdapter<Article>(Context, Android.Resource.Layout.SimpleListItem1, listofarticles);
}
答案 1 :(得分:1)
async并不代表&#34;等待并且不要运行任何其他代码。&#34;这意味着&#34;允许其他代码在此上下文中等待此调用时运行。&#34;每个异步块都是它自己的上下文,没有await将运行自己的路线。因此,在等待该调用完成或甚至启动时运行对其他方法的调用是很正常的。您不应该以这种方式依赖订单,而是要正确地同步您的代码。
最好在创建所有视图后获取数据,然后将其分配给适配器。这样一切都是可预测的,如果有必要,您甚至可以显示正确的加载指示器。
答案 2 :(得分:1)
此问题的解决方案是创建类型为View
的字段变量,以存储片段将返回的膨胀视图,并使用该视图在OnCreate
方法中获取布局文件。在await
之后,我们可以使用数据填充ListView
。所以最终的代码是:
public class LatestNewsFragment : Android.Support.V4.App.Fragment, ListView.IOnItemClickListener
{
private Context globalContext = null;
private List<Article> listofarticles;
View view;
public LatestNewsFragment()
{
this.RetainInstance = true;
}
public async override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
globalContext = this.Context;
//Create a progress dialog for loading
Android.App.ProgressDialog pr = new Android.App.ProgressDialog(globalContext);
pr.SetMessage("Loading data");
pr.SetCancelable(true);
var rest = new RestAccess();
pr.Show();
listofarticles = await rest.ListArticlesAsync ("SomeSource");
pr.Hide();
ArrayAdapter<Article> listAdapter = new ArrayAdapter<Article>(globalContext, Android.Resource.Layout.SimpleListItem1, listofarticles);
ListView listView = view.FindViewById<ListView>(Resource.Id.list_latestnews);
listView.Adapter = listAdapter;
listView.OnItemClickListener = this;
}
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
view = inflater.Inflate(Resource.Layout.Fragment_LatestNews, null);
return view;
}
}