ListView and PullToRefresh swipe down conflict - android

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);

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();

Android - asynctask that fills an adapter for a listview

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);
}

Displaying a view when clicking a item in listview

I have this listview which is a list of news items. I want to display a detailed version of a particular news item when that news item is clicked in the listview.
So far i was able to create the listview and display the news items in a listview. (NOTE: news items are taken from a JSON)
I was able to display a view when a news item is clicked in the listview. but the PROBLEM is when a listview item is clicked, a view of detailed version of news items is displayed BUT it shows all the detailed version of news.
I WANT TO SHOW ONLY THE DETAILED VERSION OF THE NEWS ITEM THAT WAS CLICKED. how can i do this?
after researching i think i should use a bundle for this.but i have no idea how to do this.
I'll post my classes here
NewsFragment.java
public class NewsFramgment extends Fragment {
private ListView listView;
private ArrayList<BaseElement> News;
private LazyAdapter adapter;
private Activity activity;
private CommonVariable commonVariable;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.news_fragment, container,
false);
activity = this.getActivity();
commonVariable = (CommonVariable) activity.getApplication();
listView = (ListView) view.findViewById(R.id.list);
listView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v,
int position, long id)
{
android.support.v4.app.Fragment detail = new NewsDetailFragment();
android.support.v4.app.FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().add(R.id.content_frame, detail).addToBackStack("back").commit();
}
});
new BackGround().execute();
return view;
}
public class BackGround extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
News = JSONServices.getNewsDescription();
return null;
}
#Override
/* check again */
protected void onPostExecute(Void result) {
commonVariable.setNewsDescription(News);
adapter = new LazyAdapter(News, activity,Element.NEWS_LIST.getType());
listView.setAdapter(adapter);
super.onPostExecute(result);
}
#Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
}
}
}
NewsDetailFramgment.java
public class NewsDetailFragment extends Fragment {
private View view1;
private ArrayList<BaseElement> newsdetail;
private LazyAdapter adapter;
private Activity activity;
private CommonVariable commonVariable;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.newsdetail_fragment, container,
false);
activity = this.getActivity();
commonVariable = (CommonVariable) activity.getApplication();
view1 = (View) view.findViewById(R.id.list);
new BackGround().execute();
return view;
}
public class BackGround extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
newsdetail = JSONServices.getNewsDescription();
return null;
}
#Override
/* check again */
protected void onPostExecute(Void result) {
commonVariable.setTheater(newsdetail);
adapter = new LazyAdapter(newsdetail, activity,Element.NEWS_DETAIL.getType());
((AdapterView<ListAdapter>) view1).setAdapter(adapter);
super.onPostExecute(result);
}
#Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
}
}
}
It looks like you're calling newsdetail = JSONServices.getNewsDescription(); and then assigning that result to your adapter in NewsDetailFragment. What is being returned by JSONServices.getNewsDescription();? I imagine it is all of the NewsDetail articles.
#Raghunandan is right, the example shows how to handle two fragments in an activity and show the 'Detail' fragment based on which item was selected in the 'News' list.
You could implement the calls in your parent Activity as shown in the example under 'Implement the Interface'.
YourParentActivity.java
public static class YourParentActivity extends Activity {
implements NewsFragment.OnHeadlineSelectedListener{
...
}
public void onArticleSelected(int position) {
NewsDetailFramgment articleFrag = (NewsDetailFramgment)
getSupportFragmentManager().findFragmentById(R.id.news_detail_framgment);
if (articleFrag != null) {
// If article frag is available, we're in two-pane layout...
// Call a method in the NewsDetailFramgment to update its content
articleFrag.updateArticleView(position);
} else {
// Otherwise, we're in the one-pane layout and must swap frags...
// Create fragment and give it an argument for the selected article
NewsDetailFramgment newFragment = new NewsDetailFramgment ();
Bundle args = new Bundle();
args.putInt(NewsDetailFramgment.ARG_POSITION, position);
newFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack so the user can navigate back
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
}
}
}
Notice that onArticleSelected() accepts int position? This is the position in the News Headlines adapter that was clicked. You can use this to get the corresponding News Detail item from your Details result.
This is purely an outline based on the example, but should show you what you need to do in your parent activity.
Read the example and add the relevant parts to your fragments (look at 'Define an Interface' for changes to your NewsFragment.java).
Edit
You need to define an interface in your NewsFragment class as shown in the documentation under 'Define an Interface'.
// Container Activity must implement this interface
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
This provides a callback to your parent class to handle when a news headline is pressed with the position of the headline which was selected. In the example it calls onArticleSelected(int position); in your parent activity to then populate the NewsDetailFragment.

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!

Categories

Resources