I'm using a ListView on my Activity and it takes a while to load from a SQLite DB, so I wanted to show a ProgressDialog to the user to let them know something is loading. I tried to run the task on a separate thread but I'm getting a CalledFromWrongThreadException. Here's my main Activity code:
#Override
public void onCreate(Bundle savedInstanceState)
{
try
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.open_issues);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
//Set Window title.
final TextView title = (TextView) findViewById(R.id.customTitle);
if (title != null)
title.setText("Open Issues");
//Call Async Task to run in the background.
new LoadIssuesTask().execute();
}
catch (Exception e)
{
Errors.LogError(e);
}
}
And the LoadIssuesTask code:
private class LoadIssuesTask extends AsyncTask<Void, Void, Cursor> {
ProgressDialog pdDialog = null;
protected void onPreExecute()
{
try
{
pdDialog = new ProgressDialog(OpenIssues.this);
pdDialog.setMessage("Loading Issues and Activities, please wait...");
pdDialog.show();
}
catch (Exception e)
{
Errors.LogError(e);
}
}
#Override
protected Cursor doInBackground(Void... params) {
LoadIssues();
return null;
}
#Override
protected void onPostExecute(Cursor c) {
pdDialog.dismiss();
pdDialog = null;
}
}
And the LoadIssues code:
private void LoadIssues(){
//Set listview of Issues.
ListView lvIssues = (ListView)findViewById(R.id.lvIssues);
lvIssues.setOnItemClickListener(viewIssuesListener);
IssueCreator = new IssueInfoCreator(this, Integer.parseInt(AppPreferences.mDBVersion));
IssueCreator.open();
lvIssues.setAdapter(new IssueInfoAdapter(this, IssueCreator.queryAll()));
IssueCreator.close();
}
Constructor for IssueInfoAdapter:
public IssueInfoAdapter(Context c, List<IssueInfo> list){
mListIssueInfo = list;
//create layout inflater.
mInflater = LayoutInflater.from(c);
}
It's throwing the error on the .setAdapter method inside LoadIssues().
ERROR:
03-12 10:41:23.174: E/AndroidRuntime(11379): Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
You're trying to access the views in the doInBackground method that doesn't run on the main UI thread. You'll have to set your adapter in the method onPostExecute that runs on the UI thread:
#Override
protected void onPostExecute(List<IsueInfo> items) {
pdDialog.dismiss();
ListView lvIssues = (ListView)findViewById(R.id.lvIssues);
lvIssues.setOnItemClickListener(viewIssuesListener);
lvIssues.setAdapter(new IssueInfoAdapter(this, items));
}
and in your doInBackground method:
#Override
protected List<IssueInfo> doInBackground(Void... params) {
IssueCreator = new IssueInfoCreator(this, Integer.parseInt(AppPreferences.mDBVersion));
IssueCreator.open();
IssueCreator.close();
return IssueCreator.queryAll();
}
Also your AsyncTask should be:
private class LoadIssuesTask extends AsyncTask<Void, Void, List<IssueInfo>>
In private void LoadIssues method call handler.setMessage(0) and create a private Handler instance to call setAdapter method
Use Handler instead of Asynctask.
Related
I want to generate a form into my activity_main.xml ScrollView. XML is loaded and parsed correctly but when I'm trying to addView(LinearLayout) then it throws exception e. My application gets url of a XML file via push notification and then parses it. According to XML it then should generate a form and display it to the user. I used this as an example: https://www.ibm.com/developerworks/xml/tutorials/x-andddyntut/#l1
Here is my main activity:
public class MainActivity extends Activity {
// label to display gcm messages
TextView lblMessage;
Controller aController;
public ScrollView sv;
Button execute;
// Asyntask
AsyncTask<Void, Void, Void> mRegisterTask;
public static String name;
public static String email;
final Context context = this;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ScrollView sv = (ScrollView) findViewById(R.id.sv);
...
}
// Create a broadcast receiver to get message and show on screen
private final BroadcastReceiver mHandleMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String newMessage = intent.getExtras().getString(Config.EXTRA_MESSAGE);
// Waking up mobile if it is sleeping
aController.acquireWakeLock(getApplicationContext());
ScrollView sv = (ScrollView) findViewById(R.id.sv);
new DoInBackground(getApplicationContext(), sv).execute(newMessage);
// Releasing wake lock
aController.releaseWakeLock();
}
};
and here is my async class:
public class DoInBackground extends AsyncTask<String, Void, Void> {
Context mContext;
ScrollView mSv;
String tag = "DynamicFormXML";
XmlGuiForm theForm;
ProgressDialog progressDialog;
Handler progressHandler;
public DoInBackground(Context context, ScrollView sv) {
this.mContext = context;
this.mSv = sv;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(String... params) {
if (GetFormData(params[0])) {
DisplayForm();
}
else
{
Log.e(tag,"Couldn't parse the Form.");
AlertDialog.Builder bd = new AlertDialog.Builder(mContext);
AlertDialog ad = bd.create();
ad.setTitle("Error");
ad.setMessage("Could not parse the Form data");
ad.show();
}
return null;
}
protected void onPostExecute() {
}
private boolean DisplayForm()
{
try
{
final LinearLayout ll = new LinearLayout(mContext);
mSv.addView(ll); //Here it fails
ll.setOrientation(android.widget.LinearLayout.VERTICAL);
...
} catch (Exception e) { // Goes to here
Log.e(tag,"Error Displaying Form");
return false;
}
}
I think the context of the main activity and also the empty Scrollview in main activity are forwarded correctly (they are not null) but i'm not 100% sure. Any help/hints are appreciated! :)
You can not touch the GUI from a background thread (e.g. the one running the doInBackground method).
In an AsynTask, you can put the UI code in onPostExecute, wich is invoked on the UI thread with the result of doInBackground.
If you have intermediate results you can call publishProgress from doInBackground, this will trigger the invocation of onProgressUpdate on the UI thread, where you can update the UI.
See AsyncTask API for an example and more details on what must be done on which thread.
Solution
Reordering code (so GUI stuff would be done onPostExecute) worked. Also i had a problem with not getting to onPostExecute() but i had to change it to onPostExecute(Void result).
Now my code looks like this and works like a charm:
public class DoInBackground extends AsyncTask<String, Void, Void> {
Context mContext;
LinearLayout mLl;
String tag = "DynamicFormXML";
XmlGuiForm theForm;
ProgressDialog progressDialog;
Handler progressHandler;
public DoInBackground(Context context, LinearLayout ll) {
this.mContext = context;
this.mLl = ll;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(String... params) {
getFormData(params[0]);
return null;
}
protected void onPostExecute(Void result) {
DisplayForm();
}
I also added ScrollView and LinearLayout to my activity_main.xml so DisplayForm() looks like that (if you want to follow the example i mentioned before ):
private void DisplayForm() {
try
{
// walk thru our form elements and dynamically create them, leveraging our mini library of tools.
int i;
for (i=0;i<theForm.fields.size();i++) {
if (theForm.fields.elementAt(i).getType().equals("text")) {
theForm.fields.elementAt(i).obj = new XmlGuiEditBox(mContext,(theForm.fields.elementAt(i).isRequired() ? "*" : "") + theForm.fields.elementAt(i).getLabel(),"");
mLl.addView((View) theForm.fields.elementAt(i).obj);
}
...
I am getting from time to time testing my app error:
03-04 20:57:01.929: E/TestApp(13673): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
From questions like this: Whats this exception?, and my own experience (I got this same error from time to time as in mentioned question) I would like to ask you guys what I can do to get rid of it?
As far as I know, I can do some stuff on AsyncTask connected to View, so I don't know why I am getting this info.
This is my code:
private MyDBAdapter mySQLiteAdapter;
private ListView wordList;
private AsyncDBDownload asycn;
private ProgressDialog dbUpdate;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.smart_guide_ocr);
asycn = new AsyncDBDownload();
wordList = (ListView) findViewById(R.id.wordsList);
//...
}
#Override
protected void onResume() {
super.onResume();
asycn.execute(null);
}
private class AsyncDBDownload extends AsyncTask<String, Integer, String> {
#Override
protected String doInBackground(String... params) {
try {
refreshList();//upload of contetn and set of adapter
} catch (Exception ex) {
Log.e(TAG, ex.toString());
}
return null;
}
#Override
protected void onPostExecute(String result) {
dbUpdate.dismiss();
}
#Override
protected void onPreExecute() {
dbUpdate = ProgressDialog.show(TestAppActivity.this, "Wait",
"DB download");
}
}
private void refreshList() {
mySQLiteAdapter = new MyDBAdapter(TestAppActivity.this);
mySQLiteAdapter.open();
String[] columns = { MyDBAdapter.KEY_TRANSLATED, MyDBAdapter.KEY_WORD, MyDBAdapter.KEY_LANG,
MyDBAdapter.KEY_ID };
Cursor contentRead = mySQLiteAdapter.getAllEntries(false, columns,
null, null, null, null, MyDBAdapter.KEY_ID, null);
startManagingCursor(contentRead);
Log.d(TAG, Integer.toString(contentRead.getCount()));
RowItem adapterCursor = new RowItem(this, R.layout.save_word_row,
contentRead, columns, new int[] { R.id.translatedWord, R.id.orgWord, R.id.langInfo }, 0);
wordList.setAdapter(adapterCursor);
mySQLiteAdapter.close();
}
You must not call wordList.setAdapter(adapterCursor); from within refresList method. That's a way of "changing a view from a non-UI thread".
So, instead, save the adapterCursor instance and use it from within the onPostExecute method.
You can not manipulate your Views within a background task. Do all the loading you need in your AsyncTask, pass it back into the activity in onPostExecute and set your adapter then. Doing any form of UI manipulation in a background task or service will throw this error.
I'm trying to make multithreading but I've got this stacktrace on few smartphones (SGS2) :
java.lang.ExceptionInInitializerError
at com.android.bordeaux.code.model.AnnouncersContainer.setLoudArrayFromJSON_online(AnnouncersContainer.java:68)
at com.android.bordeaux.code.SplashscreenActivity_Second$1.run(SplashscreenActivity_Second.java:55)
Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:421)
at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:421)
at android.os.AsyncTask.<clinit>(AsyncTask.java:152)
... 2 more
Here is my main activity (splashscreen) wich is making wait during few seconds in order to wait my asynctask finish:
public class SplashscreenActivity_Second extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.splashscreen_second);
// thread
Thread splashThread = new Thread()
{
#Override
public void run()
{
try
{
int waited = 0;
if (isInternetOn())
{
// Here I'm making my asynctask !!
AnnouncersContainer.setLoudArrayFromJSON_online(getApplicationContext());
while (waited < 5000)
{
sleep(50);
waited += 40;
}
}
else
{
AnnouncersContainer.setLoudArrayFromXML_local(getApplicationContext());
while(waited < 5000)
{
sleep(50);
waited += 60;
}
}
}
catch( InterruptedException e )
{
e.printStackTrace();
}
finally
{
Intent intent_to_tabhost = new Intent(SplashscreenActivity_Second.this, MyTabActivity.class);
startActivity(intent_to_tabhost);
finish();
}
}
};
splashThread.start();
}
}
Here is my asynctask :
public class DownloadAnnouncers extends AsyncTask<Void, Integer, Boolean>
{
public static Boolean loadFinished = false;
//JSON variables..
private static String url = null;
Context context;
public DownloadAnnouncers(Context context)
{
this.context = context;
}
#Override
protected void onPreExecute()
{
super.onPreExecute();
}
protected Boolean doInBackground(Void... params)
{
// fine retrieving all my JSON data in a global array..
}
#Override
protected void onPostExecute(Boolean downloadedArray)
{
super.onPostExecute(downloadedArray);
}
}
I know the problem is that I'm making multithreading in the UI but someone can tell me how to delete my looping thread and replacing it just with onPostExecute() method of my Asynctask ?? (it could be better to wait for asynctask finishes rather than making wait..)
Or may be help me to correct this bug with multithreading..
EDIT :
my asynctask :
public class DownloadAnnouncers extends AsyncTask<Void, Integer, Boolean>
{
public static Boolean loadFinished = false;
//JSON variables..
private static String url = null;
Context context;
public DownloadAnnouncers(Context context)
{
this.context = context;
}
#Override
protected void onPreExecute()
{
super.onPreExecute();
}
protected Boolean doInBackground(Void... params)
{
// fine retrieving all my JSON data in a global array..
}
#Override
protected void onPostExecute(Boolean downloadedArray)
{
super.onPostExecute(downloadedArray);
Intent intent_to_tabhost = new Intent(context, MyTabActivity.class);
intent_to_tabhost.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent_to_tabhost);
}
}
But now if I press cancel button I'm returning to my splashscreen activity.. and I cannot do ctx.finish() in onPostExecute method..
You can't create a Handler inside a Thread that has not called Looper.prepare(), just as your Error message says:
Can't create handler inside thread that has not called Looper.prepare()
The Handler is created within the AsyncTask, so it's not something you might see at first.
If you want to keep your code as it is you would fix it by moving the creation of the AsyncTask to outside your custom Thread.
However, there is no need for you to create the thread, you can just create your AsyncTask in the ui thread (It will run on it's own thread so it won't lock your phone while working), and perform any action you want after the task is complete in the onPostExecute-method.
class ExampleAsync extends AsyncTask<Void, Integer, Boolean>
{
public static Boolean loadFinished = false;
//JSON variables..
private static String url = null;
Context context;
public DownloadAnnouncers(Context context){
this.context = context;
}
protected Boolean doInBackground(Void... params){
// fine retrieving all my JSON data in a global array..
}
#Override
protected void onPostExecute(Boolean downloadedArray){
Intent intent_to_tabhost = new Intent(context, MyTabActivity.class);
startActivity(intent_to_tabhost);
}
}
And in your Activity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splashscreen_second);
AsyncTask myTask = new ExampleAsync(this);
myTask.execute();
}
Instead of waiting for the asyncTask , use the onPostExecute , which is called on the UI thread only after the task has finished.
You should also cancel the task if the activity was destroyed (for example if the user has pressed the back button while the splash screen is shown) .
In my code I load a spinner adapter by using Async Task
In My case The ProgressDialog is Not dismissing
This is My code.
I want to show the item after adapter load and the progressDialog is to dismiss
Please Help me, Thanks
private class LoadMoreVehicals extends AsyncTask<Object, Integer, Object> {
#Override
protected void onPreExecute() {
progressBar = ProgressDialog.show(RegistrationScreen.this, "",
"Loading...");
progressBar.setIndeterminate(true);
progressBar.setIndeterminateDrawable(getResources().getDrawable(
R.anim.progressbar_handler));
super.onPreExecute();
}
#Override
protected Object doInBackground(Object... params) {
String countryUrl = ConstantURL.COUNTRY_URL;
getCounty(countryUrl);
countrySpinner
.setAdapter(new MyCustomSpinnerAdapter(
RegistrationScreen.this,
R.layout.spinner_dropdown,
countyList));
return null;
}
#Override
protected void onProgressUpdate(Integer... values) {
progressBar.getProgress();
}
#Override
protected void onPostExecute(Object result) {
progressBar.dismiss();
Log.e("Im in onPostExecute", "");
super.onPostExecute(result);
}
}
While programming in Android you should remember one thing that any task which draws something on the screen should be executed on the main thread. When you set the adapter then android calls the getView() method of the adapter and draws views on the screen. So you should set the adapter in the postExecute() method instead in doInBackground() method.
Here is a small sample to clear my point:
class MyTask extends AsyncTask<Void, Void, Void> {
ProgressDialog pd = new ProgressDialog(MainActivity.this);
#Override
protected void onPreExecute ( )
{
//starting the progress dialogue
pd.show();
}
#Override
protected Void doInBackground (Void... params)
{
//fetch data here
...
...
return null;
}
#Override
protected void onPostExecute (Void result)
{
//set adapter here
...
...
//dismissing the progress dialogue
pd.dismiss();
}
}
In my experience i have so many problems with async runs and UI so now always separate the stuff trying to place the "responsibilities" in each place. So i do something like this:
Create my Async class with the process i want to do and nothing that transform the UI in it
Create a function in UI thread that modify the UI when async task finish, something like OnAsyncTaskComplete(Object response)
Keep communicated the threads
public class MyActivity extends Activity {
private static MyAsyncClass backgroundTask;
private static ProgressDialog pleaseWaitDialog;
//......activity stuff.......
#Override
public void onPause()
{
super.onPause();
//Get rid of progress dialog in the event of a screen rotation or other state change. Prevents a crash.
if (pleaseWaitDialog != null)
pleaseWaitDialog.dismiss();
}
//Function to avoid lose the async thread if the app interrupts (phone rotation, incoming call, etc) RECOMENDED TO HANDLE THIS!!
//Sets the current state after app resume
#Override
public void onResume()
{
super.onResume();
//If there is a background task set it to the new activity
if ((backgroundTask != null) && (backgroundTask.getStatus() == Status.RUNNING))
{
if (pleaseWaitDialog != null)
pleaseWaitDialog.show();
backgroundTask.setActivity(this);
}
}
}
//Logic business after the web service complete here
//Do the thing that modify the UI in a function like this
private void onTaskCompleted(Object _response)
{
//For example _response can be a new adapter
MyList.setAdapter((BaseAdapter)_response);
//or can be a list to create the new adapter
MyList.setAdapter(new MyAdapter(this, (ArrayList<String>)_response));
//or can be anything you want, just try to make here the things that you need to change the UI
}
/**
* Class that handle the async task
*/
public class MyAsyncClass extends AsyncTask<Void, Void, Object>
{
//Maintain attached activity for states change propose
private MyActivity activity;
//Keep the response of the async task
private Object _response;
//Flag that keep async task completed status
private boolean completed;
//Constructor
private MyAsyncClass(MyActivity activity) {
this.activity = activity;
}
//Pre execution actions
#Override
protected void onPreExecute() {
//Start the splash screen dialog
if (pleaseWaitDialog == null)
pleaseWaitDialog= ProgressDialog.show(activity.this,
"PLEASE WAIT",
"Getting results...",
false);
}
//Execution of the async task
protected Object doInBackground(Object...params)
{
//return the thing you want or do want you want
return new ArrayList();
}
//Post execution actions
#Override
protected void onPostExecute(Object response)
{
//Set task completed and notify the activity
completed = true;
_response = response;
notifyActivityTaskCompleted();
//Close the splash screen
if (pleaseWaitDialog != null)
{
pleaseWaitDialog.dismiss();
pleaseWaitDialog = null;
}
}
//Notify activity of async task completion
private void notifyActivityTaskCompleted()
{
if ( null != activity ) {
activity.onTaskCompleted(_response);
}
}
//for maintain attached the async task to the activity in phone states changes
//Sets the current activity to the async task
public void setActivity(MyActivity activity)
{
this.activity = activity;
if ( completed ) {
notifyActivityTaskCompleted();
}
}
}
}
Hope its help you
First of all you cannot set the adapter in the doInBackground
follow this design:
private class LoadMoreVehicals extends AsyncTask<Object, Integer, Object>
{
private ArrayList<Country> countries;
#Override
protected void onPreExecute() {
progressBar = ProgressDialog.show(RegistrationScreen.this, "","Loading...");
progressBar.setIndeterminate(true);
progressBar.setIndeterminateDrawable(getResources().getDrawable(R.anim.progressbar_handler));
super.onPreExecute();
}
#Override
protected Object doInBackground(Object... params) {
String countryUrl = ConstantURL.COUNTRY_URL;
countries = getCounty(countryUrl);
return null;
}
#Override
protected void onProgressUpdate(Integer... values) {
progressBar.getProgress();
}
#Override
protected void onPostExecute(Object result) {
countrySpinner.setAdapter(new MyCustomSpinnerAdapter(RegistrationScreen.this,R.layout.spinner_dropdown,countries));
progressBar.dismiss();
Log.e("Im in onPostExecute", "");
super.onPostExecute(result);
}
}
What I want: I want to show a progress dialog while adding views dynamically to my ViewFlipper.
What I have: I have used an AsyncTask to achieve this. The ViewFlipper is declared in my Main Activity, I am adding views to ViewFlipper in the doInBackground() of AsyncTask.
What is the problem: I am getting an exception on the viewFlipper.addView() statement and the exception is " Main has leaked window com.android.internal.policy.impl ..... that was originally added here. " , something like this.
Here is the code:
public class Main extends Activity
{
private ViewFlipper viewFlipper;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main_flipper);
viewFlipper = (ViewFlipper)findViewById(R.id.id_vf_main);
new LoadData().execute();
// Some other tasks.
}
class LoadData extends AsyncTask<Object, Void, String>
{
protected ProgressDialog progressDialog;
#Override
protected void onPreExecute()
{
super.onPreExecute();
progressDialog = ProgressDialog.show(Main.this,"Loading", "Loading Data...", true, false);
}
#Override
protected String doInBackground(Object... parametros)
{
for (int i = 0; i < Login.data_Channel_Name.size(); i++)
{
LayoutInflater inflater = getLayoutInflater();
RelativeLayout rl_main = (RelativeLayout) inflater.inflate(R.layout.main,null);
TextView tv_channelNumber = (TextView)rl_main.findViewById(R.id.id_tv_ChannelNumber);
if(tv_channelNumber != null)
{
tv_channelNumber.setText("Some Number");
}
TextView tv_channelName = (TextView)rl_main.findViewById(R.id.id_tv_ChannelName);
if(tv_channelName != null)
{
tv_channelName.setText("Some name");
}
viewFlipper.addView(rl_main);
}
return null;
}
#Override
protected void onPostExecute(String result)
{
super.onPostExecute(result);
progressDialog.dismiss();
}
}
}
You are trying to interact with the UI from the background thread which isn't allowed. You are allowed to mess with the UI in your onPostExecute() and onPreExecute() methods. There is another method; onProgressUpdate() you can use like so:
protected void onProgressUpdate(Integer... progress) {
// on UI thread!
viewFlipper.addView(rl_main);
}
Obviously you'll need to handle passing the view to this method by creating a field or something. You can call this within your doInBackground() with the following:
publishProgress(0);
The function is typically used for updating the percentage on a loading bar or similar (hence passing the integer) but should work fine your your purposes.