Properly running code in android main thread - android

So started programming android apps, mostly open source and tutorials to learn the language.
I have a problem now where if a user has signed in once, that he never has to sign in again. I used SharedPref for this after reading alot of answers on here, but the app is jumping frames due to this..
heres my code.
SharedPreferences prefs = getPreferences(Login.this);
prefs = PreferenceManager.getDefaultSharedPreferences(Login.this);
String remUsername = prefs.getString("username", null);
String remPassword = prefs.getString("password", null);
if (remUsername != null && remPassword != null)
{
String result = null;
try {
result = imService.authenticateUser(
remUsername.toString(),
remPassword.toString());
} catch (UnsupportedEncodingException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
Intent i = new Intent(Login.this, FriendList.class);
startActivity(i);
Login.this.finish();
}

The jumping frames are probably due to a resource-intensive operation (such as an HTTP request) being executed by the main thread. You must perform such operations in a separate thread, while leaving only UI and non-intensive operations for the main thread (that's why it is called the "UI" thread).
Read the basics here. Then you can implement it using an AsyncTask, for example. However for simplicity it is suggested to use a specialized library such as Volley or android-async-http.

Related

Show progress of multiple website downloads during button press

I need to retrieve some data from roughly 50 different URLS with the press of a button.
The code goes through them one at a time, and although it doesn't take that long, it will take around 20 seconds, and I have all this code running inside of a button.
I was hoping I could update a TextView or something to say "Loading page 1 of 50" then "Loading page 2 of 50" etc, in between accessing the different websites.
The code below works, just the button gets stuck down for an unknown amount of time, and I want the user to have some indication of how far along the loading is doing.
btnGetData.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//regionRetrieve 1 page of auction data, so we know how many future pages to retrieve. -P
String auctionURL = "https://api.hypixel.net/skyblock/auctions?page=";
String firstPage = null;
try {
firstPage = new RetrieveData().execute(auctionURL + "0").get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
auctionInfo = new JSONObject(firstPage);
} catch (JSONException e) {
e.printStackTrace();
}
//endregion
//regionRetrieve the remaining pages
int totalPages = 0;
try {
totalPages = auctionInfo.getInt("totalPages");
} catch (JSONException e) {
e.printStackTrace();
}
//Place to put the rest of the pages
ArrayList<String> remainingPages = new ArrayList<>();
//Starts at 1, because we already retrieved the 0 page as the first page.
//Also, I checked, and you do need to retrieve the 52nd page if there are say, 52 pages.
for (int i = 1; i <= totalPages; ++i) {
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//ADD SOME KIND OF NOTIFICATION HERE
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
String newPage = null;
try {
newPage = new RetrieveData().execute(auctionURL + i).get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
remainingPages.add(newPage);
}
Toast.makeText(getApplicationContext(),"All data received.",Toast.LENGTH_SHORT).show();
tvLoading.setVisibility(View.GONE);
//endregion
//stuff below this point is irrelevant to the question
}
});
I tried wrapping all of the code above inside an AsyncTask, and utilizing the "onProgressUpdate", but that did not work. Furthermore, I heard that by now, AsyncTask has been deprecated, and that there are better ways to do it.
I also tried using Toast messages, but they all show up at the end, which kind of defeats the purpose.
I even put the Toast messages in the AsyncTasks that I call in order to get the data, but that didn't work either. (The RetrieveData() is an AsyncTask that reads all the information from the URLS, and returns it as a String. I know you aren't supposed to use get, but in this case it is important the data arrives in the correct order. Unless, after retrieving the first one, and knowing how many pages there are, I could launch 50 threads at the same time to retrieve the data? But still, you are limited by your internet connection, and the user is still sitting there confused.)
Is there a proper way to do this?
Any help would be appreciated!
Instead of putting all the code in the button, make the button launch another activity, and put the data retrieval code in that activity.
Launch the code inside of a thread, and from within the thread, update UI elements to inform the user of the progress.
After the data is retrieved, store it, then start an Intent to go back to the previous activity, access the data where you stored it, and use it for whatever you needed to do.
(I know I answered my own question, but I figured it out a few hours later and nobody had responded up until now, hopefully this helps someone in the future.)

Log.d() stops working after calling Integer.parseInt(INVALID_INT)

private void someFunction(String html) {
Document doc = Jsoup.parse(html);
Element vol = doc.select(CSS_PATH).first();
Log.d("Vol", vol.text());
Log.d(TAG, "This gets printed");
Float.parseFloat(vol.text());
Log.d(TAG, "BUT THIS ONE NOT");
}
These two objects are elements of the Jsoup library.
Document doc = Jsoup.parse(html);
Element vol = doc.select(PATH.PEAK_DOWN_VOL).first();
vol.text() returns a String containing a float value.
Log.d("Vol", vol.text()); // logs something similar to 'Vol: 12.4'
But after calling Float.parseFloat(peakDownVol.text());, Log.d in this class will stop entirely. But Log.d's in the Activity class seems to work just fine.
If I change Float.parseFloat(vol.text()); to Integer.parseInt(vol.text());, it logs the subsequent statements without any problem.
I have lot of number parsing to do. So I'd like to know why is this happening exactly?
If you want the rest of the method to run regardless, you should catch the potential exception locally, so that the program can continue executing the rest of the method and not bubble back up the call stack until it finds an exception handler or crashes for the lack of one:
Log.d(TAG, "This gets printed");
try {
Float.parseFloat(vol.text());
} catch (NumberFormatException e) {
e.printStackTrace(); //leave evidence in the log
}
Log.d(TAG, "So does this");

Android Parse SDK issue

I'm using parse to store my data and do a lot of queries while using my program.
The issue is that after about +/-20 similar queries, parse findInBackground() or getFirstInBackground() doesn't return a callback and app stuck at that possition.
My query code:
ParseQuery<OptionCodeDTO> mQuery;
mQuery = ParseQuery.getQuery(OptionCodeDTO.class);
mQuery.whereEqualTo("code", prCode);
mQuery.getFirstInBackground(new GetCallback<OptionCodeDTO>() {
#Override
public void done(OptionCodeDTO optionCodeDTO, ParseException e) {
if (isVisible()) {
if (e == null) {
OptionCode opCode = new OptionCode(optionCodeDTO);
mCodes.push(opCode);
printCodes();
prDescrLayout.setVisibility(View.VISIBLE);
prDescProgress.setVisibility(View.GONE);
mPRLable.setVisibility(View.GONE);
} else {
if (e.getCode() == ParseException.CONNECTION_FAILED) {
mPrDescr.setText(R.string.dtc_lookup_check_network);
} else if (e.getCode() == ParseException.OBJECT_NOT_FOUND) {
mPrDescr.setText(R.string.pr_lookup_code_not_found);
} else {
mPrDescr.setText(R.string.dtc_lookup_other_problems);
}
prDescrLayout.setVisibility(View.VISIBLE);
prDescProgress.setVisibility(View.GONE);
}
}
}
});
First of all, if your app ANRs (application not responding) because of something from UI thread that relies on background threads, that is incorrect architecture.
Probably you have to optimize your app's interaction with Parse. Generally it is a bad practice to make lots of saveInBackground, for example from inside a loop. You can add objects, those need to be saved, to a list and then use ParseObject.saveAllInBackground(objectList)
Also an idea to optimize is to use local storage - android's built in SQLite. For example if your app relies on something being saved to Parse, the logic is like this:
When saving object first you save to local DB and run a saveInBackground method.
When fetching objects you first fetch from your local DB and then run a getInBackground method, which inside a callback persists the information to your local DB.
This way you will make your app usable without internet connection.

Android not waiting for DB response before finishing statement

I have an interesting problem that I've never run into in programming before. I have an onClickListener that does a lot of username and password checks (makes sure the username is proper length, not taken, etc). I'm using MobDB, and I was using a conditional statement that would return a row if the username already existed. The problem is that the Listener skips the DB and goes to the final check that, if everything works, posts a new username and password to my DB. How can I make it wait for a response from the DB before skipping to the last check?
Here is the relevant code:
usernamecheck3 = true;
MobDB.getInstance().execute(APP_KEY, null, rd, null, false, new MobDBResponseListener() {
#Override public void mobDBSuccessResponse() {
usernamecheck3 = false;
Log.e("mobdbSuccess:", "success");
}
#Override public void mobDBResponse(Vector<HashMap<String, Object[]>> row) {
}
#Override public void mobDBResponse(String jsonObj) {
/*Log.e("mobdbSuccess:", "jsonObj");
Log.e("mobdbSuccess:", jsonObj);
JSONObject mainObject;
try {
mainObject = new JSONObject(jsonObj);
// need to parse the json object.
} catch (JSONException e1) {
e1.printStackTrace();
} */
}
#Override public void mobDBFileResponse(String fileName, byte[] fileData) {
//get file name with extension and file byte array
}
#Override public void mobDBErrorResponse(Integer errValue, String errMsg) {
usernamecheck3 = false;
Log.e("doesnt", "work");
}
});
if(usernamecheck3 == false){
Toast.makeText(getApplicationContext(), "Username is taken, please choose another", Toast.LENGTH_SHORT).show();
}
Basically the check always returns true, and then logcat will say mobdbSuccess: success, which should have set the Bool to false.
Thanks.
MobDBResponseListener is executing on a different thread. What happens here is that the processing is split, while a thread is doing the query, the main thread on which you added the listener, skips right ahead to the validation. Your best bet is to place the validation inside the MobDBResponseListener, on the mobDBResponse method.
Try to debug your code and calls, the Listener may be using an async task. If so, you may do anything you please from the response method, as it will be executing in the main thread again. Otherwise, you should look at solutions that handle threaded execution like Handlers

Robotium testcase

I have a Robotium test case and It should be like
UI Application starts uploading data to server
User swaps to some other application on the device
uploading operation is running at the background
user comes to the main UI application
How to keep track of uploading the data at background? can we use multithreading for this?
try {
mSolo.clickOnMenuItem("UPLOAD");
mSolo.sleep(1000);
Instrumentation inst = new Instrumentation();
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
mSolo.waitForActivity(Settings.ACTION_APPLICATION_SETTINGS);
mSolo.goBack();
mSolo.assertCurrentActivity("main",
UIActivity.class);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Is this code correct? If not suggest me a modification or correct code.
Help is always appreciated,
Thanks
You cannot interact with other applications unless you signed the third party application with your own key (see black box testing).
But what you can is pressing Home, Back and starting Intents. The following code is untested but hopefully gives you an idea:
try {
mSolo.clickOnMenuItem("UPLOAD"); // start upload
mSolo.sleep(1000);
mSolo.goBack(); // leave app
...
Intent intent = new Intent("com.company.another.app.SomeActivity");
startActivity(inent); // start another app
...
// option one: get app context and use it for access on preferences, etc.
Context context = this.getInstrumentation().getTargetContext().getApplicationContext();
// option two: wait for logs that you write while uploading
solo.waitForLogMessage("Upload completed");
...
Intent intent = new Intent("com.myapp.MyMainUIActivity");
startActivity(inent); // start own Main Activity again
...
} catch (Exception e) {
e.printStackTrace();
}
So you could use log messages, preferences or any other methods of your app in order to follow up the upload progress.
You cannot leave your application and run it again with Instrumentation. This part is not correct:
Instrumentation inst = new Instrumentation();
inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
Why do you create new instrumentation? You can simply run:
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
by the way, solo.goBack() just does it, so it doesn't make sense to call it with instrumentation. I would simply rewrite it to:
try {
mSolo.clickOnMenuItem("UPLOAD");
mSolo.sleep(1000);
mSolo.goBack();
assertTrue(mSolo.waitForActivity(Settings.ACTION_APPLICATION_SETTINGS));
mSolo.goBack();
mSolo.assertCurrentActivity("main", UIActivity.class);
} catch (Exception e) {
e.printStackTrace();
}

Categories

Resources