ListView not refreshing in a custom ViewGroup - android

I have a custom view group which allowed my screen to swip to the right when I call the menu button letting appear a second layout (my menu).
I have similars activities in my project without the menu and it works fine.
it's a simple view with a listview in it which displays data from webservices.
when I come on this activity, the content of my listView is not refreshed when data arrives. but when i press the menu button, the view swip and the content of my list view appears (without images, that appears when i scroll my listView).
here is the event rased when the menu button is pressed :
public void customScroll(final int i) {
new Thread(new Runnable() {
#Override
public void run() {
if (i == 0) {
for (int inc = 200; inc >= 0; inc -= 5) {
final int inc2 = inc;
runOnUiThread(new Runnable() {
#Override
public void run() {
//cvg is the implementation of my customViewGroup
cvg.scrollTo(inc2);
}
});
try {
synchronized (this) {
wait(5);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else {
for (int inc = 0; inc <= 200; inc += 5) {
final int inc2 = inc;
runOnUiThread(new Runnable() {
#Override
public void run() {
cvg.scrollTo(inc2);
}
});
try {
synchronized (this) {
wait(5);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
And that is the thread wich is run in "runOnUiThread" when my data are downloaded :
private Runnable returnRes = new Runnable() {
#Override
public void run() {
if (doubleVideos != null && doubleVideos.size() > 0) {
videoAdapter.notifyDataSetChanged();
for (int i = 0; i < doubleVideos.size(); i++) {
videoAdapter.add(doubleVideos.get(i));
}
}
videoAdapter.notifyDataSetChanged();
}
the behavior of my activity is quite strange, ask me if you don't understand.
i've tried already tried :
cvg.invalidate();
cvg.scrollTo(20);
//listView.notify();
listView.refreshDrawableState();
cvg.refreshDrawableState();
listView.bringToFront();
listView.invalidate();
cvg.invalidate();
listView.refreshDrawableState();
billion thanks for those who red this.

I have the same problem before.., I think you didn't implement ViewGroup in the proper way! In the custom ViewGroup call the measure method for each child before calling the layout method on the onLayout.

Related

How to use backbutton to unselect all item in gridview?

i'm using backbutton as interface from activity but it's not working properly for me because on backpress showing 0 size of arraylist
// here is the activity class from where i'm getting backbutton interface..
public class Multiple_Images extends AppCompatActivity {
#Override
public void onBackPressed() {
if(twice ==true){
Intent intent =new Intent(this,MainActivity.class);
startActivity(intent);
}ImageAdapter imageAdapter =new ImageAdapter(this);
imageAdapter.onBackPress();
Toast.makeText(this, "Press twice", Toast.LENGTH_SHORT).show();
twice =true;
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
twice =false; } }, 2000); }}
//here is the adapter class here i'm using backbutton
public class ImageAdapter extends BaseAdapter implements onBackPressListener {
ArrayList<String> selectedArraylist ;
#Override
public boolean onBackPress() {
selectedArraylist.clear();
Toast.makeText(context, "All values unselected", Toast.LENGTH_SHORT).show();
return true;
}
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
urimodel=new ArrayList<>();
final ImageView imageGrid ;
Activity activity = (Activity) context;
actionMode = activity.startActionMode(new Actionmode());
final GridModel gridModel=(GridModel) this.getItem(i);
if(view==null) {
view = LayoutInflater.from(context).inflate(R.layout.model, null);
selectedArraylist =new ArrayList<>();
}
final CardView cardView= (CardView)view.findViewById(R.id.cardview_image);
imageGrid = (ImageView) view.findViewById(R.id.grid_image);
// gridText = (TextView) view.findViewById(R.id.grid_text);
imageGrid.setScaleType(ImageView.ScaleType.CENTER_CROP);
// imageGrid.setScaleType(ImageView.ScaleType.CENTER_CROP);
Picasso.get().load(gridModel.getImage()).resize(200,200).into(imageGrid);
if (selectedArraylist.contains(gridModel.getImage_text())) {
cardView.setCardBackgroundColor(CARD_SELECTED_COLOR);
}else {
cardView.setCardBackgroundColor(Color.WHITE);
}
return view;
}
}
Simply you can do this inside onBackPressed
#Override
public void onBackPressed() {
if (twice == true) {
super.onBackPressed(); //this backs to the previous activity, if you want to stay with Intent, add finish() after startActivity()
return;
} else {
for (int i = 0; i < list.size(); i++) {
if (gridView.isItemChecked(i)) {
gridView.setItemChecked(i, false);
}
}
//selectedArraylist.clear(); this is clearing your array of selected items
}
twice = true;
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
twice = false;
}
}, 2000);
}
I don't know, why did you put selectedArraylist =new ArrayList<>(); in adapter getView() method. getView() is fired every time, when a new list item is inflated, that mean every time, when you are changing adapters source, scrolling list this method is called, and every time you are initialize you array, and all data inside lost. You should treat an adapter class just like a tool for displaying items, and all actions like above make outside adapter.
pretty much easy,
I give you my own project code, hope it help you.
StudentFragment.java:
private void MultiSelected_Student(int position) {
Student data = adapter_class.getItem(position);
if (data != null) {
if (selectedIds.contains(data)) selectedIds.remove(data);
else selectedIds.add(data);
}
}
private void Remove_MultiSelected() {
try {
selectedIds.clear();
} catch (Exception e) {
e.printStackTrace();
}
}
public void Group_UnSelect() {
Remove_MultiSelected();
MultiSelected = false;
fab.setVisibility(View.GONE);
homeeActivity.studentsMultiSelect = false;
notifyy();
}
private void notifyy() {
adapter_class.notifyDataSetChanged();
}
HomeActivity.java:
public boolean studentsMultiSelect = false;
#Override
public void onBackPressed() {
if (studentsMultiSelect) {
studentFragment.Group_UnSelect();
} else {
super.onBackPressed();
}
}

Android async task frame animation

To begin with I have tried a lot of ways to make a smooth animation in Android and probably my best option was to use AnimationDrawable. Everything was perfect until I got out of memory exception on older devices. The reason for that obviously is the number of frames, in my case 75. That is how I got to the point of using AsyncTask and Thread.sleep() to animate the frames. To avoid animation lag I used a Stack in which I preload the first 10 frames and then just pop the used one and push a new one until there are no more frames. Everything worked better than I expected, but the only problem is that at the end of the animation the last frame disappears and I am hitting my head whole day to understand why is that happening with no success obviously. Below is the code from the Activity in which I call the animation and the file where the animation code is.
SplashActivity.java
private void startAnimation() {
gifImageView = (LogoAnimImageView) findViewById(R.id.gifImageView);
gifImageView.setSplashActivityContext(this);
gifImageView.setBackgroundResource(R.drawable.logo_frame_0);
gifImageView.setAnimImageViewListener(new LogoAnimImageView.LogoAnimImageViewInterface() {
#Override
public void animationEnd() {
mAnimationFinished = true;
LoadNextActivity();
}
});
gifImageView.startLogoAnimation();
}
LogoAnimImageView.java
public class LogoAnimImageView extends ImageView {
public interface LogoAnimImageViewInterface {
void animationEnd();
}
final Handler mHandler = new Handler();
private Stack<Drawable> mImageStack;
private SplashActivity mSplashActivity;
private LogoAnimImageViewInterface mListener;
private int mFrameIndex;
private int[] mResources = {R.drawable.logo_frame_0,R.drawable.logo_frame_1,R.drawable.logo_frame_2,R.drawable.logo_frame_3,
R.drawable.logo_frame_4,R.drawable.logo_frame_5,R.drawable.logo_frame_6,
R.drawable.logo_frame_7,R.drawable.logo_frame_8,R.drawable.logo_frame_9,R.drawable.logo_frame_10,
R.drawable.logo_frame_11,R.drawable.logo_frame_12,R.drawable.logo_frame_13,R.drawable.logo_frame_14,
R.drawable.logo_frame_15,R.drawable.logo_frame_16,R.drawable.logo_frame_17,R.drawable.logo_frame_18,
R.drawable.logo_frame_19,R.drawable.logo_frame_20,R.drawable.logo_frame_21,R.drawable.logo_frame_22,
R.drawable.logo_frame_23,R.drawable.logo_frame_24,R.drawable.logo_frame_25,R.drawable.logo_frame_26,
R.drawable.logo_frame_27,R.drawable.logo_frame_28,R.drawable.logo_frame_29,R.drawable.logo_frame_30,
R.drawable.logo_frame_31,R.drawable.logo_frame_32,R.drawable.logo_frame_33,R.drawable.logo_frame_34,
R.drawable.logo_frame_35,R.drawable.logo_frame_36,R.drawable.logo_frame_37,R.drawable.logo_frame_38,
R.drawable.logo_frame_39,R.drawable.logo_frame_40,R.drawable.logo_frame_41,R.drawable.logo_frame_42,
R.drawable.logo_frame_43,R.drawable.logo_frame_44,R.drawable.logo_frame_45,R.drawable.logo_frame_46,
R.drawable.logo_frame_47,R.drawable.logo_frame_48,R.drawable.logo_frame_49,R.drawable.logo_frame_50,
R.drawable.logo_frame_51,R.drawable.logo_frame_52,R.drawable.logo_frame_53,R.drawable.logo_frame_54,
R.drawable.logo_frame_55,R.drawable.logo_frame_56,R.drawable.logo_frame_57,R.drawable.logo_frame_58,
R.drawable.logo_frame_59,R.drawable.logo_frame_60,R.drawable.logo_frame_61,R.drawable.logo_frame_62,
R.drawable.logo_frame_63,R.drawable.logo_frame_64,R.drawable.logo_frame_65,R.drawable.logo_frame_66,
R.drawable.logo_frame_67,R.drawable.logo_frame_68,R.drawable.logo_frame_69,R.drawable.logo_frame_70,
R.drawable.logo_frame_71,R.drawable.logo_frame_72,R.drawable.logo_frame_73,R.drawable.logo_frame_74
};
public LogoAnimImageView(Context context) {
super(context);
}
public LogoAnimImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LogoAnimImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void startLogoAnimation() {
mFrameIndex = 10;
mImageStack = new Stack<Drawable>();
for (int i=1;i<=mFrameIndex;i++) {
Drawable drawable = getDrawable(mResources[i]);
mImageStack.push(drawable);
}
mFrameIndex++;
mSplashActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
new LogoAnimOperation().execute((Object)null);
}
});
}
public void setSplashActivityContext(SplashActivity splashActivity) {
this.mSplashActivity = splashActivity;
}
public void setAnimImageViewListener(LogoAnimImageViewInterface listener) {
this.mListener = listener;
}
private Drawable getDrawable(int id) {
Drawable drawable;
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){
drawable = mSplashActivity.getDrawable(id);
} else {
drawable = mSplashActivity.getResources().getDrawable(id);
}
return drawable;
}
private class LogoAnimOperation extends AsyncTask<Object,Void,String> {
#Override
protected String doInBackground(Object... params) {
int number=1;
while (mImageStack.size() > 1) {
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
final Drawable drawable = mImageStack.pop();
mSplashActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
LogoAnimImageView.this.setBackground(drawable);
}
else {
LogoAnimImageView.this.setBackgroundDrawable(drawable);
}
if (mFrameIndex < mResources.length) {
Drawable newDrawable = getDrawable(mResources[mFrameIndex]);
mImageStack.push(newDrawable);
mFrameIndex++;
}
}
});
}
return "";
}
#Override
protected void onPostExecute(String s) {
mSplashActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
Drawable drawable = getDrawable(R.drawable.logo_frame_74);
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
LogoAnimImageView.this.setBackground(drawable);
}
else {
LogoAnimImageView.this.setBackgroundDrawable(drawable);
}
}
});
mListener.animationEnd();
super.onPostExecute(s);
}
}
}
...but the only problem is that at the end of the animation the last
frame disappears and I am hitting my head whole day to understand why
is that happening with no success obviously.
The problem may lie in your AsyncTask's onPostExecute(String):
#Override
protected void onPostExecute(String s) {
mSplashActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
Drawable drawable = getDrawable(R.drawable.logo_frame_74);
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
LogoAnimImageView.this.setBackground(drawable);
} else {
LogoAnimImageView.this.setBackgroundDrawable(drawable);
}
}
});
mListener.animationEnd();
super.onPostExecute(s);
}
onPostExecute(String) will always be called on the UI thread. So, mSplashActivity.runOnUiThread(....) is redundant.
By using runOnUiThread(Runnable), you are posting to the UI thread's event queue. So, the runnable is executed when its turn comes up. However, the code after the mSplashActivity.runOnUiThread(....) call may get executed before the runnable. So, mListener.animationEnd() may be getting called before your LogoAnimImageView has a chance to display R.drawable.logo_frame_74.
But, this should not happen in your case. If runOnUiThread(Runnable) is called from the UI thread (which, it is), the Runnable is not posted to the event queue, and executed immediately instead.
I suspect that the real issue here is that there isn't any delay between the last frame of your animation (R.drawable.logo_frame_74), and launch of next activity. Perhaps you could comment out the call to mListener.animationEnd(), to check whether the animation ends at the last or second-last frame.
Although this is an interesting approach, and one I haven't seen before, I have to say that you are meddling with more threads than you need to. If you're trying to load Drawables as and when they are needed, there is a simpler way:
public class LogoAnimImageView extends ImageView {
....
....
// flag to indicate whether `mNextFrameDrawable` should continue loading the next frame
private boolean mStopAnimating;
// loads the next frame, and calls back to activity when done
private Runnable mNextFrameRunnable = new Runnable() {
#Override
public void run() {
if (!mStopAnimating) {
if (isFinishedAnimating() && mListener != null) {
mListener.animationEnd();
} else { // Load next frame
setViewBg(getNextFrameDrawable());
// Will load the next frame in 40 ms
postDelayed(this, 40L);
}
}
}
};
// This method can be set `public static` and placed in a separate `Utils` class
private void setViewBg(Drawable d) {
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
setBackground(drawable);
} else {
setBackgroundDrawable(drawable);
}
}
private Boolean isFinishedAnimating() {
return mFrameIndex >= mResources.length;
}
// returns the next frame's drawable and increments the `mFrameIndex` pointer
private Drawable getNextFrameDrawable() {
return getDrawable(mResources[mFrameIndex++]);
}
// start animating
public void startLogoAnimation() {
mFrameIndex = 0;
mStopAnimating = false;
post(mNextFrameRunnable);
}
// stop animating
public void stopLogoAnimation() {
mStopAnimating = true;
removeCallbacks(mNextFrameRunnable);
}
....
....
}
AsyncTask is neither needed, nor designed to handle such scenarios.

RecyclerView animation when clearing whole adapter and adding new elements

So imagine that at first we load 10 items in our RecyclerView. Adding or removing one element gives us a nice animation (the adapter has stable ids).
The problem is, I have a search bar, I can look for something and then the result should replace the current items of the RecyclerView. If some item was already there, there is a nice "moving" animation. But if all items are new, there is a quite ugly fade-in transition that it's too fast and looks like a glitch. Is it possible to override that animation? I'd like to have a fade-out-fade-in one but slower.
By the way, when the query returns with results, I do this in the adapter:
mItems.clear();
mItems.addAll(resultItems);
notifyDataSetChanged();
Also, it's worth to say that if I make a search with no results, then I see the RecyclerView empty and then if I get some results again, the transition from empty state to some results looks ok.
You can batch remove and insert items in a RecyclerView.
adapter.notifyItemRangeRemoved(0, mItems.size());
mItems.clear();
mItems.addAll(resultItems);
adapter.notifyItemRangeInserted(0, mItems.size());
EDIT: After looking at your problem some more you probably don't want to do what I suggested above. Instead you should not clear your list and instead remove some items and then notify the adapter of the change with notifyItemRemove(index)
If you do range methods like RangeRemoved/RangeAdded, you loose out in the animation side. So, do it one by one in a loop to preserve the animation effect of one by one, including a delay in the loop. Here's how I have implemented:
MainActivity.java
clearItemsView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final List<LineItem> lineItemsCopy = new ArrayList<>(lineItems);
new Thread(new Runnable() {
#Override
public void run() {
for (int i=0; i<lineItemsCopy.size(); i++) {
runOnUiThread(new Runnable() {
#Override
public void run() {
salesOrderItemListAdapter.removeItem(0);
}
});
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
Snackbar snackbar = Snackbar.make(coordinatorLayout, getString(R.string.items_cleared_message), Snackbar.LENGTH_LONG)
.setAction(getString(R.string.label_undo), new View.OnClickListener() {
#Override
public void onClick(View v) {
new Thread(new Runnable() {
#Override
public void run() {
for (int i=0; i<lineItemsCopy.size(); i++) {
final int finalI = i;
runOnUiThread(new Runnable() {
#Override
public void run() {
salesOrderItemListAdapter.restoreItem(lineItemsCopy.get(finalI), 0);
}
});
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}).setActionTextColor(Color.YELLOW);
snackbar.show();
}
});
RecyclerViewAdapter.java
//Only remove & restore functions are shown
public void removeItem(int position) {
lineItems.remove(position);
notifyItemRemoved(position);
}
public void restoreItem(LineItem item, int position) {
lineItems.add(position, item);
notifyItemInserted(position);
}

java.lang.IllegalStateException:Make sure the content of your adapter is not modified from a background thread, but only from the UI thread

java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(2131296513, class xyz.ScrollDetectableListView) with Adapter(class android.widget.HeaderViewListAdapter)]
I am getting above exception sometimes while scrolling through the dynamic listview and then clicking on item.I researched a lot but unable to find the exact reason that why i am getting this error sometimes and how it can be resolved?
private ScrollDetectableListView mFListView;
public FAdapter mFAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_feed_view, container, false);
View headerView = getActivity().getLayoutInflater().inflate(R.layout.view_feed_header, null);
try{
mFListView = (ScrollDetectableListView) rootView.findViewById(R.id.feed_list_view);
mFContainer = (SwipeRefreshLayout) rootView.findViewById(R.id.feed_container);
mFListView.addHeaderView(headerView);
mFListView.setEmptyView(rootView.findViewById(R.id.empty_view));
mFContainer.setColorSchemeResources(R.color.green, R.color.pink, R.color.fbcolor,
R.color.instagramcolor, R.color.googlecolor, R.color.flickrcolor);
mFView = getActivity().getLayoutInflater().inflate(R.layout.view_footer, null);
ImageView rotateImageView = (ImageView) mFooterView.findViewById(R.id.spinner);
Animation rotation = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate);
rotation.setFillAfter(false);
rotateImageView.startAnimation(rotation);
mFContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh()
{
initializeFListView();
}
});
initializeFListView();
mProgressDialog.setVisibility(View.VISIBLE);
mHActivity.setDataChangedListener(new DataChangedListener() {
#Override
public void onDataChanged() {
mFContainer.setRefreshing(true);
mProgressDialog.setVisibility(View.GONE);
initializeFListView();
}
});
}catch(Exception e){}
return rootView;
}
public void initializeFListView()
{
FApi.getTrending(getActivity(), xyz, new APIResponseListener() {
#Override
public void onResponse(Object response) {
setFeedAdapter((List<Video>) response);
}
#Override
public void onError(VolleyError error) {
if (error instanceof NoConnectionError) {
String errormsg = getResources().getString(R.string.no_internet_error_msg);
Toast.makeText(getActivity(), errormsg, Toast.LENGTH_LONG).show();
}
}
});
}
private void setFAdapter(List<Video> response)
{try {
List<Video> videos = response;
mFAdapter = new FAdapter(getActivity(), videos, mProfileClickListener, mCommentClickListener);
mFListView.setOnScrollListener(new EndlessScrollListenerFeedView(getActivity(), mFListView, mFView, mFAdapter, videos, mFType, ""));
mFListView.setAdapter(mFAdapter);
mProgressDialog.setVisibility(View.GONE);
if (mFContainer.isRefreshing()) {
mFContainer.setRefreshing(false);
}
if (mFAdapter.getCount() < mCount) {
mFView.setVisibility(View.GONE);
mFListView.removeFooterView(mFooterView);
}
}catch(Exception e){}
}
}
My suggestion try to set ur list adapter on UI Thread,,,
private void setFAdapter(List<Video> response)
{
try {
List<Video> videos = response;
mFAdapter = new FAdapter(getActivity(), videos, mProfileClickListener, mCommentClickListener);
mFListView.setOnScrollListener(new EndlessScrollListenerFeedView(getActivity(), mFListView, mFView, mFAdapter, videos, mFType, ""));
runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
mFListView.setAdapter(mFAdapter);
}
});
mProgressDialog.setVisibility(View.GONE);
if (mFContainer.isRefreshing()) {
mFContainer.setRefreshing(false);
}
if (mFAdapter.getCount() < mCount) {
mFView.setVisibility(View.GONE);
mFListView.removeFooterView(mFooterView);
}
}catch(Exception e){}
}
}
Keep one singleton class object in hand. So that you can synchronize two thread on it. Care to be taken to not to block the ui thread.
Reduce number of interfaces to only one method to start preparing data for your list and only one method to call your notifydatasetchanged/setAdapter on list.
Means there should be only one method like prepareData() which will be executed by a background thread. synchronise this method on your singleton object.
MyListAdaper adapter = null;
// Call this from a background thread
public void prepareData() {
synchronized (SingleTonProvider.getInstance()) {
List<AnyDataTypeYouWant> data = null;
// populate data here by your application logic.
adapter = new MyListAdaper(data);
}
}
And have only one method to refresh list.
// Also Call this from a background thread only
public void refreshList() {
synchronized (SingleTonProvider.getInstance()) {
runOnUiThread(new Runnable() {
#Override
public void run() {
mFListView.setAdapter(adapter);
}
});
}
}
have no other code on any place to prepare data and set data on list.
Call the methods I mentioned from a background thread only.
I just gave general solution to your problem. You have to work on your specific case by yourself.

Appending ListView from onScrollListener

I am trying to update my listview to make an "endless scroll". What happens is that the first 40 results load fine, when i get to the bottom of the scroll, next 40 results replace the first 40...
What I want is for second set of 40 results to add to the first 40 so I have an endless list and ability to scroll back to the beginning of the list.
I am posting my code below. Thank you!
public class SearchResults extends Activity implements BannerAdListener, OnScrollListener{
private LinearLayout bottomNav;
private ListView ringtoneList;
private int start = 0, num = 40, curPage = 1;
private Handler handler = new Handler();
private ProgressDialog progressDialog = null;
private ArrayList<Ringtone> ringtones;
private MoPubView moPubView;
private String searchString;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle extras = getIntent().getExtras();
if (extras == null) {
// no search string defined
finish();
} else {
searchString = extras.getString("search_string");
}
setContentView(R.layout.search_results);
ringtoneList = (ListView)findViewById(R.id.ringtone_list);
ringtoneList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> av, View view, int position, long id) {
Intent i = new Intent(SearchResults.this, RingtoneView.class);
i.putExtra("ringtone", ringtones.get(position));
startActivity(i);
}
});
performSearch();
moPubView = (MoPubView) findViewById(R.id.adview);
moPubView.setAdUnitId(Utils.MoPubBannerId);
moPubView.loadAd();
moPubView.setBannerAdListener(this);
ringtoneList.setOnScrollListener(this);
}
public void onScroll(AbsListView view, int firstVisible, final int visibleCount, int totalCount) {
Log.i("List", "firstVisible="+firstVisible+" visibleCount="+visibleCount+" totalCount="+totalCount);
boolean loadMore = firstVisible + visibleCount >= totalCount;
if(loadMore) {
Log.i("List", "Loading More Results");
curPage++;
start = num * (curPage-1);
new Thread() {
public void run() {
ringtones = Utils.search(start, num, searchString);
if (ringtones != null && ringtones.size() > 0) {
handler.post(new Runnable() {
public void run() {
ringtoneList.setAdapter(new RingtoneRowAdapter(SearchResults.this, ringtones));
}
});
} else {
handler.post(new Runnable() {
public void run() {
new AlertDialog.Builder(SearchResults.this)
.setTitle(getResources().getString(R.string.context_info)).setMessage(getResources().getString(R.string.context_noresult))
.setPositiveButton(getResources().getString(R.string.context_ok), null).show();
}
});
}
}
}
.start();
}
}
public void onScrollStateChanged(AbsListView v, int s) { }
private void performSearch() {
progressDialog = ProgressDialog.show(SearchResults.this, getResources().getString(R.string.loading_message), getResources().getString(R.string.loading_search), true);
new Thread() {
public void run() {
ringtones = Utils.search(start, num, searchString);
if (ringtones != null && ringtones.size() > 0) {
updateList();
} else {
handler.post(new Runnable() {
public void run() {
new AlertDialog.Builder(SearchResults.this)
.setTitle(getResources().getString(R.string.context_info)).setMessage(getResources().getString(R.string.context_noresult))
.setPositiveButton(getResources().getString(R.string.context_ok), null).show();
ringtoneList.setVisibility(View.INVISIBLE);
bottomNav.setVisibility(View.INVISIBLE);
}
});
}
progressDialog.dismiss();
}
}.start();
}
private void updateList() {
handler.post(new Runnable() {
public void run() {
//Log.d("search", "ringtones.size() " + ringtones.size());
ringtoneList.setVisibility(View.VISIBLE);
ringtoneList.setAdapter(new RingtoneRowAdapter(SearchResults.this, ringtones));
}
});
}
}
Please help! Thank you!
While I can't be 100% sure, I think your problem has to do with the fact that you're setting a new adapter that only has the section of ringtones that in loads. It probably has to do with this snippet:
ringtones = Utils.search(start, num, searchString);
if (ringtones != null && ringtones.size() > 0) {
handler.post(new Runnable() {
public void run() {
ringtoneList.setAdapter(new RingtoneRowAdapter(SearchResults.this, ringtones));
}
});
}
Instead of putting an entirely new array of ringtones, you should add on to the one you already have. Your ringtones variable is already an instance variable, so I'm sure if you changed this line:
ringtones = Utils.search(start, num, searchString);
to the following:
ringtones.addAll(Utils.search(start, num, searchString));
It might fix your problem.
Your code is a little bit messy, but your updateList method is creating a NEW RingtoneRowAdapter. You should ADD items to the list and call
mRingtoneRowAdapter.notifyDatasetChanged();
That will tell the adapter to get new views (if needed) along with a lot of internal stuff happening at Adapter's level. So it's kinda:
private void updateList() {
handler.post(new Runnable() {
public void run() {
if ( mAdapter == null ) {
mAdapter = new RingtoneRowAdapter(SearchResults.this, ringtones);
ringtoneList.setAdapter(mAdapter);
} else {
mAdapter.notifyDataSetChanged();
}
ringtoneList.setVisibility(View.VISIBLE);
}
});
}
And of course, as already suggested, don't init your ringtones array all the time. Just add data to it.
You don't need (most of the times) to hide the list, you can add in your layout, any View (including a full Layout!) with an id of:
android:id="#android:id/empty"
And if your ListView has an id of android:id="#id/android:list", and you're in a ListFragment or ListActivity, then when the list is empty, the "empty" layout will be shown (which can be a dummy view if you don't want to see it).
Just suggestions :)
UPDATE: For your null, I see that the logic is a little bit weird and since we don't know the requirements for your app (i.e. what to do if there are no results, what to do if the results are invalid, etc.) I'll assume you just want to make sure it works and deal with that later.
So with that in mind, I see two problems.
I assume your Utils.search method can return null, because you're checking for it. To me that feels strange, I'd rather return an empty array indicating that the search produced no results; that little remark aside, you are not checking (or we don't know because we haven't seen the source for your search method), if the searchString is null or not.
You haven't provided a stack trace, so we can't tell where the null is happening (either inside search or ?)
A quick solution would be to check for null before searching… I would personally do this INSIDE the search function.
Something like:
public ArrayList<Ringtone> search(final int start, final int num, final String searchString) {
if ( searchString == null ) {
//DECIDE WHAT YOU WANT TO DO, EITHER:
return null;
// OR YOU CAN RETURN AN EMPTY ARRAY
return new ArrayList<Ringtone>();
}
// You should check for these (change according to your rules)
if (start < 0 ) {
start = 0; // protect yourself from bad data.
}
if ( num < 0 ) {
num = 0;
}
/// THE REST OF YOUR search FUNCTION
return <your array>
}
Now another thing is that you may want the search to return incremental results (so you can ADD them to your array (instead of returning a new array every time). For that, as already suggested, use the addAll trick, but then, DON'T return null, return an new empty array, so there's no harm done if there's nothing else to add.

Categories

Resources