Android - asynctask that fills an adapter for a listview - android

Basically the flow is like the following: when a fragment is created (or when the user swipes to refresh the layout), an AsyncTask is executed. The ASyncTask retrieves the info from a URL and setup an adapter which is then assigned to a ListView.
This is the code of the Fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.tasks_page, container, false);
mSwipeRefreshLayout = (SwipeRefreshLayout)
rootView.findViewById(R.id.list_container);
mSwipeRefreshLayout.setOnRefreshListener(this);
mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,
android.R.color.holo_green_light, android.R.color.holo_red_light);
lv = (ListView) rootView.findViewById(android.R.id.list);
getTasks();
return rootView;
}
private void getTasks() {
new TasksRetriever(mSwipeRefreshLayout,tasksAdapter,lv).execute();
}
#Override
public void onRefresh() {
mSwipeRefreshLayout.setRefreshing(true);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
getTasks();
}
}, 1000);
}
The TaskRetriever constructor is:
public TasksRetriever(SwipeRefreshLayout srl, TasksAdapter ta, ListView lv) {
adpt = ta;
this.srl = srl;
this.lv = lv;
}
and postExecute is:
#Override
protected void onPostExecute(List<TasksItem> result) {
super.onPostExecute(result);
dialog.dismiss();
if (adpt == null) adpt = new TasksAdapter(result);
else adpt.setItemList(result);
lv.setAdapter(adpt);
adpt.notifyDataSetChanged();
srl.setRefreshing(false);
}
Not sure yet if it works but I was wondering if I'm on the right track because it doesn't look clean to me. On the other hand, I can't create the adapter and assign it to the ListView until I actually have data for it... Is this considering OK or am I doing it wrong?

I would set the list adapter up in the onCreate() method under your list view and make this a instance variable (not a local one) so that the Async task can access it as follows i.e.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
lv = (ListView) rootView.findViewById(android.R.id.list);
mAdpt = new TasksAdapter(null);
lv.setAdapter(mAdpt);
}
And then in your onPostExecute()
#Override
protected void onPostExecute(List<TasksItem> result) {
dialog.dismiss();
mAdpt.clear();
for( TaskItem ti : result )
{
mAdpt.add( ti );
}
mAdpt.notifyDataSetChanged();
srl.setRefreshing(false);
}
Also make sure you account for the null value being passed into your adapter in the getView() method.
Hope this helps.

you CAN create an empty adapter and fill it later - in AsyncTask
your onPostExecute() should look like:
#Override
protected void onPostExecute(List<TasksItem> result) {
dialog.dismiss();
if (adpt == null){
adpt = new TasksAdapter(result);
lv.setAdapter(adpt);
}else{
adpt.clear();
}
for( TaskItem ti : result ) adpt.add( ti );
adpt.notifyDataSetChanged();
srl.setRefreshing(false);
}

Related

ListFragment sometimes skips code in onLoadFinished() method of Loader

I have a ListFragment which fetches data from the net using a Loader. I use a new instance of this ListFragment in every page of my ViewPager. It works perfectly, but when I use TabLayout or moves pages quickly, the Fragment keeps loading and does not display the data in the ListView.
When I checked using log messages, I found that the ListFragment skips some lines of code in the onLoadFinished() method. It does not make the ProgressBar invisible. It does add items to Adapter, but it is not being displayed in the ListView. This problem also happens in the first page of the ViewPager.
Is there any specific rule to be followed when using ListFragments in a ViewPager?
Here is the ListFragment class. If you look at the onLoadFinished() method, you can see the lines causing problem:
public class ListViewFragment extends ListFragment
implements LoaderManager.LoaderCallbacks<List<GameNews>> {
public static ListViewFragment newInstance(String url) {
Log.d("ListViewFragment", "newInstance created");
ListViewFragment f = new ListViewFragment();
// Supply url input as an argument.
Bundle args = new Bundle();
args.putString("url", url);
f.setArguments(args);
return f;
}
List<GameNews> TotalNews;
ListView gameListView;
LinearLayout emptyView;
Button retryButton;
ListAdapter adapter ;
private View progressBar;
final private int game_loader = 0;
ArrayList<String> urls = new ArrayList<>();
String mUrlString;
int index;
//LIFE CYCLE OF FRAGMENT
//------------------------------------------------------------------
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUrlString = getArguments().getString("url");
urls.add(mUrlString);
TotalNews = new ArrayList<GameNews>();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_list_view, container, false);
ArrayList<GameNews> gameList = new ArrayList<>();
adapter = new ListAdapter(getActivity(), gameList);
return rootView;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
emptyView = (LinearLayout)
view.findViewById(R.id.no_internet_view);
progressBar = view.findViewById(R.id.progress_bar);
retryButton = (Button) view.findViewById(R.id.retry_button);
gameListView = getListView();
emptyView.setVisibility(View.INVISIBLE);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(adapter);
//If connected to net start the loader
if (isConnected()) {
getActivity().getSupportLoaderManager().restartLoader(game_loader,
null,
ListViewFragment.this);
}
}
//OVERRIDED METHODS OF LOADERMANAGER
//---------------------------------------------------------------------
#Override
public android.support.v4.content.Loader<List<GameNews>> onCreateLoader(int i, Bundle bundle) {
AdManager manager = new AdManager(getActivity());
return new FragmentLoader(getActivity(), urls, 1000);
}
//HERE IS THE PROBLEM PLEASE FOCUS INSIDE THIS METHOD
//-------------------------------------------------------
#Override
public void onLoadFinished(Loader<List<GameNews>> loader, List<GameNews> games) {
progressBar.setVisibility(View.INVISIBLE); //This line of code is not executed
adapter.clear();
TotalNews.addAll(games);
adapter.addAll(games);//And the listView is not populated
}
//-------------------------------------------------------
#Override
public void onLoaderReset(Loader<List<GameNews>> loader) {
adapter.clear();
}
//REUSABLE METHODS
//------------------------------------------------------------------
//Method checks if there is internet
public boolean isConnected() {
ConnectivityManager manager = (ConnectivityManager)
getActivity().getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo info = manager.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
return true;
}
else {
return false;
}
}
}
Your Fragment class is using the Activity's LoaderManager:
getActivity().getSupportLoaderManager().restartLoader(...);
And each instance is using the same ID in its restartLoader() call:
final private int game_loader = 0;
This means that each Fragment instance was using and restarting the same Loader over and over again, leading to the weird behavior you observed.
The solution is quite simple: use Fragment's local LoaderManager, instead of the Activity's.
getLoaderManager().restartLoader(...);
With this, you don't need to worry about changing the ID in each instance, since Loaders are unique to their Fragment, and the Loader will be properly handled over the Fragment's lifetime, which would likely not have been the case when using the Activity's LoaderManager.

Updating listview on fragment

I have a ListView that has a SwipeRefreshLayout implemented on it. What I am trying to do is update the ListView when the user swipes down. I've tried looking for different options but unfortunately I am unable to find a solution.
Would it be better to reload the Fragment or the onCreate function?
Here is my code
public class NewsTab extends Fragment implements OnRefreshListener{
final LinkedList<News> listnews = new LinkedList<News>();
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.news_tab, container, false);
swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh_layout);
swipeRefreshLayout.setOnRefreshListener(this);
newslist = (ListView) view.findViewById(R.id.displaynews);
populatelist(listnews);
adapter = new CustomAdapter(getActivity(),listnews);
newslist.setAdapter(adapter);
return view;
}
public void onRefresh() {
swipeRefreshLayout.setRefreshing(true);
new StartNewsLoad().execute("");
}
private class StartNewsLoad extends AsyncTask<String, Void, String> {
//ProgressDialog pd = null;
protected String doInBackground(String... params) {
reloadNews();
loadTopNews();
//NewsTab.listnews = new LinkedList<News>();
return null;
}
protected void onPostExecute(String result) {
adapter = new CustomAdapter(getActivity(),listnews);
adapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
}
}
///ANSWER
I found the answer! I added the following line of code inside my costume adapter and called it outside as adapter.refresh();
public void refresh(LinkedList<News> newsList)
{
this.results = newsList;
notifyDataSetChanged();
}
Thank you everybody for your help!
First of all no need to create the adapter again in onPostExecute() as you have already initialized it in onCreateView().You simple need to notify the adapter of the changes in the "listnews" if it has changed.Make sure that your list actually changes before calling the notifyDataSetChanged().
Also please use debugger or log to check whether your onRefresh() is called or not.From the way i see, it should be overriden with #override annotation as it is the method of the interface implemented by your fragment(swipeRefreshLayout.setOnRefreshListener(this);).It should be like:
#Override
public void onRefresh() {
swipeRefreshLayout.setRefreshing(true);
new StartNewsLoad().execute("");
}
In onCreateView() intialize your listview and swiperefereshLayout with listener and set your adapter to your listview like that --->
if(adapter==null&& listNews==null){
listnews=new ArrayList<object>();
adapter=new CustomAdapter(getActivity(),listnews);
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();}
else{
listView.setAdapter(adapter);}
this way it also handle retaining the listData onOrientationchange.
and when you get api response or referesh listview with swipe layout,dont create new adapter just add listNews to your adapter and call adapter.notifyDataSetChanged();

Best way to fill a list with an adapter in a fragment

I developed an app which fills a list. It works fine in the way I did it but I'm not conviced that I solved the problem in a recommended way. I read that you should override onActivityCreated in a Fragment and fill the list there instead of doing this in onCreateView. onCreateView should only be used to inflate static views. Is this true? If yes, how should these two methods look like in the end?
This is my Fragment class:
public class FragmentMain extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
List<MyItem> items = createListItems();
ListView listView = (ListView) view.findViewById(R.id.list);
MyListAdapter adapter = new MyListAdapter(view.getContext(), items);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(view.getContext(),
"Clicked " + position, Toast.LENGTH_LONG)
.show();
}
});
return view;
}
.
.
.
}
My MainActivity just adds the fragment:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentMain fm = new FragmentMain();
getFragmentManager().beginTransaction()
.add(R.id.fragment_main_container, fm).commit();
}
.
.
.
}
That is true to a certain extend only because onCreateView happens on the UI thread and you don't want anything slowing that down otherwise your UI will be slow and choppy. For example, in your fragment class you have a call to a method "createListItems()". I don't know how many items you're making but if it's a lot it could slow down your UI (especially if youre accessing a database and querying objects and so on). So you could do it in onActivityCreated but you could also use an AsyncTask. So your code would become something like this:
public class LoadListObjectsTask extend AsyncTask<Void, List<MyItem>, Void> {
private MyListAdapter myListAdapter;
private Context mContext;
public LoadListObjectsTask(Context context) {
this.mContext = context;
}
#Override
public void doInBackground(Void...params) {
//create your list objects here instead of on UI thread. This will run on a separate thread.
myListAdapter = new MyListAdapter(mContext, items);
return items; //return list of MyItems
}
//This is called when doInBackground is done. THIS WILL RUN ON THE UI THREAD So don't do
//anything slow here
#Override
public void onPostExecute(List<MyItem>...params //don't really need the list here//) {
listView.setAdapter(myListAdapter);
}
}
then in your fragment
public class FragmentMain extends Fragment {
private ListView listView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
List<MyItem> items = new ArrayList<MyItem>();
listView = (ListView) view.findViewById(R.id.list);
//new code
new LoadListObjectsTask(getActivity()).execute();
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(view.getContext(),
"Clicked " + position, Toast.LENGTH_LONG)
.show();
}
});
return view;
}
public void onResume()... {
//also add the task here so your content is reloaded on resume..
new LoadListObjectsTask(getActivity()).execute();
}
.
.
.
}
If you don't want to do this just make your List of MyItems a private field and move
List<MyItem> items = createListItems();
to onActivityCreated().
Hope that helps!

ListView Not Updating After Calling notifyDataSetChanged, invalidateViews

I tried following the recommendations here (How to refresh Android listview?) to update my ListView, but to no avail.
The relevant code is below. Please note that I call runOnUiThread in my MainActivity to refresh the ListView of this (fragment).
protected ArrayAdapter<String> adapter;
protected View view;
protected ArrayList<String> theList;
protected ListView listView;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
view = inflater.inflate(R.layout.fragment_1, container, false);
listView = (ListView) view.findViewById(R.id.listview);
theList = new ArrayList<String>();
adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, theList);
listView.setAdapter(adapter);
if(view != null) { return view;}
((ViewGroup) listView.getParent()).removeView(listView);
container.addView(listView);
return view;
}
protected void updateListViewWithPath(String resultFromServerRead) {
//TODO: Somehow split the information..
theList.add("yo");
theList.add("hey");
adapter.clear();
adapter.addAll(theList);
listView.invalidateViews();
adapter.notifyDataSetChanged();
listView.refreshDrawableState();
}
You already have the reference to the arraylist list so you dont need to add it again to the listView just simply add the string to the arraylist and notifyDataSetChanged;
example:
protected void updateListViewWithPath(String resultFromServerRead) {
//TODO: Somehow split the information..
theList.add("yo");
theList.add("hey");
adapter.notifyDataSetChanged();
}

ListView and PullToRefresh swipe down conflict

I have used #chrisbanes ActionBar-PullToRefresh with my project, now I'm facing a conflict between ListView and PullToRefresh swipe down gesture.
When the ListView is already scrolled and I want to get to the top by swiping down, PullToRefresh trigger a refresh action instead of swiping the list up.
PS :
Here is my source code without extra stuff.
public class TestFragment extends Fragment implements OnRefreshListener {
private PullToRefreshLayout mPullToRefreshLayout;
private TestAdapter testAdapter;
private ListView testListView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.testActivity = (TestActivity)getActivity();
PullToRefreshLayout testLayout = (PullToRefreshLayout)inflater.inflate(R.layout.test_list_view, null);
this.testData = new ArrayList<Object>();
// Set List Adapter
this.testListView = new ListView(this.testActivity);
this.testData = getSearchResult();
this.testAdapter = new TestAdapter(this.testActivity, this.testData);
testListView.setAdapter(this.testAdapter);
testListView.setFocusableInTouchMode(false);
testLayout.addView(twitterListView);
return testLayout;
}
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ViewGroup viewGroup = (ViewGroup) view;
// As we're using a Fragment we create a PullToRefreshLayout manually
mPullToRefreshLayout = new PullToRefreshLayout(viewGroup.getContext());
// We can now setup the PullToRefreshLayout
ActionBarPullToRefresh.from(getActivity())
// We need to insert the PullToRefreshLayout into the Fragment's ViewGroup
.insertLayoutInto(viewGroup)
// all children are pullable
.allChildrenArePullable()
.listener(this)
.setup(mPullToRefreshLayout);
}
#Override
public void onRefreshStarted(View view) {
final TestAdapter adapter = this.testAdapter;
final TestActivity activity = this.testActivity;
final PullToRefreshLayout pull2Refresh = this.mPullToRefreshLayout;
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
/// refresh data source
getFragmentData(activity, true);
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
// Notify Program Adapter that data has changed
adapter.notifyDataSetChanged();
}
});
// Notify PullToRefreshLayout that the refresh has finished
pull2Refresh.setRefreshComplete();
}
}.execute();
}
}
I found the issue
It was my mistake, first i must load ListView from Layout by identifier
-- this.testListView = new ListView(this.testActivity);
++ this.testListView = (ListView)testLayout.findViewById(R.id.test_listview);
And remove the last line
-- testLayout.addView(twitterListView);

Categories

Resources