Currently I have a button that loads an activity.
That activity has a ListView in it that for whatever reason takes a fairly long time to load(not waiting for data, the data is already in memory). The problem is that while it's rendering the list, the UI is waiting where the button was clicked, and doesn't change screen until the list has fully loaded.
What I'd like, is to only display the list once the activity is loaded, so at least something happens as soon as they press the button(responsive UI is important).
Currently, my hack around solution to this, is to spawn a thread, wait for 50 milliseconds, and then set the adapter for my list(using runOnUiThread). Of course, if the activity takes longer than 50ms to load on some phones, then it'll have to load the entire list to load the activity again.
Current relevant code:
#Override
public void onPostResume() {
super.onPostResume();
new Thread(new Runnable() {
#Override
public void run() {
final AdapterData data = getData();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
myAdapter = new MyAdapter(MyActivity.this, data);
lvMyList.setAdapter(myAdapter);
}
});
}
}).start();
}
I just changed variable names in the code, but that's not relevant. This is just so you can see my general solution.
I feel like there should be some kind of callback that's called when the activity finishes creating. I thought that onPostResume waited for the activity to finish loading, but that did not work. It still hung.
you can use IdleHandler
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.xxx);
final AdapterData data = getData();
IdleHandler handler = new IdleHandler() {
#Override
public boolean queueIdle() {
myAdapter = new MyAdapter(MyActivity.this, data);
lvMyList.setAdapter(myAdapter);
return false;
}
};
Looper.myQueue().addIdleHandler(handler);
}
Related
So I will start with my final outcome that I would like to acheive and give you what I have done and then things that I have tried.
Final Outcome is to have a pdf file download and display on screen in a fragment. Have a Indefinite Progress Bar spinning while the pdf is downloading and loading to screen. When document is loaded to screen dismiss the progress bar. Because the progress bar disappears before the pdf is displayed add about 5 seconds to it.
So with my current code on the devices that I have tested it works beautifully. However after letting others test it, it crashes and we get android.view.ViewRootImpl$CalledFromWrongThreadException.
So here is the current code that on some devices causes the above error:
public class MenuFragment extends Fragment {
PDFView pdfView;
ProgressBar simpleProgressBar;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//just change the fragment_dashboard
//with the fragment you want to inflate
//like if the class is HomeFragment it should have R.layout.home_fragment
//if it is DashboardFragment it should have R.layout.fragment_dashboard
return inflater.inflate(R.layout.fragment_menu, null);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
pdfView = getView().findViewById(R.id.pdfView);
new RetrievePDFStream().execute("http://gator3067.temp.domains/~kazack/app/Menu.pdf");
}
class RetrievePDFStream extends AsyncTask<String,Void, InputStream> {
#Override
protected InputStream doInBackground(String... strings) {
simpleProgressBar = getView().findViewById(R.id.simpleProgressBar);
simpleProgressBar.setVisibility(View.VISIBLE);
InputStream inputStream = null;
try{
URL url = new URL(strings[0]);
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
if(urlConnection.getResponseCode() == 200)
{
inputStream = new BufferedInputStream((urlConnection.getInputStream()));
}
}
catch (IOException e)
{
return null;
}
return inputStream;
}
#Override
protected void onPostExecute(InputStream inputStream){
pdfView.fromStream(inputStream).load();
new Thread() {
public void run() {
try{
// Do some work here
sleep(5000);
} catch (Exception e) {
}
// start next intent
new Thread() {
public void run() {
// Dismiss the Dialog
simpleProgressBar.setVisibility(View.INVISIBLE);
// start selected activity
}
}.start();
}
}.start();
}
}
}
So in my new revised code I already made one adjustment, which is inregards to accessing UI in background. So I moved the 2 following lines from background to PreExecute method:
simpleProgressBar = getView().findViewById(R.id.simpleProgressBar);
simpleProgressBar.setVisibility(View.VISIBLE);
Now I do understand the error which is you can not access it from a thread that did not start it to begin with. Which in my code I am creating a new thread and trying to dismiss it that way. Which will throw the exception error on some devices.
So after reading some more, I found out that instead of using new Thread that I should be using:
getActivity().runOnUiThread(new Runnable()
So I rewrote my code to do that and what happens is the indeterminate does not spin and just sits there doing nothing and still disappears fairly quickly.
So as a temporary solution I changed OnPostExecut to:
#Override
protected void onPostExecute(InputStream inputStream){
pdfView.fromStream(inputStream).load();
simpleProgressBar.setVisibility(View.INVISIBLE);
}
Now all is good, however I do not get the indefinite progress bar to run an additional 5 seconds like I get with my original code, which is apparently wrong because it crashes on some devices.
Any and all help would be greatly appreciative, again overall goal is to take what I have and add 5 additional seconds to the progress bar before dismissing it.
I did not know if there is a way to dismiss it from one thread while in another.
I believe issue is now resolved. After typing in everything above a thought hit me, so I tried it and walah it worked. Created the apk and ran it on a working device and also ran it on a device that was crashing. And both work beautifully.
So the end result was changing the onPostExecute from
#Override
protected void onPostExecute(InputStream inputStream){
pdfView.fromStream(inputStream).load();
new Thread() {
public void run() {
try{
// Do some work here
sleep(5000);
} catch (Exception e) {
}
// start next intent
new Thread() {
public void run() {
// Dismiss the Dialog
simpleProgressBar.setVisibility(View.INVISIBLE);
// start selected activity
}
}.start();
}
}.start();
}
to:
#Override
protected void onPostExecute(InputStream inputStream){
pdfView.fromStream(inputStream).load();
new Thread() {
public void run() {
try{
// Do some work here
sleep(5000);
} catch (Exception e) {
}
// start next intent
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
// Dismiss the Dialog
simpleProgressBar.setVisibility(View.INVISIBLE);
// start selected activity
}
}); {
}
}
}.start();
}
I can't approve my own solution, but if someone can confer that this is the right way to do it, or show a better way to do it I would be more than greatful.
I've been using AsyncTasks for a while however, I've recently encountered a scenario where I'm unsure of how to handle correctly. Since I thought it would be a somewhat common scenario I decided to ask the question here.
So, I'm trying to use an AsyncTask to make a simple call to sign a user in to the app. After the call completes, if it succeeds, the user should be taken to another activity. This logic is simple. The problem arrises when the user navigates away from the app before the sign in call returns. In such a case, what should I do in onPostExecute()?
What I've seen some apps do is they continue with the call anyways, as long as the activity is still around, and will launch the next activity. However this creates a weird experience where the user navigates away from the app, then several seconds later, the app just pops back up in their face. Of course, I would like to avoid doing this.
Update
Example code:
public class ExampleActivity extends Activity {
private boolean mIsPaused;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Button btnSignIn = (Button) findViewById(R.id.btn_sign_in);
btnSignIn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
new SignInTask(ExampleActivity.this).execute();
}
});
...
}
#Override
protected void onPause() {
super.onPause();
mIsPaused = true;
}
#Override
protected void onResume() {
super.onResume();
mIsPaused = false;
}
private boolean isPaused() {
return mIsPaused;
}
...
private static class SignInTask extends AsyncTask<Void, Void, SomeResult> {
private final WeakReference<ExampleActivity> mAct;
public SignInTask(ExampleActivity act) {
mAct = new WeakReference<ExampleActivity>(act);
}
#Override
protected SomeResult doInBackground(Void... params) {
return mApi.signIn(creds);
}
#Override
protected void onPostExecute(SomeResult result) {
if (result.getCode() == OK) {
ExampleActivity act = mAct.get();
if (act != null) {
if (act.isPaused()) {
// do something
} else {
startActivity(new Intent(act, NextActivity.class));
}
} else {
// do something
}
}
}
}
}
made your AsyncTask class as static inner class.
Pretty interesting problem... Going with what you've started by using booleans, you could save the response the Activity receives to the SharedPreferences in the event it is paused, or continue processing normally if it is not. If the Activity later resumes (or is recreated), check whether or not there is a saved response and handle accordingly. I was thinking something along the lines of:
import org.json.JSONObject;
import android.app.Activity;
import android.os.Bundle;
public class TaskActivity extends Activity {
private static final String KEY_RESPONSE_JSON = "returned_response";
private boolean paused = false;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// don't setup here, wait for onPostResume() to figure out what to do
}
#Override
public void onPostResume(){
super.onPostResume();
paused = false;
if(isSavedResponseAvailable()) processResponse(getSavedResponse());
else setup();
}
#Override
public void onPause(){
paused = true;
super.onPause();
}
private void setup(){
// normal setup
}
public void onReceiveResponse(JSONObject response){
if(paused) setSavedResponse(response);
else processResponse(response);
}
private void processResponse(JSONObject response){
// Continue with processing as if they never left
getSharedPreferences(this.getClass().getName(), 0).edit().clear().commit(); // Clear everything so re-entering won't parse old data
}
private boolean isSavedResponseAvailable(){
return getSavedResponse() != null;
}
private JSONObject getSavedResponse(){
try{
return new JSONObject(getSharedPreferences(this.getClass().getName(), 0).getString(KEY_RESPONSE_JSON, ""));
}
catch(Exception e){ }
return null;
}
private void setSavedResponse(JSONObject response){
getSharedPreferences(this.getClass().getName(), 0).edit().putString(KEY_RESPONSE_JSON, response.toString()).commit();
}
}
Clearly that's assuming your response from the task is JSON, but there's no reason you couldn't extend that to save the data individually and rebuild the necessary response object from the saved preference data.
As far as clean approaches go, though... I give this about a 3/10, but I can't think of anything better (well, other than making the TaskActivity abstract and forcing implementations to override setup(), processResponse(), isResponseAvailable(), getSavedResponse(), and setSavedResponse(), but that would only be mildly better for like a 4/10)
I would suggest putting a try/catch statement in the post execute - as far as I know what would happen in this situation is that you would get some kind of Window Manager exception.
What I would STRONGLY recommend, however, is stopping any async tasks (with the cancel method) on the onPause method, meaning that you won't interrupt them.
http://developer.android.com/reference/android/os/AsyncTask.html#cancel(boolean)
public final boolean cancel (boolean mayInterruptIfRunning)
Added in API level 3
Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
Calling this method will result in onCancelled(Object) being invoked on the UI thread after doInBackground(Object[]) returns. Calling this method guarantees that onPostExecute(Object) is never invoked. After invoking this method, you should check the value returned by isCancelled() periodically from doInBackground(Object[]) to finish the task as early as possible.
Parameters
mayInterruptIfRunning true if the thread executing this task should be interrupted; otherwise, in-progress tasks are allowed to complete.
Returns
false if the task could not be cancelled, typically because it has already completed normally; true otherwise
See Also
isCancelled()
onCancelled(Object)
boolean isRunning; //set it to true in onResume, and false in onStop
boolean isWaiting; // set it to true in onPostExecute, if "isRunning" is false
check in onResume whether isWaiting is true, if yes, take user to another screen.
Use the cancel() of AsynchTask class onBackPress() of Activty class
public class ExampleActivity extends Activity {
private boolean mIsPaused;
SignInTask singleTaskObj;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Button btnSignIn = (Button) findViewById(R.id.btn_sign_in);
btnSignIn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
singleTaskObj = new SignInTask(ExampleActivity.this).execute();
}
});
...
}
#Override
protected void onPause() {
super.onPause();
mIsPaused = true;
}
#Override
protected void onResume() {
super.onResume();
mIsPaused = false;
}
protected void onBackPressed()
{
singleTaskObj.cancel();
}
private boolean isPaused() {
return mIsPaused;
}
...
private static class SignInTask extends AsyncTask<Void, Void, SomeResult> {
private final WeakReference<ExampleActivity> mAct;
public SignInTask(ExampleActivity act) {
mAct = new WeakReference<ExampleActivity>(act);
}
#Override
protected SomeResult doInBackground(Void... params) {
return mApi.signIn(creds);
}
#Override
protected void onPostExecute(SomeResult result) {
if (result.getCode() == OK) {
ExampleActivity act = mAct.get();
if (act != null) {
if (act.isPaused()) {
// do something
} else {
startActivity(new Intent(act, NextActivity.class));
}
} else {
// do something
}
}
}
}
}
I am using PullToRefresh ListView from chrisbanes which I found here.
I implemented it successfully, thanks to its documentations. :)
However, I am stuck at this one point now. I am using volley to get the data from the server. It works perfectly till I added a check to see if theres no more data then simply Toast the user.
I did like below,
#Override
public void onRefresh(
PullToRefreshBase<ListView> refreshView) {
if (hasMoreData()){
//Call service when pulled to refresh
orderService();
} else{
// Call onRefreshComplete when the list has been refreshed.
toastShort("No more data to load");
orderListAdapter.notifyDataSetChanged();
mPullRefreshListView.onRefreshComplete();
}
}
The toast comes up, but I also continue seeing the Loading... message below my ListView. I thought onRefreshComplete(); should take care of it but it didn't.
How do I do this? Please help.
After banging my head for almost 3hours I was able to solve this. It was quite simple tough.
What I did was created a Handler and a Runnable which calls mPullRefreshListView.onRefreshComplete(); and checked after some time that if mPullRefreshListView was still refreshing then call the method again which closes it on the next call. :)
Code goes like this..
#Override
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
if (hasMoreData()) {
// Call service when pulled to refresh
toastShort("Last");
orderService();
} else {
// Call onRefreshComplete when the list has been
// refreshed.
toastShort("No more data to load");
upDatePull(); //this method does the trick
}
}
private void upDatePull() {
// lvOrders.setAdapter(null);
handler = new Handler();
handler.postDelayed(runnable, 1000);
}
Runnable runnable = new Runnable() {
#Override
public void run() {
mPullRefreshListView.onRefreshComplete();
if (mPullRefreshListView.isRefreshing()) {
Logger.d("xxx", "trying to hide refresh");
handler.postDelayed(this, 1000);
}
}
};
Credits to this link.
you should use onRefreshComplete(); in a separate thread like:
#Override
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
if (hasMoreData()){
//Call service when pulled to refresh
orderService();
} else{
toastShort("No more data to load");
orderListAdapter.notifyDataSetChanged();
}
new GetDataTask(refreshView).execute();
}
public class GetDataTask extends AsyncTask<Void, Void, Void> {
PullToRefreshBase<?> mRefreshedView;
public GetDataTask(PullToRefreshBase<?> refreshedView) {
mRefreshedView = refreshedView;
}
#Override
protected Void doInBackground(Void... params) {
// Do whatever You want here
return null;
}
#Override
protected void onPostExecute(Void result) {
mRefreshedView.onRefreshComplete();
super.onPostExecute(result);
}
}
In my Main Activity, I have a Thread that is doing alot of stuff, including adding some records to a database. In my second activity, which inherit from the Main Activity, I want to do a query to my database. But I need to check if the first thread in the Main Activity is finished, what I've done so far is:
public class History extends Main {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!(MainThread.isAlive())) {
getFromDatabase();
}
}
}
This is my getFromDatabase() method
pd = ProgressDialog.show(this, "Please Wait",
"Getting cases from database", false);
Thread t = new Thread(this);
t.start();
which will call this run method:
#Override
public void run() {
ArrayList<Case> c = db1.getAllCases();
Message msg = handler.obtainMessage();
msg.obj = c;
handler.sendMessage(msg);
}
private Handler handler = new Handler() {
#SuppressWarnings("unchecked")
#Override
public void handleMessage(Message m) {
pd.dismiss();
list = (ArrayList<Case>) m.obj;
tempList = getCaseNumberToTempList(list);
tempCaseList = createTempList(list);
lv.setAdapter(new CustomAdapter(History.this, list));
lv.setTextFilterEnabled(true);
}
};
But if I do so, the following line of code will crash my application, it will give a NullPointerException:
if(!(MainThread.isAlive())) {
getFromDatabase();
}
How can I be sure that that the first thread is finished with all the work before I query the database from my history activity?
You can make the Thread in the getFromDatabase() method a static class level variable, write a static get method for it in your Activity, and check for isAlive() in your child Activity.
How about simply using a semaphore variable that you modify from the thread once it has reached a certain state?
I have an activity with one listview showing items. This activity has three buttons and a menu with multiple other buttons.
When i push one button, a new thread is launched, gets datas from a web service, fills the listview. Then the listview is shown.
The only problem is that each time i push one of the buttons, i launch a thread. Depending on how long it takes for the datas to be retrieved, it happens sometimes that the listview is not filled with the good datas (it the user doesn't wait for one thread to be finished and pushes another button)
First i tried to cancel the thread but it does not work (async task or with thread.interrupt)
How can i stop previous threads so that the listview will be filled with the last datas ?
Thanks for the help :)
// Items to be displayed in the listview
private ArrayList<HashMap<String, Object>> oItems = new ArrayList<HashMap<String, Object>>();
// Launch the thread when toggle is checked
button.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {
LoadListView();
}
}
});
oMenu.setOnChildClickListener(new OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
LoadListView();
return true;
}
});
private void LoadListView() {
new Thread(ListViewRunnable).start();
}
private Runnable ListViewRunnable = new Runnable() {
public void run() {
while (GetItems() < 1) {
}
MainHandler.post(new Runnable() {
public void run() {
}
});
}
private int GetItems() {
try {
HashMap<String, Object> oItem;
//Get items from web services
List<ItemFromService> oItemsFromService = GetItemsFromWebService();
for (oItemFromService : oItemsFromService) {
oItem = new HashMap<String, Object>();
oItem.put("ID", oItemFromService.get_iID());
oItem.put("Label", oItemFromService.get_sLabel());
oItems.add(oItem);
}
MainHandler.sendEmptyMessage(GOT_ITEMS);
} catch (Exception e) {
}
return 1;
}
}
private Handler MainHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
if (msg.what == GOT_ITEMS) {
adapter.notifyDataSetChanged();
}
}
};
Until get the complete required data, don't use adaptername.notifyDataSetChanged() method So that the adapter of the ListView will not get Updated and the Old Data will get persisted.
You can use the Thread class instead of AsyncTask. Instantiate the Thread and maintain a handle to it. Use:
hThread.interrupt()
to send a thread an interrupt message, and inside the thread, test:
if (isInterrupted())
return;
to leave the thread.