ListAdapter: Fill views async, outsource query - android

I am using a GridView and a CursorAdapter. All items in GridView have different types, so that my database query has many subqueries and is very slow.
So I decide to use a simple query (select only the ids).
All subqueries should be outsourced asynchronous in CursorAdapter like this (dummy code). And after the async database queries the GridView item is updated.
#Override
public void bindView(View view, Context context, Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndex(Columns._ID));
ViewLoader.queue(id, view);
}
private class ViewLoader {
public queue(long id, View view) {
Bitmap bitmap = ... // load bitmap from web by using id
String title = ... // query title from database by using id
String subtitle = ... // query subtitle from database by using id
TextView titleView = (TextView) view.findViewById(R.id.title);
titleView.setText(title);
...
// update GridView
}
What is the best approach?

I dont think that this is the best approach.
All of the thread swithing between UI and back thread will make your work unprodutable.
I had used a Cursor that queried over 4 tables with about 30-40 columns and it worked ok, i can even say pretty fast. My point is that i think that the issue is with your binding. Images can and should be loded async (i use Picasso, but a lot of libraries can do the trick). My guess is that u start with the query (try to limit it to 30 rows and then us pagination technique to load more) and work trough the binding and everything will workout perfectly. Ask freely if i didnt explain myself.

Related

Unable to delete pages when using FragmentStatePagerAdapter - What is the usual behavior of row ID's when integer primary key is used?

Please feel free to skip to the question as this background understanding may not be necessary for you.
I am new to android and sqlite and I am designing an app which has a content provider to access an sqlite database with multiple tables. There are several activities which use different adapters to display info from the database to the UI (i.e. cursor adapters, fragment state page adapter and array adapters). I have been having issues with the delete function in all of my activities which don't use the cursor adapter. When I try to update or delete a row from a table it deletes the wrong row or it doesn't delete anything at all. I believe it is a problem with the adapter where I am trying to figure out which row it is to send the correct info to the content provider.
The identical java code works perfectly with the cursor adapter and the rows delete normally and the other CRUD operations work. The insert and query functions work normally for all tables.The provider code uses a switch statement for each table but it is basically identical for each Uri case. All of the tables have _id as the integer primary key which is NOT set to auto increment. Since I don't fully understand how the row id works my java code does not reflect it and I keep having these issues. Although I have read many documents about content providers, adapters, sqlite databases, etc. certain key details are not clear to me.
My question is how does the row id get assigned numbers in the database when it is set to _id column as a primary key and what happens to those numbers when the database is changed?
For example, say I have an empty database. Initially after inserting the first row, the Uri will return a path segment for the 0 row and the adapter position would be 0... what would the row id for the database be (0 or 1) ?
Then for each row I insert, I know that row number would increase by one integer. Say I insert 4 rows - 0,1,2,3. Now when I am ready to delete - should the last path segment on the Uri be one integer less than the row number (i.e do I send a Uri with a last path segment of 2 to delete row 3)? Finally, after deleting, will the row ids then automatically get re-assigned so that row 4 now becomes row 3 ? Is there some code that I need to write to make that happen in the database? The primary keys are not set to auto increment.
I have different adapters and activities to where I can not access the actual database row ID once the data is displayed in the UI, so I am using the adapter position as a surrogate. This is why I am having trouble with update and delete.
Thank you very much if you read this entire question and take the time to answer it, it would help me tremendously.
I have an activity that is tabbed and uses FragmentStatePagerAdapter that is populated by a database. Here is the Adapter that I adjusted to keep track of the rows:
**EDITED:**
public class TankSectionsPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<Fragment> tankFragments = new ArrayList<>();
private ArrayList<String> tankTitles = new ArrayList<>();
//I added this ArrayList below to store the tankIDs to match the Fragments//
**public ArrayList<Integer> tankIDs = new ArrayList<>();**
public TankSectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return tankFragments.get(position);
}
#Override
public int getCount() {
return tankFragments.size();
}
#Override
public String getPageTitle(int position) {
return tankTitles.get(position);
}
public void addPage(Fragment fragment, String tankName) {
tankFragments.add(fragment);
tankTitles.add(tankName);
// I added this below so the ID position would match each fragment position //
**tankIDs.add(tankId);**
notifyDataSetChanged();
}
// Finally I added this method below to the adapter//
** public ArrayList<Integer> getPageId(){
return tankIDs;
}**
Here is the activity where the method is called and where it pulls the data from the cursor to pass to the Adapter. There is a loop where each row creates a page(tab) in the ViewPager:
public class MyClass extends Tank implements TabLayout.OnTabSelectedListener, LoaderManager.LoaderCallbacks<Cursor> {
public TankSectionsPagerAdapter tankSectionsPagerAdapter;
TabLayout tabLayout;
private ViewPager mViewPager;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_class);
mViewPager = (ViewPager) findViewById(R.id.container);
addPages(mViewPager);
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
tabLayout.setupWithViewPager(mViewPager);
tabLayout.addOnTabSelectedListener(this);
}
public void addPages(ViewPager mViewPager) {
tankSectionsPagerAdapter = new TankSectionsPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(tankSectionsPagerAdapter)
try {
...
Cursor cursor = getContentResolver().query(MyProvider.CONTENT_URI_TABLE_TANK_SETUP, MyDatabaseHelper.ALL_TABLE_TANK_SETUP_COLUMNS, tankDataFilter, null, null);
if (cursor.moveToFirst()) {
do {
tName = cursor.getString(cursor.getColumnIndex(MyDatabaseHelper.TANK_NAME)); ...
// all the variables are stored in the bundle passed to the fragment/
...
**tankSectionsPagerAdapter.addPage(MainTankFragment.newInstance(tankBundle),tName, int tankID);**
tankDataFilter = tankDataFilter + (-1);
}
while (cursor.moveToNext());
cursor.close();
} else {
Toast...
}
} catch (Exception e) {
Toast..
}
}
...
// Get Row ID from cursor(tankID), parameter in addPage() above//
//Get ID's from Adapter //
** ArrayList <Integer> pageID= tankSectionsPagerAdapter.getPageId();**
This is the Activity with Spinner to choose the rows/fragments to edit or delete.
public class EditTank extends Tank implements LoaderManager.LoaderCallbacks<Cursor>
...
// Get the ArrayList//
ArrayList<Integer> IDtags =getIDIntent.getIntegerArrayListExtra("tank_edit_key");
loadEditTankSpinnerData();
////***Here is the Spinner. Use row ID from the ArrayList******
Note: Don't use the id of the spinner
editTankSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view int position, long id) {
*** tankID = IDtags.get(position); ***
}
private void loadEditTankSpinnerData() {
List<String> tankNames = new ArrayList<String>();
Cursor cursor = getContentResolver().query(MyProvider.CONTENT_URI_TABLE_TANK_SETUP, MyDatabaseHelper.TANK_NAMES, null, null,null);
try{
if (cursor.moveToFirst()) {
do {
tankNames.add(cursor.getString(1));
} while (cursor.moveToNext());
cursor.close();
} else {
deleteTankBtn.setEnabled(false);
editTankBtn.setEnabled(false);
Toast...
}
} catch (Exception e) {
Toast...
}
...
}
The above code worked well with CursorAdapter but not with the fragmentStatePagerAdapter (***Prior to the edits it did not work, now it works well and deletes correctly).
I spent days(weeks) on this because I didn't understand why the rows weren't deleting. I hope this helps someone.
Word of advise - Try to write your question as simple as possible also the code. You shouldn't share too much code in here. People will just ignore.
You're using a CursorLoader but not using it properly. Use the LoadFinished method's cursor data.
Then you can directly pass the cursor to your FragmentPageAdapter and use it directly there.
Hope this helps.
Thanks to #pskink, #CL, and #albeee - this is what I learned from you guys and my own research.
In order to delete rows from database which is populating FragmentStatePagerAdapter or ArrayAdapter you have to be able to link the correct row with what is being displayed in the adapter. Otherwise the rows won't delete or will be inconsistent or incorrect. The CursorAdapter will automatically handle the watching for changes and selecting the ID for you. If you can use CursorAdapter or a direct onItemClickListener and get the id directly from the AdapterView with getSelectedId() or just long ID, then that is a good way to get the row ID. However, if you are getting the id indirectly by other means then you have to handle the associations...
1.You should not use the adapter position, spinner position or even spinner id to select the row. These are useful only to determine which item/fragment you want. Only the ID of the OnClickListener on the item itself will consistently give the correct row.
2.The database rows behave as such - the integer primary key will auto increment even if AUTOINCREMENT is not chosen, but the row ID's are stable once assigned. This means that each new row will have a higher ID than the last but if you delete rows in between, it will not change the IDs of the remaining rows. So the rows will skip and not be consecutive when there are edits and deletions. For this reason you must have a method to link the item to the ID permanently. There may be a better way to do this, however, I will edit the code above to show one way that I was able to do it for the FragmentStatePagerAdapter so the user can add and delete fragments dynamically.

Images from url flickering with custom cursor adapter

I am querying the SQLite database and putting the data into a List View. One of the database rows contains an image Url field (which can also be a Uri).
The images are loaded as they should but as soon as I scroll the list all the images start flickering, some are changing places or displaying in the different places.
I already understood that this behavior is happening because the List View is reusing rows on scroll, but I have no idea how to fix this behavior. Also I cannot use external libraries like Picasso in this project.
Here is my adapter code:
public class FilmsListCustomAdapter extends CursorAdapter {
private LayoutInflater cursorInflater;
public FilmsListCustomAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
cursorInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
TextView filmTitle = (TextView) view.findViewById(R.id.filmListTitle);
TextView filmScore = (TextView) view.findViewById(R.id.filmListScore);
ImageView filmImage = (ImageView)view.findViewById(R.id.filmListPoster);
ImageView filmSeen = (ImageView)view.findViewById(R.id.filmListSeen);
String title = cursor.getString( cursor.getColumnIndex("title") );
String score = cursor.getString(cursor.getColumnIndex("score"));
String url = cursor.getString( cursor.getColumnIndex("url") );
int seen = cursor.getInt( cursor.getColumnIndex("seen") );
if(Patterns.WEB_URL.matcher(url).matches()){
LoadImage loadImage = new LoadImage(context,filmImage);
loadImage.execute(url);
}
else{
Bitmap bmp = BitmapFactory.decodeFile(url);
CamImage camImage = new CamImage(context,Uri.parse(url));
Bitmap rotetedIm = camImage.rotateCamImage(bmp,url);
if(rotetedIm!=null){filmImage.setImageBitmap(Bitmap.createScaledBitmap(rotetedIm, 850, rotetedIm.getHeight(), false));}
else{filmImage.setImageResource(R.drawable.no_poster);}
}
GlobalMethods methods = new GlobalMethods(context);
filmTitle.setTypeface(methods.getWalkFont());
filmTitle.setText(title);
filmScore.setText(score);
if(seen==1){filmSeen.setImageResource(R.drawable.eye);}
else{filmSeen.setImageResource(0);}
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
return cursorInflater.inflate(R.layout.film_row, viewGroup, false);
}
}
What is probably happening is this:
You get a web URL for an image and queue an AsyncTask to download it for an ImageView
You scroll and the ImageView is recycled
This time the ImageView gets a local URL, so you do that immediately
The previously queued AsyncTask completes and loads a now unrelated image over the image you just put in
The key to clearing this up is to make sure to cancel the task once the ImageView is recycled. One way you can do this is to put a reference to the AsyncTask in a tag in the ImageView. When you get a recycled ImageView, you check the tag and see if there is a task in progress and cancel it before you start a new task.
You should check out this article on Android Developers Blog, it will explain a little more about the problem and how to fix it:
Multithreading For Performance | Android Developers Blog
I think this article was written back when AsyncTasks were changed to run in parallel threads, since they talk about tasks completing out of order. They've since reverted to serial execution so I don't think that part applies anymore, but the concept is similar since your immediate loading of the local image acts like a task executing out of order.
Two other things I would consider:
In getView, always call imageView.setImageBitmap(null) first to clear out any leftover image in the recycled ImageView. I like to init ImageViews witth a very neutral gray bitmap that represents an "Image Loading" state.
Use an AsyncTask to decode the local files as well as retrieve the web files. I'll bet your list scrolling will seem a lot smoother when you do this.

Android ListView image flickers while trying to load the image from SQLite Database

I implemented ListView to display some items from the SQLite Database. Each of which contains an Image and some Data.
I want my ListView to work faster even if it contains thousand rows of data. So i tried to implement some optimizations that i have noticed. Here is the basic structure of my CustomCursorAdapter:
Class CustomCursorAdapter extends CursorAdapter
{
Cursor cursor;
public CustomCursorAdapter(..., Cursor _cursor)
{
cursor = _cursor;
}
public void bindView(View _view, Context _context, Cursor _cursor)
{
if( view.getTage() == null)
{
//create and initialize a new holder and set it to view tag.
holder = new Holder();
...
...
holder.imageView = (ImageView) view.findViewById(R.id.image);
holder.imageView.setImageDrawable(defaultDrawable);
view.setTag(holder);
}
String mediaID = _cursor.getString("media_id");
//create an asyncTask to load the image from database
new MediaLoader(context, holder.imageView).execute(mediaID);
}
private class MediaLoader extends AsyncTask<String, Void, Media>
{
private Context context;
private final WeakReference <ImageView> imageViewReference;
public MediaLoader(Context _context, ImageView _imageView)
{
context = _context;
imageViewReference = new WeakReference<ImageView>(_imageView);
}
protected Media doInBackground(String... args)
{
String _mediaID = args[0];
return MediaDataManager.getInstance(_context).getMediaObjectForListAdapter(_mediaID);
}
protected void onPostExecute(final Media _media)
{
super.onPostExecute(_media);
if( imageViewReference != null )
{
ImageView _imageView = imageViewReference.get();
if(_imageView != null)
{
if( _media != null && _media.getImage() != null )
{
_imageView.setImageBitmap(_media.getImage());
}
else
{
_imageView.setImageDrawable(defaultDrawable);
}
}
}
}
}//Media Loader ends.
}//Custom Cursor Adapter Ends.
Using this approach loading time of image seemed ok to me.
But in some android devices (low configuration ones), i am experiencing image flickering. For some reason during scrolling or even loading i noticed images keeps changing throughout the list. But the final image that remains in every row is always the correct one.
Edit:
Loading of images one by one is not a problem for me. But showing some irrelevant images before showing the correct one is my only concern.
I couldn't find any helpful resource by searching. Any kind of help is very much appreciated.
Let's imagine that you have 100 rows in your Cursor. And, let's suppose that this is a really short ListView, where only 2 rows are visible. And, let's suppose that after you load up the adapter in the ListView, the user flings through the whole list.
What your code will do is:
Fork 100 AsyncTask instances, where on Android 3.2+ they will only execute one at a time (unless your targetSdkVersion is fairly low)
Download 100 images
Put each of those 100 images into the rows as they come in
All of this, for a case where you only need 2 images, the ones at the end.
This is why you really should consider using an existing library for this sort of thing, like Picasso, where you could plug in logic to pull values out of... well, wherever your images are actually stored. These sorts of libraries already handle these sorts of situations.
If you insist upon implementing this yourself, you will need to add in the smarts to realize that if the user scrolled the list and we are recycling a row, that we no longer need previous tasks that are tied to that row. Cancel those and queue up a task to download what you need. Also, consider using executeOnExecutor() on API Level 11+, so some of these will run in parallel.

Asynchronous binding using a SimpleCursorAdapter

I have a ListView that should display items that contain some text and an image.
The data is loaded from a local SQLite db file (that contains the text and image URLs).
I'd like to:
Get the text, url from DB.
Asynchronously download the image from the URL
Bind both values to the ListView (using the SimpleCursorAdapter).
So far, i was able to read the values from DB, however i am not sure how i can run the bind only AFTER i have successfully loaded each image?
In other words, i'd like to asynchronously bind each element as it's loaded to the appropriate UI item.
Here is a nice example which shows how to this http://www.androidkit.com/loading-images-from-remote-server-over-http-on-a-separate-thread.
Briefly,
1) you need to have a Map<Url, Bitmap>.
2) Have a default image that is displayed when image data from server is not available yet.
3) Have onScroll listener for your ListView, to know which items are currently displayed.
4) First, download those that are being displayed.
5) Once an image is downloaded, call notifyDataSetChanged() to bind available Image to the view.
6) You can use Softreferences or LRUCache to avoid OutofMemoryException
I have solved a similar problem to this. I received a XML from server and store the information in a Database. After that I populated the list using the CursorAdapter. In my case I have both images and text.
To solve the problem in the cursor adapter I did something like this:
#Override
public void bindView(View v, Context ctx, Cursor c) {
TextView title = (TextView) v.findViewById(R.id.titleID);
title.setText(c.getString(c.getColumnIndex(yourColumName)));
ImageView i = (ImageView) v.findViewById(R.id.ImageID);
String s = c.getString(c.getColumnIndex(youtImageColumn));
imageLoader.DisplayImage(s,i);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View v = inflater.inflate(R.layout.yourRowLayout, parent, false);
return v;
}
In this case the ImageLoader is an async lazy image loader gotten from here:
https://github.com/thest1/LazyList

Related Spinners

This application should have four or more related spinners which should reload when their 'parent' spinner' selection changes - as an example with 2 spinners: houses, and rooms - if you choose a house, the room spinner should reload from the sqlite database.
I have tried two approaches: a MySpinner class that takes a "child" Spinner in its constructor and tells the child to update itself when OnSelectedItem is triggered, like so
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (MySpinner.this.mChild.equals(null) == false) {
MySpinner.this.mChild.updateData((int)id);
}
}
the child's updateData is
public void updateData (int parentValue) {
new backgroundTask().execute("create");
}
which background tasks is an AsyncTask to query the sqlite database:
#Override
protected Void doInBackground(String... params) {
Db = new MyDatabase(mContext);
Db.open();
if(params[0] == "create") {
if (mTable.equals("T_room")){
mCursor = mDb.getRooms(mParentValue);
}
}
return null;
}
My second approach has been to create all my spinners directly in the activity.java file. This second approach has me implement one AsyncTask for all 4 or more spinners and choose what to query from the db, based on who calls with what value.
The first approach crashes on the only 'real' line of code in the asynctask, the second approach drives me mad with autosetting spinners and a jumble of ifs in the asynctask.
I'm not a coder by any means, and wonder if someone well versed in object-oriented coding can enlighten me as to what would be good coding behaviour to solve my specific problem (several spinners that update each other on selection.)
This is interesting, at this moment I'm doing something similar. Just keep a reference to the adapter, and inside onItemSelected access the object with adapter.getItem(pos). Then you can use this object to update the second spinner adapter. Just take care of UI threading. I would like to do this in a cleaner way but I don't know how to do it.

Categories

Resources