evanill80
evanill80

Reputation: 157

How to: Add child item to listview in onPreExecute, then change View of item added in onPostExecute

Summaray: Im using ListView to implement a chat. Each entry in the listview is a sent or received message which can either be text or an image. when an image is sent an asynctask object will...

  1. protected void onPreExecute() insert a an entry to the list view, it contains a thumbnail of the image being sent and a progress spinner.
  2. protected Void doInBackground(String... params) - will upload the image.
  3. protected void onPostExecute(Void param) - should set the progress spinner in the entry added in step 1 to invisible.

I have this working up to part 3. Im not sure how to modify the progress spinner of the ListView entry added in step 1. Keep in mind that after I add the entry to the ListView in step 1, many more entries (chat messages) will probably have been added to the Array storing the data used in the list. There are other threads that can add data to this Array.

When I execute add through the ArrayAdapter I do not know at what index it is added to. I do know it is at the end of the Array, so I thought about saving the index I get from executing getCount() but since there are muliple threads adding data to the Array I believe the add and getCount operations would have to be atomic, which they are not!

I could use some advice on how to proceed.

ArrayAdapter:

public class ChatArrayAdapter extends ArrayAdapter<ChatListEntry>{

    private static final String TAG = "Kingfisher";
    private TextView message;                                                   
    private List<ChatListEntry> messages = new ArrayList<ChatListEntry>();      
    private RelativeLayout wrapper;                                                                                         
    private LinearLayout wrapper_text_entry;                            

    static class ViewHolder { 
        public ImageButton btn_showFSImageActivity;
        ImageView imgCaptureThbnail;
    }

    public ChatArrayAdapter(Context context, int textview_resourceID) {
        super(context, textview_resourceID);
    }


    @Override
    public void add(ChatListEntry object) {
        messages.add(object);
        super.add(object);
    }

    public int getCount() {
        return this.messages.size();
    }

    public ChatListEntry getItem(int index) {
        return this.messages.get(index);
    }

    /**
     * Every new item that is added to the ListView gets a new View inflater
     * and loads specified layout. Even if the View/Layout is already loaded. 
     * ie the same type of message is being added to the chat listview.
     * This is easier to code, I dont have to check what the view/layout is
     * currently loaded, but this may not be the most efficient way to do it. 
     */
    public View getView(int position, View convertView, ViewGroup parent) {

        //test - id like to see every time getView is invoked. print to log.
        Log.i(TAG, "getview invoked.");


        View row = convertView;

        final ChatListEntry chatentry = getItem(position);

        LayoutInflater inflater = (LayoutInflater)          this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        if(chatentry.msgType.equals("IMG")) {
            row = inflater.inflate(R.layout.list_view_row_img, parent, false);


            if(chatentry.isOutbound == false) {
                chatentry.imgthmbnail = createThumbnailImg(getBitmapFromURL(chatentry.content));

                //test
                Log.i(TAG, "bitmap width is" +  chatentry.imgthmbnail.getWidth());

            }

            wrapper = (RelativeLayout) row.findViewById(R.id.wrapper);
            ViewHolder viewHolder = new ViewHolder();
            viewHolder.btn_showFSImageActivity = (ImageButton) row.findViewById(R.id.upload_img_thmbnail);
            viewHolder.imgCaptureThbnail = (ImageView) row.findViewById(R.id.upload_img_thmbnail); 
            viewHolder.imgCaptureThbnail.setImageBitmap(chatentry.imgthmbnail);

            row.setTag(viewHolder);

            //testing
            Log.i(TAG,"img received from srv. Here is associtated text content: " + chatentry.content);

            viewHolder.btn_showFSImageActivity.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    Log.i(TAG,"yay button clicked, here is chatentry.content:" + chatentry.content);
                    MainActivity main_act = (MainActivity) v.getContext();
                    main_act.onShowImgInActivity(chatentry.content);
                }
            });

            wrapper.setBackgroundResource(chatentry.isOutbound ? R.drawable.bubble_green : R.drawable.bubble_yellow);
            wrapper.setGravity(chatentry.isOutbound ? Gravity.RIGHT : Gravity.LEFT);

        } else if(chatentry.msgType.equals("TXT")) {
            row = inflater.inflate(R.layout.list_view_row, parent, false);

            wrapper_text_entry = (LinearLayout) row.findViewById(R.id.wrapper_listviewtextrow);
            message = (TextView) row.findViewById(R.id.textview_chatmsg);
            message.setText(chatentry.content);
            message.setBackgroundResource(chatentry.isOutbound ? R.drawable.bubble_green : R.drawable.bubble_yellow);
            wrapper_text_entry.setGravity(chatentry.isOutbound ? Gravity.RIGHT : Gravity.LEFT);

        } else
            Log.e(TAG, "Something has gone terribly wrong in ChatArrayAdapter-getView.");

        return row;
    }

}

AsyncTask class:

public class ServerTask_UploadContent extends AsyncTask<String, Integer , Void>  {
private String SERVERURL;
private final static String INPUT_IMG_FILENAME = "/imgcapture_temp.jpg";    

//Task state
private final int UPLOADING_PHOTO_STATE  = 0;
private final int SERVER_PROC_STATE  = 1;

private ProgressDialog dialog;
private String fname;                   /* file name for file to be uploaded */
private String uploadFilePath;          /* file name on android storage to be uploaded. */
private MainActivity app;
private static final String TAG = "Kingfisher";

private int lstvwentrnum = -1;              //index were captured image is being saved in listview.

public ServerTask_UploadContent(Context c) {
    super();
    app = (MainActivity) c;
    SERVERURL =  app.getString(R.string.srv_domain) + app.getString(R.string.srv_uploadfile);
}

//upload photo to server
private HttpURLConnection uploadPhoto(FileInputStream fileInputStream)
{
    fname = createTimeStamp() + ".jpg";

    final String lineEnd = "\r\n";
    final String twoHyphens = "--";
    final String boundary = "*****";

    try
    {
        URL url = new URL(SERVERURL);
        // Open a HTTP connection to the URL
        final HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        // Allow Inputs
        conn.setDoInput(true);              
        // Allow Outputs
        conn.setDoOutput(true);             
        // Don't use a cached copy.
        conn.setUseCaches(false);

        // Use a post method.
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Connection", "Keep-Alive");
        conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+boundary);

        DataOutputStream dos = new DataOutputStream( conn.getOutputStream() );

        dos.writeBytes(twoHyphens + boundary + lineEnd);

        if( uploadFilePath.compareTo((Environment.getExternalStorageDirectory().toString()  + INPUT_IMG_FILENAME)) == 0 )
            dos.writeBytes("Content-Disposition: form-data; name=\"uploadedfile\";filename=\"" + fname +"\"" + lineEnd);
        else
        {
            Log.e(TAG, "Something has gone wrong passing video to  HTTPImgUploader. " +
                    "path passed to class is: " + uploadFilePath + "\n" +
                    "path using getexternalstoragedircotry function is: " + 
                    (Environment.getExternalStorageDirectory().toString()));
            System.exit(-1);
        }

        dos.writeBytes(lineEnd);

        // create a buffer of maximum size
        int bytesAvailable = fileInputStream.available();
        int maxBufferSize = 1024;
        int bufferSize = Math.min(bytesAvailable, maxBufferSize);
        byte[] buffer = new byte[bufferSize];

        // read file and write it into form...
        int bytesRead = fileInputStream.read(buffer, 0, bufferSize);
        Log.d(TAG, "buffer: "  + buffer);

        while (bytesRead > 0)
        {
            dos.write(buffer, 0, bufferSize);
            bytesAvailable = fileInputStream.available();
            bufferSize = Math.min(bytesAvailable, maxBufferSize);
            bytesRead = fileInputStream.read(buffer, 0, bufferSize);
        }

        // send multipart form data after file data...
        dos.writeBytes(lineEnd);
        dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
        publishProgress(SERVER_PROC_STATE);

        // close streams
        fileInputStream.close();
        dos.flush();

        return conn;
    }
    catch (MalformedURLException ex){
        Log.e(TAG, "error: " + ex.getMessage(), ex);
        return null;
    }
    catch (IOException ioe){
        Log.e(TAG, "error: " + ioe.getMessage(), ioe);
        return null;
    }
}


protected void onPreExecute() 
{ 
    app.addImgToChat(fname);
}

@Override
protected Void doInBackground(String... params) 
{           
    uploadFilePath = params[0];
    processImage(uploadFilePath);

    return null;
}


@Override
protected void onPostExecute(Void param) 
{
    app.sendFileUploadSuccessMsg(fname);
}

Upvotes: 1

Views: 206

Answers (1)

SuppressWarnings
SuppressWarnings

Reputation: 4182

Why don't you assign an extra field to your ChatListEntry model providing unique id for every message?

If, as you say, the problem you're having is that this ArrayList can get entries from different processes, you could keep a static value in the model class which you could use as reference auto-incremental value from anywhere in the application. This way you could make sure every message has a unique identifier.

I would do something like:

public class ChatListEntry {

    private static int INCREMENTAL_ID = 0;

    // Fields:

    private int id;

    ...

    //----
    // Constructor:
    public ChatListEntry(id){
        this.id = id;
    }

    public static int getNextId(){
        return INCREMENTAL_ID++;
    }

}

Then construct them as follows:

ChatListEntry cle = new ChatListEntry(ChatListEntry.getNextId());

You could then easily propagate the item id back to the MainActivity method, which I assume handles item layout and invalidates the ListView...

With few modifications to your custom AsyncTask, you could even bridge it back (if you have to).

Something like:

    public class ServerTask_UploadContent extends AsyncTask<String, Integer , Void> {

        private final int messageId = ChatListEntry.getNextId();

        protected void onPreExecute() 
        { 
            ChatListEntry newCle = new ChatListEntry(messageId);
            newCle.setFname(fname);
            // You send copy of the object with unique Id:
            app.addImgToChat(newCle);
        }

        @Override
        protected Void doInBackground(String... params) 
        {           
            uploadFilePath = params[0];
            processImage(uploadFilePath);

            return null;
        }


        @Override
        protected void onPostExecute(String messageId) 
        {
            // You notify with messageId wich item to point to:
            app.sendFileUploadSuccessMsg(fname,messageId);
        }
    }

You could then find that particular item from within the ArrayList<ChatListEntry> at sendFileUploadSuccessMsg(), set the ProgressBar to invisible / replace it, and lastly call notifyDataSetChanged() your ChatArrayAdapter.

Upvotes: 0

Related Questions