How to disable Button while AsyncTask is running? (Android) - android

I am trying to disable a button while a download task is being executed. I have tried using setEnabled, setVisibility and setClickable. I think I tried all combinations of these options. All of them disable the button click events while the task is performing, but the events are still being registered somehow, and when I reactive the button, the handler is called if I clicked the button while it was disabled... even if it was invisible or "gone"! (not sure if it is called a handler, I want to refer to the onClick method).
I have also inserted a counter and a Log to verify what I've stated above. The code is shown below. This piece of code if(counter>1) return; is meant to stop the crash, but I would like to remove it, since I want to re-enable the button, and not disable it forever.
onClick:
public void downloadOnClick(View v) {
counter++;
Log.d(this.getLocalClassName(), "Button was clicked " + counter + " times.");
if(counter>1) return;
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
//mButton.setVisibility(View.GONE);
mButton.setEnabled(false);
//mButton.setClickable(false);
mTextView.setText("Getting html file...");
// if we use simple http, we will need to handle redirect status code
new DownloadWebpageTask().execute("https://www.google.com/");
} else {
mTextView.setText("No network connection available.");
}
}
AsyncTask:
private class DownloadWebpageTask extends AsyncTask<String, Void, String> {
private HttpURLConnection mConnection;
#Override
protected String doInBackground(String... urls) {
try {
URL url = new URL(urls[0]);
mConnection = (HttpURLConnection) url.openConnection();
mConnection.setReadTimeout(10000 /* milliseconds */);
mConnection.setConnectTimeout(15000 /* milliseconds */);
mConnection.setRequestMethod("GET");
mConnection.setDoInput(true);
mConnection.connect();
int statusCode = mConnection.getResponseCode();
if (statusCode != HttpURLConnection.HTTP_OK) {
return "Error: Failed getting update notes";
}
return readTextFromServer(mConnection);
} catch (IOException e) {
return "Error: " + e.getMessage();
}
}
private String readTextFromServer(HttpURLConnection connection) throws IOException {
InputStreamReader stream = null;
try {
stream = new InputStreamReader(connection.getInputStream());
BufferedReader br = new BufferedReader(stream);
StringBuilder sb = new StringBuilder();
String line = br.readLine();
while (line != null) {
sb.append(line + "\n");
line = br.readLine();
}
return sb.toString();
} finally {
if (stream != null) {
stream.close();
}
}
}
#Override
protected void onPostExecute(String result) {
mTextView.setText(result);
// Can not reactivate button / cancel (pending?) events....
//mButton.setVisibility(View.VISIBLE);
mButton.setEnabled(true);
//mButton.setClickable(true);
}
}
The full project (it is very simple, just a training example) is available to test in this repository that I have just created.
To conclude, from what I have read, there is in fact a problem regarding button disabling. Mostly this is resolved through the use of a flag to call the onClick method only when the flag is true. Although, this does not solve the problem of re-enabling the button. I have also tried mButton.cancelPendingInputEvents(); but it does not work (and I do not know why. Click events are not yet registered? Or they are not pending?
Is there a simple solution to this problem? Any ideas? Am I missing some basic detail? If not, I am considering trying to create a new button programatically to contour the problem. If I do not keep references to old buttons, are they deleted through garbage collection?
Thanks in advance!
[Edit] Clarification:
Since the title could be misleading in this point, I want to clarify that I am able to disable and re-enable the button and all the functionality is ok except when the buttion is disabled. And note that I have added the line if(counter>1) return; just to test but it stops the button from working the way I wanted (that's why I am not using a flag. I don't want this line to be there when I solve the problem!). The log is enough to inform me that the method is being called when the button is re-enabled, because I clicked it when it was disabled!

I found that with your example, the AsyncTask was completing so fast that there was a very short amount of time that the Button was not clickable due to being disabled. So, it's basically a timing issue.
I found that by delaying the re-enabling of the Button by 4 seconds, it works as expected.
Note with this change that visually, the Button is re-enabled a split second after the TextView is populated.
Here is the code change in onPostExecute():
#Override
protected void onPostExecute(String result) {
mTextView.setText(result);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
//re-enable the button
mButton.setEnabled(true);
}
}, 4000);
}
Note that you can remove the counter logic and it should work as expected now:
public void downloadOnClick(View v) {
Log.d(this.getLocalClassName(), "Button was clicked");
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
mButton.setEnabled(false);
mTextView.setText("Getting html file...");
// if we use simple http, we will need to handle redirect status code
new DownloadWebpageTask().execute("https://www.google.com/");
} else {
mTextView.setText("No network connection available.");
}
}

The error is here:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DOWNLOAD TEXT"
android:id="#+id/button"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:onClick="downloadOnClick" />
You are missing the concept of OnClickListener! First of all, you have to modify the above xml in this way removing onClick attribute:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DOWNLOAD TEXT"
android:id="#+id/button"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
Than you have to modify the Activity onCreate method in order to set the OnClickListener on your button:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.text);
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
counter++;
Log.d(this.getLocalClassName(), "Button was clicked " + counter + " times.");
if(counter>1) return;
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
mButton.setEnabled(false);
mTextView.setText("Getting html file...");
// if we use simple http, we will need to handle redirect status code
new DownloadWebpageTask().execute("https://www.google.com/");
} else {
mTextView.setText("No network connection available.");
}
}
});
}
This is the right way to handle a click.
See more:
Button | Android Developer
Moreover:
The best way from my point of view is implements the OnClickListener() on your Activity:
public class MyActivity extends Activity implements View.OnClickListener {
}
In this way you can write for each button where you need to set the OnClickListener do:
buttonX.setOnClickListener(this);
buttonY.setOnClickListener(this);
buttonZ.setOnClickListener(this);
In your Activity onClick() you must override the OnClickListener methods, so:
#Override
public void onClick(View v) {
if(v.getId() == R.id.ButtonX)){
//do here what u wanna do.
} else if(v.getId() == R.id.ButtonY){
//do here what u wanna do.
} else if(v.getId() == R.id.ButtonZ){
//do here what u wanna do.
}
}
Also in onClick you could use view.getId() to get the resource ID and then use that in a switch/case block to identify each button and perform the relevant action.

Related

How to cope with Google API asynchronous behavior?

I have a simple Android app. The main activity looks for a file on the internal device storage. What happens after that depends on whether the file exists or not. This sequence works just fine as the whole execution is synchronous.
If I now use Google Drive API, all file access are asynchronous and the main activity goes on without waiting for the result of my search...
I found no way to 'force' a synchronous behavior. Synchronous calls of Google API are not allowed in the UI thread.
Moreover, performing such calls in an AsyncTask leads to the same problem...
Any idea on how to handle such scenarios ?
Regards,
Laurent
------------------------- EDIT ----------------------------------
I tried several options but they all lead to the same result.
Both the main activity and the asynctask used to look for my file get blocked.
Any idea why and what to do ?
Main activity code :
if (!TermsOfUseAgreementHandler.mTermsOfUseAgreed) {
Log.i(mTag, "Terms of use agreement needs to be checked");
mCountDownLatch = new CountDownLatch(1);
TermsOfUseAgreementSearchTask searchAgreementTask = new TermsOfUseAgreementHandler().new TermsOfUseAgreementSearchTask();
// Start the async task to look for agreement file using parallel execution
searchAgreementTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
try {
// Block the main thread so that the task gets a chance to perform its Google Drive access...
mCountDownLatch.await(); // MAIN ACTIVITY HANGS HERE !
} catch (InterruptedException e) {
Log.e(mTag, "Failed to look for user agreement file", e);
}
// The async task should have finished (and decreased mCountDownLatch so that await returns)
// At this moment, the result should be available for the main activity in mTermsOfUseAgreed...
if (!TermsOfUseAgreementHandler.mTermsOfUseAgreed)
TermsOfUseAgreementHandler.checkTermsOfUse(this);
}
AsyncTask code :
protected Boolean doInBackground(Void... params) {
Log.i(MainActivity.mTag, "Looking for user agreement file");
// Look for the agreement file with synchronous Google Drive API access
Query lookForAgreement = new Query.Builder().addFilter(Filters.eq(SearchableField.TITLE, mTermsOfUseAgreementFile)).build();
MetadataBuffer result = Drive.DriveApi.getAppFolder(MainActivity.mGoogleApiClient).queryChildren(MainActivity.mGoogleApiClient, lookForAgreement).await().getMetadataBuffer(); // ASYNCTASK HANGS HERE !
mTermsOfUseAgreed = result != null
&& result.getCount() != 0
&& result.get(0) != null
&& result.get(0).getTitle() != null
&& result.get(0).getTitle().equals(mTermsOfUseAgreementFile);
// Now that the result is available, decrease the countDownLatch so that the main activity may continue...
MainActivity.mCountDownLatch.countDown();
return mTermsOfUseAgreed;
}
------------------------- EDIT 2 ----------------------------------
I also tried to move the connection to the drive API from the main activity to the asynctask as shown in a drive API example link.
I still get the same result but the problem is now the connection because the associated callback listener is never called...
public class TermsOfUseAgreementSearchTask extends AsyncTask<Void, Void, Boolean> {
private GoogleApiClient mClient = null;
final private CountDownLatch latch = new CountDownLatch(1);
public TermsOfUseAgreementSearchTask(Context context) {
GoogleApiClient.Builder builder = new GoogleApiClient.Builder(context)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addScope(Drive.SCOPE_APPFOLDER);
mClient = builder.build();
mClient.registerConnectionCallbacks(new ConnectionCallbacks() {
#Override
public void onConnected(Bundle arg0) {
Log.i(MainActivity.mTag, "Connected");
latch.countDown();
}
#Override
public void onConnectionSuspended(int arg0) {}
});
mClient.registerConnectionFailedListener(new OnConnectionFailedListener() {
#Override
public void onConnectionFailed(ConnectionResult arg0) {
Log.i(MainActivity.mTag, "Connection failed");
latch.countDown();
}
});
}
#Override
protected Boolean doInBackground(Void... params) {
Log.i(MainActivity.mTag, "Connect to drive API");
mClient.connect();
try {
Log.i(MainActivity.mTag, "Wait for connection");
latch.await(); // ASYNC TASK HANGS HERE !
} catch (InterruptedException e) {
return false;
}
if (!mClient.isConnected()) {
return false;
}
try {
Log.i(MainActivity.mTag, "Looking for user agreement file");
// Look for the agreement file with synchronous Google Drive API access
Query lookForAgreement = new Query.Builder().addFilter(Filters.eq(SearchableField.TITLE, mTermsOfUseAgreementFile)).build();
MetadataBuffer result = Drive.DriveApi.getAppFolder(mClient).queryChildren(mClient, lookForAgreement).await().getMetadataBuffer();
mTermsOfUseAgreed = result != null
&& result.getCount() != 0
&& result.get(0) != null
&& result.get(0).getTitle() != null
&& result.get(0).getTitle().equals(mTermsOfUseAgreementFile);
// Now that the result is available, decrease the countDownLatch so that the main activity may continue...
//MainActivity.mCountDownLatch.countDown();
return mTermsOfUseAgreed;
} finally {
mClient.disconnect();
}
}
}

android getDrawable() doesn't run

I am trying to make an app to turn on mobile data on click. However, it requires to detect if mobile data is already on. If it is already on, data1.png is displayed else data0.png. However, for some reason it is not changing. TEST 2 is running though. (wdata is Image Button) This is the code:
ConnectivityManager cm = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
try {
Class cmClass = Class.forName(cm.getClass().getName());
Method method = cmClass.getDeclaredMethod("getMobileDataEnabled");
method.setAccessible(true); // Make the method callable
// get the setting for "mobile data"
mobEnabled = (Boolean)method.invoke(cm);
} catch (Exception e) {
// Some problem accessible private API
// TODO do whatever error handling you want here
}
Toast.makeText(MainActivity.this, ""+mobEnabled, Toast.LENGTH_SHORT).show();
IF MOBILE DATA IS ENABLED, IT RETURNS TRUE.
if(mobEnabled){
wdata1=getResources().getDrawable(R.drawable.data1);
Toast.makeText(MainActivity.this, ":Test 1:"+mobEnabled, Toast.LENGTH_SHORT).show(); //RUNS
wdata.setImageDrawable(wdata1);// DOESN'T SET
}else{
wdata1 =
getResources().getDrawable(R.drawable.data0);
Toast.makeText(MainActivity.this, ":Test 2:"+mobEnabled, Toast.LENGTH_SHORT).show();
wdata.setImageDrawable(wdata1);
}
I have used the Drawable wdata1 in other areas:
wdata.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(mobileDataEnabled(getApplicationContext())){
setMobileDataEnabled(getApplicationContext(), false);
wdata1 =
getResources().getDrawable(R.drawable.data0);
amobileDataEnabled = false;
}else{
setMobileDataEnabled(getApplicationContext(),true);
wdata1 =
getResources().getDrawable(R.drawable.data1);
amobileDataEnabled = true;
}
wdata.setImageDrawable(wdata1);
}
});
And in the onResume() method:
if( amobileDataEnabled){
wdata1 =
getResources().getDrawable(R.drawable.data1);
wdata.setImageDrawable(wdata1);
}else {
wdata1 =
getResources().getDrawable(R.drawable.data0);
wdata.setImageDrawable(wdata1);
}
After setting a drawable to something else, try calling wdata.invalidateDrawable(wdata1); right afterwards to indicate it needs to be refreshed.

Display Toast from AsyncTask until event occurs

I would like to display "Connecting ..." using Toast until the device is connected to a new network (more specifically when an IP address is obtained, see the while loop in doInBackground below). I am using AsyncTask for connecting to the network, but if I put Toast.makeText(...).show() inside onProgressUpdate() the toast calls will stack and end up displaying the text way longer than desired. My Connect class:
public class Connect extends AsyncTask<Object,Void,Void>{
private static final String TAG="sure2015test";
private Context context;
private Network network;
#Override
protected Void doInBackground(Object... params) {
this.network=(Network)params[0];
this.context=(Context) params[1];
final WifiConfiguration conf = new WifiConfiguration();
conf.SSID = "\"" + network.ssid + "\"";
if(network.password!=null){
conf.preSharedKey = "\""+ network.password +"\"";
}
else{
conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
}
if(network.manager.addNetwork(conf)==-1){
Log.i(TAG, "Add network fail");
}
List<WifiConfiguration> configs = network.manager.getConfiguredNetworks();
for (WifiConfiguration i : configs) {
if (i.SSID != null && i.SSID.equals("\"" + network.ssid + "\"")) {
network.manager.disconnect();
if(network.manager.enableNetwork(i.networkId, true)==false){
Log.i(TAG,"Enable Network fail ");
}
if(network.manager.reconnect()==false) {
Log.i(TAG, "Reconnect fail");
}
}
}
WifiInfo info=network.manager.getConnectionInfo();
while((info.getIpAddress())==0){
//Wait until non-zero IP address (once a non-zero ip address is obtained, you are connected to the network)
//Tried to use NetworkInfo.DetailedState to check if it was CONNECTED
//However the api method said it remained in OBTAINING_IPADDR state even after it obtained an ip (must be bug)
info=network.manager.getConnectionInfo();
publishProgress();
Log.i(TAG,"IP "+info.getIpAddress());
try{
Thread.sleep(100);
}
catch(InterruptedException e){
Log.i(TAG,"Interrupted exception");
}
}
return null;
}
#Override
protected void onProgressUpdate(Void... values) {
Toast.makeText(context, "Connecting ...", Toast.LENGTH_SHORT).show();
super.onProgressUpdate(values);
}
#Override
protected void onPostExecute(Void aVoid) {
Toast.makeText(context,"Connected",Toast.LENGTH_SHORT).show();
Intent newActivity = new Intent(context, Device.class);
newActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
newActivity.putExtra("Device", network.ssid);
context.startActivity(newActivity);
super.onPostExecute(aVoid);
}
}
I'd suggest using a ProgressDialog instead of a Toast. You can show it before running the AsyncTask and hide it onPostExecute. It'd look something like.-
ProgressDialog dialog = ProgressDialog.show(CurrentActivity.this, "", "Connecting...", true, false);
[...]
dialog.dismiss();
By the way, don't forget to put your string literals in a Strings resources file :)
Toast is not a good practise to show progress work. Toast is designed to show minor and fast information(ex. In your case is to show that devise is connected), but in my opinion it's better to implement ProgressBar in your layout and set it visible while AsyncTask execute doInBackground and set it invisible in onPostExecute.
If you need to show Toast within the other thread, also within doInBackground(Object... params), you can use:
YourActivity.this.runOnUiThread(new Runnable() {
public void run() {
//put here your thread
}
});
Hovewer, you need to get Context in this case. I make it with static variable. But do not forget, the Toast is appearing only for short time and you can't manage it. If you need to show the message all the time you do some stuff, you can use ProgressDialog

Android:No source path

Im having a pretty simple requirement.I have a Login activity. Once user enters login details and clicks on submit button, the application should perform a internet access check. If device is not connected to internet then it should display a toast saying "No internet access". If internet is accessible then the application will perform authentication and navigate to Home page.
I have written the code snippet to perform internet check in a separate class for re usability. The code snippet is as below:
package com.example.rinventory.Common;
import android.content.Context;
import android.net.NetworkInfo;
import android.net.ConnectivityManager;
public class ConnectionDetector
{
private Context _context;
public ConnectionDetector(Context context)
{
this._context = context;
}
public boolean isConnectingToInternet()
{
ConnectivityManager connectivity = (ConnectivityManager) _context.getSystemService(Context.CONNECTIVITY_SERVICE);
//Get all the active networks available.
NetworkInfo info = connectivity.getActiveNetworkInfo();
//If it is connected to available networks return true else false.
if(info != null && info.isConnected())
return true;
else
return false;
}
}
And my Login activity's submit button functionality is as below:
public class RInventoryLogin extends ActionBarActivity
{
boolean isInternetPresent = false;
ConnectionDetector detect;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rinventory_login);
Button btnLogin = (Button) findViewById(R.id.btnSubmit);
btnLogin.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
//Throws error at this particular line
isInternetPresent = detect.isConnectingToInternet();
if(isInternetPresent)
{
EditText editEmail = (EditText)findViewById(R.id.editEmail);
EditText editPassword = (EditText)findViewById(R.id.editPassword);
LoginData data = new LoginData();
data.setEmail(editEmail.getText().toString());
data.setPassword(editPassword.getText().toString());
new CallJsonParserLogin().execute(data);
}
else
{
Toast.makeText(RInventoryLogin.this, "No Internet Access", Toast.LENGTH_SHORT).show();
}
}
});
}
}
When I try to execute, it loads the activity. However once I enter login details and click on submit the application shuts down with "unexpectedly your application has stopped working"
I have included Internet, Network state parameters in Manifest file.
When I comment the line
isInternetPresent = detect.isConnectingToInternet();
and subsequent if else blocks it just works fine. What might be the issue I'm missing here?
I have tried the same creating a demo application and that works fine.
Please help
you are not initializing detect object.
add this line before setting click listener to your button :
detect = new ConnectionDetector(this);

code to get phone nos from contacts in android

I am building an app that gets 3 contacts from CONTACTS ,,
i want a ready made code to do this and to also show to the user in a layout,, which numbers he has choosen. I have heard that this task is to be done in a background thread I was thinking that if i would do this using
extending AsynTask
is it true?? Please give me full code to do that.
My activity in which i have to include this code is like this:
public class Registration extends Activity implements View.OnClickListener {
RegisteredUser user;
EditText name,mobile ;
Button guardian1,guardian2;
#Override
protected void onCreate(Bundle savedInstanceState) {
Button submit;
super.onCreate(savedInstanceState);
setContentView(R.layout.registration);
submit = (Button) findViewById(R.id.register_registration_submit);
submit.setOnClickListener(this);
}
#Override
public void onClick(View view) {
switch(view.getId()){
case R.id.register_registration_submit:
if(isConnected()){
if(inputData())
Log.d("gone","hum");
new SendRegistrationData(getBaseContext(),Registration.this).execute(user);
}
else
Toast.makeText(getBaseContext(), "You are not connected to internet.Please connect yourself and try again.", Toast.LENGTH_LONG).show();
break;
default: Log.d("application","no button match");
}
}
private boolean inputData() {
user = new RegisteredUser();
name = (EditText) findViewById(R.id.registration_name);
mobile = (EditText) findViewById(R.id.registration_mob);
user.setName(name.getText().toString().trim());
user.setMobile(mobile.getText().toString().trim());
//code to input name and mobile and also validation
//** HERE I WANT CODE TO GET 3 CONTACTS .WHEN USER CLICK ON BUTTON.
button.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View arg0) {
new GetContact().execute();
}
});
/*/ also put a validation to check either the user has choosen
all three contacts if not,, return false to input data ,,
so that form can not be submitted */
return true;
}
public boolean isConnected(){
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Activity.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected())
return true;
else
return false;
}
}
basically what you need to do to retrieve contacts from your phone is to use Content Providers: http://developer.android.com/guide/topics/providers/content-providers.html
You'll have to query your contacts and say what fields you want to retrieve from them, this query will return a Cursor to you and this cursor can be used to iterate through your contacts.
You can get a complete documentation about it on this page: http://developer.android.com/guide/topics/providers/contacts-provider.html
Once you get a hold of it, it's quite simple.

Categories

Resources