Show progress of multiple website downloads during button press - android

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.)

Related

C#, Android: Build own event listener

here is the problem. I am using a custom component from someone else called a "Fancy showcase view". It focuses on buttons in my activity on highlights them with a text as a tutorial through the app. I am starting the first message, and when the user dissmises this by clicking anywhere in the activity, the next button is supposed to be highlighted. Unfortunately, the component, which otherwise is perfect, doesnt have a listener implemented like "OnDismis" of the first tutorial view so the next could start. Just putting both into code one after the other skips the second one. It also tried working with lifecycle methods, such as OnFocuseChanged() but even after the tutorial gets dismissed, this method isnt called a second time. What would you guys say is the best way to handle this? Here is what is NOT working:
try
{
new FancyShowCaseView.Builder(this) // if this crashes, we need clean rebuild
.Title(title1)
.TitleStyle(0, (int)GravityFlags.Center | (int)GravityFlags.Center)
.Build()
.Show();
}
catch (Exception e)
{
Toast.MakeText(this, "There was an error ... " + e, ToastLength.Short).Show();
}
try
{
new FancyShowCaseView.Builder(this) // if this crashes, we need clean rebuild
.Title("TEST")
//.TitleStyle(0, (int)GravityFlags.Center | (int)GravityFlags.Center)
.FocusOn(txtL)
.Build()
.Show();
}
catch (Exception e)
{
Toast.MakeText(this, "There was an error ... " + e, ToastLength.Short).Show();
}
The second one doesnt show up. There are no event handlers and I cannot make use of lifecycle methods. Click counting wouldnt work either, since the user might click on the activity while it is loading so hard coded values arent a good option either. Any ideas?
Thanks:)
Use the FancyShowCaseQueue to control the sequence.
You add individual FancyShowCaseViews to it and when you "Show()" the queue, each FancyShowCaseView happens in the order in-which you added them to the queue.
Example:
var fancyView1 = new FancyShowCaseView.Builder(this)
.Title("StackOverflow 1")
.FocusOn(button1)
.Build();
var fancyView2 = new FancyShowCaseView.Builder(this)
.Title("StackOverflow 2")
.FocusOn(button1)
.Build();
var fancyQueue = new FancyShowCaseQueue()
.Add(fancyView1)
.Add(fancyView2);
fancyQueue.Show();
I am using a Xamarin.Android binding library of FancyShowCaseView, but you can review the Java-based examples are in the sample app in the repo, ie:AnimatedActivity.java
Also you can implement the ME.Toptas.Fancyshowcase.IDismissListener interface:
public void OnDismiss(string p0)
{
//
}
public void OnSkipped(string p0)
{
//
}
And use that implementation on each of your FancyShowCaseViews:
var fancyView2 = new FancyShowCaseView.Builder(this)
.Title("StackOverflow 2")
.FocusOn(button2)
.DismissListener(this)
.Build();

Android webview - shouldOverrideUrlLoading() not called in some jsp pages

I've been developing hybrid apps for many companies with mobile websites.
And as a matter of fact, there are some websites made with using jsp.
I already had the knowledge that iframes and javascripts xhr requests will not fire webViewClient's shouldOverrideUrlLoading override function. I'm fine with that.
But today I learned that SOME actions such as:
JSP Page Redirects
Link Clicks within a JSP page
JSP/JS induced URL Loads
will not ALWAYS fire this function.
Hence, shouldOverrideUrlLoading() does not fire, when the webView is asked to load a page that it cannot load(i.e. "intent://...",) it shows an error page.
Has anyone encountered this kind of behaviour and is there any solution to work around it ?
Below is the code I'm using to invoke activities, where urls with 'intent:' protocol (which will fail because this function never gets called when above actions are performed)
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// ... omitted ...
if ( url.startsWith("intent:") ) {
Intent intent = null;
try {
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
// The following flags launch the app outside the current app
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
try {
getActivity().startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
return true;
}
}
ps. please notice that every other websites' page loads will perfectly call shouldOverrideUrlLoading().
I couldn't find any JSP related bugs on android webViews so I'm asking one.
ps. I am happily willing to provide sample websites that some gracious readers will try on.. but the website's written in Korean so I doubt it will help.
Thank you!
Your problem might not related to JSP, the real problem may be shouldOverrideUrlLoading() itself. In this case, using shouldOverrideUrlLoading() may not be a good idea, so why not try another perspective?
I've encountered many problems when using
shouldOverrideUrlLoading() loading XmlHttpRequest. At the end, I
came up with the idea using onProgressChanged() and it solved all
my problems. I've written a similar answer here.
I tried adding your code into my own webview project and tested it with some JSP sites, and looks like it always work. I also added loadUrl() after other activities are invoked, so after pressing the back button, the loading error page will not be displayed again. So try this one :
First declare a global variable to store last URL.
String strLastUrl = null;
Then override onProgressChanged(WebView view, int progress)
mWebView.setWebChromeClient(new MyWebChromeClient(){
#Override
public void onProgressChanged(WebView view, int progress) {
if (progress == 100) {
//A fully loaded url will come here
String StrNewUrl = view.getUrl();
if(TextUtils.equals(StrNewUrl,strLastUrl)){
//same page was reloaded, not doing anything
}else{
String strOldUrl = null;
//save old url to variable strOldUrl before overwriting it
strOldURL = strLastUrl;
//a new page was loaded,overwrite this new url to variable
strLastUrl = StrNewUrl;
if ( strLastUrl.startsWith("intent:") ) {
Log.d("TAG", "intent triggered");
Intent intent = null;
try {
intent = Intent.parseUri(strLastUrl, Intent.URI_INTENT_SCHEME);
// The following flags launch the app outside the current app
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
//reload the page before invoking other activities
view.loadUrl(strOldURL);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
}
super.onProgressChanged(view, progress);
}
});

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");

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();
}

Problem with Smack in Facebook chat app for Android: Connection.getRoster().getEntries() is always empty

I'm trying to implement a simple Facebook chat application on Android. It lets the user login, after which the app opens a new ListActivity enumerating the user's online contacts, and upon clicking on an item, another Activity opens where their conversation will take place.
The first screen where the user logs in is called MainActivity. It contains two EditTexts: one for the FB username, and the other for password. There are also two buttons: Clear, which empties the EditTexts, and OK, which upon click, executes the following AsyncTask via new LoginTask().execute():
class LoginTask extends AsyncTask<Void, Void, Void> {
private ProgressDialog loading;
#Override
protected void onPreExecute() {
loading = ProgressDialog.show(MainActivity.this, "", "Loading...");
}
#Override
protected Void doInBackground(Void... params) {
// setup XMPP connection
ConnectionConfiguration config = new ConnectionConfiguration(
"chat.facebook.com", 5222, "chat.facebook.com");
config.setDebuggerEnabled(true);
config.setSASLAuthenticationEnabled(true); // TRUE for fb
conn = new XMPPConnection(config);
try {
// attempt login
conn.connect();
SASLAuthentication.supportSASLMechanism("PLAIN", 0);
conn.login(login_field.getText().toString(),
pwd_field.getText().toString(), "");
Log.d("TRACE", "isAuthenticated? " + conn.isAuthenticated());
// list online contacts
Roster roster = conn.getRoster();
Collection<RosterEntry> entries = roster.getEntries();
Log.d("TRACE", "entries.size()=" + entries.size());
for (RosterEntry e : entries) {
Log.d("PRESENCE", e.getUser() + "=" + roster.getPresence(e.getUser()).isAvailable());
if (roster.getPresence(e.getUser()).isAvailable()) {
HashMap<String, String> contact = new HashMap<String, String>();
contact.put(NAME_KEY, e.getName());
contact.put(USERJID_KEY, e.getUser());
Log.d("ADD", "NAME_KEY=" + e.getName() + " USERJID_KEY=" + e.getUser());
contacts.add(contact);
}
}
Collections.sort(contacts, new ContactComparator()); // sort alphabetically
Log.d("TRACE", "MainActivity.contacts.size(): " + contacts.size());
Intent in = new Intent(MainActivity.this, ContactList.class);
startActivity(in);
} catch (Exception e) {
Log.d("EXCEPTION", e.toString());
}
return null;
}
#Override
protected void onPostExecute(Void result) {
loading.dismiss();
}
}
My problem is that whenever the second screen is opened, the ListView often doesn't show anythying, BUT IT SOMETIMES DOES. I decided to add log messages to the part where I'm retrieving the contacts via conn.getRoster() and I found out that most of the time, a call to conn.getRoster().getEntries.size() returns a zero, but when I actually login to Facebook via browser, I can see that I do have online contacts. What I don't understand, though, is why it sometimes returns the correct number, but only once in almost every billion times I run the application.
Somebody help, please? This app's due in four hours and I don't know what to do anymore since there seems to be nothing wrong with my code.
I found the answer to this question the day after I posted it here. Apparently, when conn.login() is called, the user becomes authenticated and the Facebook server starts sending the roster items. The reason why nothing appears in the second screen most of the time is that the code reaches the call to startActivity() EVEN BEFORE the Facebook server is done sending the packets containing the roster items. Sometimes, though, the server is fast enough to send the packets but startActivity() is called and adding to the ArrayList becomes interrupted, resulting to an incomplete ListView of your online contacts. A workaround I did was to call Thread.sleep() after conn.login() and create a 5- to 10-second delay so that the server would have enough time to send all the packets.
That worked only for a while but when I has the added task of retrieving even the photos of my contacts (using VCard), even a 30-second delay wasn't enough. You should find a way then to determine when the server is done sending packets and the VCards and proceed with adding to the ArrayList only when you get the signal.
Unfortunately, looking at the Smack documentation, there doesn't seem to be a way to do that. But that's because my goal was, overall, wrong. What I was trying to do is this:
Log in to Facebook.
Get all online contacts.
Put them in an ArrayList.
Open a ListActivity.
Display the ArrayList contents in the ListActivity.
When it should be doing this:
Log in to Facebook.
Open a ListActivity.
Get ALL your contacts.
Dynamically update the ListView depending on your contacts' presence (still haven't done this part).
In short, the Smack API has been designed to handle real-time events, and because it's an instant messaging library, I don't think it should have been designed otherwise. What I was trying to do in the first example was rather static.
Maybe you also consifer roster.reloadAndAwait()
public void getRoaster(final Callback<Collection<RosterEntry>> callback) {
final Roster roster = Roster.getInstanceFor(connection);
if (!roster.isLoaded())
try {
roster.reloadAndWait();
} catch (SmackException.NotLoggedInException | SmackException.NotConnectedException | InterruptedException e) {
e.printStackTrace();
}
Collection<RosterEntry> entries = roster.getEntries();
for (RosterEntry entry : entries) {
android.util.Log.d(AppConstant.PUBLIC_TAG, entry.getName());
}
}

Categories

Resources