Asked  7 Months ago    Answers:  5   Viewed   13 times

I am trying to unzip a folder using Android's AsyncTask. The class (called Decompress) is an inner class of Unzip where Unzip itself is a non-Activity class. The pseudo-code is:

public class Unzip {  
  private String index;  
  private String unzipDest;    //destination file for storing folder.
  private Activity activity;
  private boolean result;      //result of decompress.

  public void unzip(String loc) {

    Decompress workThread = new Decompress(loc, activity);
    workThread.execute();  
    if(unzip operation was successful) {
      display(index);
  }

  //Class Decompress:
class Decompress extends AsyncTask<Void, Integer, Boolean> {

        private ProgressDialog pd = null;
        private Context mContext;
                private String loc;
        private int nEntries;
        private int entriesUnzipped;

        public Decompress(String location, Context c) {
                        loc = location;
            mContext = c;
            nEntries = 0;
            entriesUnzipped = 0;
            Log.v(this.toString(), "Exiting decompress constructor.");
        }

        @Override
        protected void onPreExecute() {
            Log.v(this.toString(), "Inside onPreExecute.");
            pd = new ProgressDialog(mContext);
            pd.setTitle("Unzipping folder.");
            pd.setMessage("Unzip in progress.");
            pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            Log.v(this.toString(), "Showing dialog and exiting.");
            pd.show();
        }

               @Override
        protected Boolean doInBackground(Void... params) {
                       //unzip operation goes here.
                       unzipDest = something;  //unzip destination is set here.

                       if(unzip operation is successful) {
                          result = true;
                          index = url pointing to location of unzipped folder.
                       } else {
                         result = false;
                       }
                }

    @Override
        protected void onPostExecute(Boolean result) {
            if(result) {
                if(pd != null) {
                    pd.setTitle("Success");
                    pd.setMessage("folder is now ready for use.");
                    pd.show();
                    pd.dismiss();
                    pd = null;
                    Log.v(this.toString(), "Unzipped.");

                    index = unzipDest + "/someURL";
                    Log.v(this.toString(), "index present in: " + index);
                }
            } else {
                pd = ProgressDialog.show(mContext, "Failure", "Cannot unzip.");
                pd.dismiss();
            }
        }
    }   

Problems I am facing:
1. The value of unzipDest and index, updated in doInBackground, remain null to Unzip and all its objects. How can I ensure that the values remain updated?
2. I know that doInBackground occurs in a thread separate from the main UI thread. Does that mean that any values updated in the new thread will be lost once that thread returns?

 Answers

45

How can I ensure that the values remain updated?

They will be updated since they are member variables. However, since AsyncTask is asynchrounous, they might not be updated yet when you check them. You can use an interface to create a callback when these values are updated. This SO answer covers how to do this

Does that mean that any values updated in the new thread will be lost once that thread returns?

No they shouldn't be "lost". They probably just haven't been changed in the AsyncTask when you check them.

Since this isn't your actual code I can't see when you are trying to access them but you can use the interface method or call the functions that need these values in onPostExecute(). You also can do a null check before trying to access them. It just depends on the functionality and flow that you need as to which is the best way. Hope that helps.

Edit

In the answer I linked to, you tell the Activity that you will use that interface and override its method(s) with implements AsyncResponse in your Activity declaration after creating the separate interface class

public class MainActivity implements AsyncResponse{

then, in your Activity still, you override the method you declared in that class (void processFinish(String output);)

@Override
 void processFinish(String output){  // using same params as onPostExecute()
 //this you will received result fired from async class of onPostExecute(result) method.
   }

then this is called in onPostExecute() when the listener sees that it is done with delegate.processFinish(result); delegate is an instance of AsyncResponse (your interface class)

    public class AasyncTask extends AsyncTask{
public AsyncResponse delegate=null;

   @Override
   protected void onPostExecute(String result) {
      delegate.processFinish(result);
   }

Interface example taken from linked answer above and adjusted/commented for clarity. So be sure to upvote that answer if it helps anyone.

Tuesday, June 1, 2021
 
Tucker
answered 7 Months ago
90

Keep the following in mind:

  • I don't have the same environment as yours, so it might not work one on one.
  • Spaces in database fieldNames and arrayKeys, etc. are discouraged. I prefer to use lowerCamelCase, check if dB fieldNames match the code below!
  • Read the comments I've placed in the code.
  • I didn't take psr coding, value validation or safety (sql injection), etc. in consideration. The code is to guide you, you should take these things in consideration yourself.

Hopefully getting you closer to your goals...

Update: The values of the Evaluate field of each row is now validated to be an integer in the range from 1 to 5.

<?php

//Initialize variables
$result     = '';
$doUpdate   = isset($_POST['updateButton_x']);

//Because button type is 'image', we get parameters buttonName_x and buttonName_y
//from the browsers POST request when the form is sent.
if ($doUpdate) { 
    //Update button pressed.
    //Initialize variables
    $stmtSetParams = $stmtInParams = array();
    $validationOptions = array('options' => array('min_range' => 1, 'max_range' => 5));
    //Define statement and parameters (Assuming dB field 'item' is the primary key).
    $set = '`hole1` = CASE `item` ';
    foreach ($_POST['evaluate'] as $item => $hole1) {
        //Get input value of each table row
        if (filter_var($hole1, FILTER_VALIDATE_INT, $validationOptions) !== false) {
            //Field is not blank
            $set .= 'WHEN ? THEN ? ';
            $stmtSetParams[] = $stmtInParams[] = $item;
            $stmtSetParams[] = $hole1;
        } else {
            //Field is not an integer from 1 to 5
            $result .= "Field 'Evaluate' of item '$item' with a value of '$hole1' is not from 1 to 5 and skipped while saving!\n";
        }
    }
    $set .= 'END';
    //Define query placeholders
    $placeHolders = implode(', ', array_fill(0, count($stmtInParams), '?'));
    $query = <<<SQL
UPDATE `details` SET $set WHERE `item` IN ($placeHolders)
SQL;
}

//Query the dB.
try {
    $dbh = new PDO('mysql:host=localhost;dbname=gcmes', 'root');
    if ($doUpdate) {
        //Update requested. Prepare and execute update query
        $stmt = $dbh->prepare($query);
        $stmt->execute(array_merge($stmtSetParams, $stmtInParams));
        $result .= 'Update Completed!';
    }
    //Query for en fetch (updated) table data
    $stmt = $dbh->query("SELECT * FROM `details`");
    $tableData = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    //A PDO exception is raised
    $result = 'Error: ' . addslashes($e->getMessage());
}

//Alert results of database operations
if ($result != '') {
    echo "<script>alert('$result')</script>";
}
?>

<form id="form1" name="chooseFormNameIfNeeded" method="post" action="test.php">
    <!--  attribute align is deprecated, define and use a class instead -->
    <table class="info align-center">
        <tr>
            <!--  use th instead of td for table header -->
            <!--  using <b> tag is discouraged -->
            <th width="10"><b>No</b></th>
            <th width="30"><b>Category</b></th>
            <th width="50"><b>Job</b></th>
            <th width="40"><b>Evaluate</b></th>
        </tr>
        <?php
            foreach ($tableData as $rowData) {
                //Print a table row for each of the fetched records
                echo <<<HTML
<tr>
    <td>{$rowData['item']}</td>
    <td>{$rowData['category']}</td>
    <td>{$rowData['job']}</td>
    <td>
        <!-- Assuming dB field 'item' is the primary key. -->
        <input type="number" name="evaluate[{$rowData['item']}]" id="Evaluate" value="{$rowData['hole1']}"
               min=1 max=5
        >
    </td>
</tr>
HTML;
            }
        ?>
    </table>
    <!--  Attribute align is deprecated, define and use a class instead -->
    <!--  Value attribute should not be specified -->
    <input name="updateButton" type="image" id="Update_btn" src="http://via.placeholder.com/100x50/0000FF?text=Update" 
           alt="submit Button" class="align-right" 
           onmouseover="this.src='http://via.placeholder.com/100x50/00FF00?text=Update'"
           onmouseout="this.src='http://via.placeholder.com/100x50/0000FF?text=Update'"
    >
</form>
Saturday, May 29, 2021
 
fhonics
answered 7 Months ago
80

My intense tasks are loosely coupled and the execution order does not matter, by doing this way, a single thread is allocated to run a list of intense tasks.

AsyncTask presently uses a thread pool with several threads. In the future, it may be restricted to a single thread -- Google has hinted that this will be the case.

wonder if there is a workaround that I can achieve the same behavior under API level 11.

The default behavior is the behavior you want. If you examine the source code to AsyncTask, you will see that as of Gingerbread, it used a thread pool with a minimum of 5 threads and a maximum of 128.

Now, bear in mind that the vast majority of Android devices in use today are single-core. Hence, unless your "intense tasks" are intensely not doing much but blocking on network I/O, you do not want to be doing them in parallel, as context switches between threads will simply slow you down further.

Wednesday, June 9, 2021
 
van_folmert
answered 6 Months ago
29

It's only deprecated on Honeycomb and above, and it will work just fine on those too. The 'new' way is to use loaders (you can use the compatibility library to get those in pre-HC versions) or retained fragments. If you call setRetainInstance() the instance passed as is to the newly created activity (they actually use onRetainNonConfigurationInstance in the FragmentActivity of the compatibility library), so it's effectively the same as what you have now.

Tuesday, August 17, 2021
 
Steve Vinoski
answered 4 Months ago
28

This is the problem:

class MyList<T> implements Iterable<T> {
    private class MyListIterator<T> implements Iterator<T> {
        ...
    }
}

(It doesn't help that in your cut down version you've made MyList non-generic.)

At that point there are two different T type variables - the one in the nested class and the one in the outer class. You don't need Node to be generic - you just need:

class MyList<T> implements Iterable<T> {
    private class MyListIterator implements Iterator<T> {
        ...
    }
}

Now there's only one T - the one in the outer class. It's not like you want the list iterator to have a different T from the one declared in the enclosing instance, so you don't want it to be generic.

To put it another way: try making MyListIterator generic in a type parameter with a different name, and then it'll be clearer what's going wrong, as the two names will be distinguishable in the error message. It's effectively:

Type mismatch: cannot convert from another.main.MyList.Node<TOuter> to
another.main.MyList.Node<TInner>

(or vice versa).

Wednesday, September 29, 2021
 
huhushow
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share