I have a ListView that onLongClick it calls a method that is supposed to go out to a website, pull a jsonArray from it and then return information that is pulled from the array. However, when it calls the HttpURLConnection.connect() method it fails and goes to the catch block. When I use the getMessage() message on the exception it only returns Null. This is the second time in this program that I've connected to a URL in this same way and it works the first time perfectly. What could be causing this issue?
Here is the code for when the method is called:
list.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
public boolean onItemLongClick(AdapterView<?> a, View v, int pos, long id) {
String trainNum = list.getItemAtPosition(pos).toString();
String info = "hello";
try {
info = getCurrentTrainInfo(trainNum);
}catch(Exception e){
info = e.getMessage();
if(info == null)
info = "info is null";
tv.setText(info);
}
Toast.makeText(getApplicationContext(), info, Toast.LENGTH_LONG).show();
return true;
}
}
);
And here is the method getCurrentTrainInfo that is called in the try block above:
public String getCurrentTrainInfo(String num) throws IOException{
String sURL = "http://www3.septa.org/hackathon/RRSchedules/" + num;
URL url = new URL(sURL);
HttpURLConnection request2 = (HttpURLConnection) url.openConnection();
request2.connect();
JsonParser jp = new JsonParser();
JsonElement root = jp.parse(new InputStreamReader((InputStream) request.getContent()));
JsonArray rootArr = root.getAsJsonArray();
int i = 0;
String acTime = "";
String station = rootArr.get(i).getAsJsonObject().get("station").getAsString();
String schTime = rootArr.get(i).getAsJsonObject().get("sched_tm").getAsString();
String esTime = rootArr.get(i).getAsJsonObject().get("est_tm").getAsString();
tv.setText(station);
String info = "Current Station: " + station + "\nScheduled leave time: " + schTime + "\nEstimated leave time: " + esTime;*/
return info;
}
Is there anything I can do to fix this problem?
I see your request is being made in the UI thread, you mentioned that in another moment used this same way and it worked, I believe this may have happened when you ran your application on a device/emulator with a version of Android prior to 3.0.
Within an Android application you should avoid performing long
running operations on the user interface thread. This includes file
and network access. StrictMode allows to setup policies in your
application to avoid doing incorrect things. As of Android 3.0
(Honeycomb) StrictMode is configured to crash with a
NetworkOnMainThreadException exception, if network is accessed in
the user interface thread.
You can create a AsyncTasks class and move the call request to it.
Related
Aim
In a fragment, I have a search bar which looks for online news about what the user typed. I would want to display these news (title + description + date of publication + ... etc.) in the GUI, as vertical blocks.
Implementation
Explanations
In the fragment, within the search event handling, I instanciated an asynchronous task and execute it with the good URL REST API I use to do the search.
In the asynchronous task, I make use of this REST API (thanks to the URL and some required parameters as an authorization key, etc.). When my asynchronous task gets answered, it must update the fragment's GUI (i.e.: it must vertically stack GUI blocks containing the titles, descriptions, etc. of the got news).
Sources
You will find sources in the last part of this question.
My question
In the asynchronous task (more precisely: in its function that is executed after having got the answer), I don't know how to get the calling fragment. How to do this?
Sources
Fragment part
private void getAndDisplayNewsForThisKeywords(CharSequence keywords) {
keywords = Normalizer.normalize(keywords, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "");
new NetworkUseWorldNews().execute("https://api.currentsapi.services/v1/search?keyword=" + keywords + "&language=en&country=US");
}
Asynchronous task part
public class NetworkUseWorldNews extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String[] urls) {
StringBuilder string_builder = new StringBuilder();
try {
URL url = new URL(urls[0]);
HttpsURLConnection https_url_connection = (HttpsURLConnection) url.openConnection();
https_url
_connection.setRequestMethod("GET");
https_url_connection.setDoOutput(false);
https_url_connection.setUseCaches(false);
https_url_connection.addRequestProperty("Authorization", "XXX");
InputStream input_stream = https_url_connection.getInputStream();
BufferedReader buffered_reader = new BufferedReader(new InputStreamReader(input_stream));
String line;
while((line = buffered_reader.readLine()) != null) {
string_builder.append(line);
}
buffered_reader.close();
} catch (IOException e) {
e.printStackTrace();
}
return string_builder.toString();
}
#Override
protected void onPostExecute(String result) {
try {
JSONObject news_response_http_call = new JSONObject(result);
switch(news_response_http_call.getString("status")) {
case "ok":
JSONArray news = news_response_http_call.getJSONArray("news");
for(int i = 0; i < news.length(); i++) {
JSONObject a_news = news.getJSONObject(i);
String title = a_news.getString("title");
String description = a_news.getString("description");
String date_of_publication = a_news.getString("published");
String url = a_news.getString("url");
String image = a_news.getString("image");
System.out.println(title + ": " + date_of_publication + "\n" + image + "\n" + url + "\n" + description);
WorldNewsFragment world_news_fragment = ...;
}
break;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
If I am right, you want to update View of your caller Fragment. if FragmentA called service then FragmentA should be update.
However the approach you are asking is wrong. Instead of getting caller Fragment in your AsyncTask response. You should do it with Callback.
So now you will need to pass callback in AsyncTask. So instead of posting full code, here are already answers with this problem.
Finally your calling syntax will look like.
NetworkUseWorldNews task = new NetworkUseWorldNews(new OnResponseListener() {
#Override
public void onResponse(String result) {
// Either get raw response, or get response model
}
});
task.execute();
Actually I am still very unclear about your question. Let me know in comments if you have more queries.
Must checkout
Retrofit or Volley for calling Rest APIs
Gson for parsing JSON response automatically to models
So my goal is to load a random wikipedia page, get the title from it, and then use the wikipedia api to get the correct title to return for display (titles with special characters need "translated" to be able to display them correctly.) My problem comes when I use my JSONRequest class (Async) to try to execute the api url and create a JSON object. When it tries to execute, it freezes and doesn't go any further (but does not crash.) It isn't a problem with the URL, it is valid and works on desktop and mobile. This method was also used in another non-async class so I know it works there. My guess is that it is an async issue. This may be a stupid question with a simple answer, but any help is greatly appreciated!
public class getRandom extends AsyncTask<String, Void, String> {
private static final String REQUEST_METHOD = "GET";
private static final int READ_TIMEOUT = 15000;
private static final int CONNECTION_TIMEOUT = 15000;
String result, rawTitle;
// Gets random wiki page and returns text to be loaded into search bar
#Override
protected String doInBackground(String... params){
String randomUrl = "https://en.wikipedia.org/wiki/Special:Random";
String titleApiUrl = "https://en.wikipedia.org/w/api.php?action=query&format=json&titles=";
// Get random title
try {
URL url = new URL(randomUrl);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
//Set method and timeouts
con.setRequestMethod(REQUEST_METHOD);
con.setReadTimeout(READ_TIMEOUT);
con.setConnectTimeout(CONNECTION_TIMEOUT);
// Get status
int status = con.getResponseCode();
// Check for move or redirect and update url
if (status == HttpURLConnection.HTTP_MOVED_TEMP
|| status == HttpURLConnection.HTTP_MOVED_PERM) {
String location = con.getHeaderField("Location");
URL newUrl = new URL(location);
con = (HttpURLConnection) newUrl.openConnection();
}
// Get name of page
rawTitle = con.toString();
int temp = rawTitle.indexOf("wiki/") + 5;
rawTitle = rawTitle.substring(temp);
con.disconnect();
}
catch(IOException e){
e.printStackTrace();
rawTitle = "Sailboats"; // Very random, totally not hard coded result.
}
// Ensure correct title format (use wiki api)
try{
// Get json from api
JSONRequest wikiRequest = new JSONRequest();
String wikiApiJsonString = wikiRequest.execute(titleApiUrl + rawTitle).get();
// Create json object with returned string
JSONObject jsonObj = new JSONObject(wikiApiJsonString);
// Get correct title from json
result = jsonObj.getString("title");
}
catch(ExecutionException | InterruptedException | JSONException e){
e.printStackTrace();
result = "Sailboats"; // Very random, totally not hard coded result.
}
return result;
}
protected void onPostExecute(String result){
super.onPostExecute(result);
}
}
By default, async tasks all run on the same thread. So it won't actually start a second task until yours finishes. Your options are:
1)Call executeOnExecutor instead of execute and tell it to use a new thread.
2)Architect your code such that you can make the request on this thread directly. You're already on a thread, no reason to launch a new one.
3)Write your code such as you don't need to call .get().
After getting the following code to work reliably for a month or so, it stopped working reliably a couple of days ago. About half the time it returns a properly translated string and the other half of the time it returns one of the following two messages:
java.io.FileNotFoundException:
https://api.cognitive.microsoft.com/sts/v1.0/issueToken
java.net.UnknownHostException: Unable to resolve host
"api.microsofttranslator.com": No address associated with hostname
The timing of this problem's beginning coincided with the expiration of my free azure cognitive services account however I migrated to a pay-as-you-go account yesterday and the problem continues.
Why is this happening?
static class translateMessageX extends AsyncTask<String, Void, String>
{
//input string array of 3 items
//[0]is the message to be translated
//[1]is the from language i.e. "english"
//[2]is the to language i.e. "spanish"
//[3]"echo" or "received"
String retString;
String inString = null;
String messageType = null;
String URLHolder = ""; //hold the URL here while we are translating the text
#Override
protected String doInBackground(String... params)
{
inString = params[0];
String from = params[1];
String to = params[2];
messageType = params[3];
int urlStart = inString.indexOf("http");
if (!(urlStart == -1))
{
URLHolder = inString.substring(urlStart);
inString = inString.substring(0, urlStart -1);
}
else
{
URLHolder = "";
}
Integer mesChars = params[0].length();
Integer tCharsLeft = GlobalStuff.getTranslationsFromSP();
if (tCharsLeft > 0)
{
if (tCharsLeft < mesChars) //we charge for both 'echo' and 'received' translations
{
GlobalStuff.updateTranslationInventory(tCharsLeft * -1);
}
else
{
GlobalStuff.updateTranslationInventory(mesChars * -1);
}
GlobalStuff.notifyListeners(this, "#uui", "notused", "notused" );
try
{
Language fromLang = GlobalStuff.getLang(from);
Language toLang = GlobalStuff.getLang(to);
//retString = Translate.execute(inString, fromLang, toLang);
//String debugstr = "look at retStr";
String authenticationUrl = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken";
HttpsURLConnection authConn = (HttpsURLConnection) new URL(authenticationUrl).openConnection();
authConn.setRequestMethod("POST");
authConn.setDoOutput(true);
authConn.setRequestProperty("Ocp-Apim-Subscription-Key", GlobalStuff.translateKey);
IOUtils.write("", authConn.getOutputStream(), "UTF-8");
String token = IOUtils.toString(authConn.getInputStream(), "UTF-8");
System.out.println(token);
// Using the access token to build the appid for the request url
String appId = URLEncoder.encode("Bearer "+token, "UTF-8");
String text = URLEncoder.encode(inString, "UTF-8");
String translatorTextApiUrl = String.format("https://api.microsofttranslator.com/v2/http.svc/Translate?appid=%s&text=%s&from=%s&to=%s", appId, text, fromLang, toLang);
HttpsURLConnection translateConn = (HttpsURLConnection) new URL(translatorTextApiUrl).openConnection();
translateConn.setRequestMethod("GET");
translateConn.setRequestProperty("Accept", "application/xml");
retString = IOUtils.toString(translateConn.getInputStream(), "UTF-8");
String debug = "look at retString";
}
catch (Exception e)
{
retString = e.toString();
}
}
else
{
retString = "OUT OF TRANSLATION CREDITS - " + inString;
}
return retString;
}
#Override
protected void onPostExecute(String result)
{
//rest of logic should be here??
String debug = "look at result";
String answer = extractTranslation(result);
.. . . .
Host not found looks like a simple connectivity error. These hosts do exist.
You can void the call to the token service by passing the key in the call to api.microsofttranslator.com directly:
https://cognitive.uservoice.com/knowledgebase/articles/1815385-api-translator-text-speech-using-the-api-key
That fixes one of the host not found problems, but not the other.
I would recommend though to not embed the key in the client application. It is safer to call the translator service from your own proxy service, where the proxy is able to safely identify your client as your client.
Alrighty, so I understand that this general question has been asked numerous times here, but I have yet to find an answer that makes sense to me. Almost every answer I've seen just says some blurb like, "hey, just throw this in your method and you're good", but I'm not seeing full examples, and what I've tried is not working either.
Here's the error I receive:
[mono] android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
So, simply put, I have an activity that grabs some information from a web service and then throws the web service results into a couple of TextViews. Could someone please help me figure out where and how I need to use the RunOnUiThread()? Here's the code:
using Android.App;
using Android.OS;
using System;
using System.Web;
using System.Net;
using System.IO;
using Newtonsoft.Json;
using Android.Widget;
namespace DispatchIntranet
{
[Activity (Label = "#string/Summary")]
public class SummaryActivity : Activity
{
private static readonly Log LOG = new Log(typeof(SummaryActivity));
private TextView summaryTotalRegularLabel;
private TextView summaryTotalRollover;
private TextView summaryScheduledLabel;
private TextView summaryRemainingRegular;
private string url;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// SET THE LAYOUT TO BE THE SUMMARY LAYOUT
SetContentView(Resource.Layout.Summary);
// INITIALIZE CLASS MEMBERS
init();
if (LOG.isInfoEnabled())
{
LOG.info("Making call to rest endpoint . . .");
if (LOG.isDebugEnabled())
{
LOG.debug("url: " + this.url);
}
}
try
{
// BUILD REQUEST FROM URL
HttpWebRequest httpReq = (HttpWebRequest)HttpWebRequest.Create(new Uri(this.url));
// SET METHOD TO 'GET'
httpReq.Method = GetString(Resource.String.web_service_method_get);
// ASK FOR JSON RESPONSE
httpReq.Accept = GetString(Resource.String.web_service_method_accept);
// INVOKE ASYNCHRONOUS WEB SERVICE
httpReq.BeginGetResponse((ar) => {
HttpWebRequest request = (HttpWebRequest)ar.AsyncState;
using (HttpWebResponse response = (HttpWebResponse)request.EndGetResponse (ar))
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
// PUT RESPONSE INTO STRING
string content = reader.ReadToEnd();
// CONVERT STRING TO DYNAMIC JSON OBJECT
var json = JsonConvert.DeserializeObject<dynamic>(content);
if (LOG.isDebugEnabled())
{
LOG.debug("content: " + content);
LOG.debug("json: " + json);
LOG.debug("TOTAL_REGULAR_PTO_HOURS: " + json.d[0].TOTAL_REGULAR_PTO_HOURS);
}
// ** THIS IS WHAT WILL NOT WORK **
this.summaryTotalRegularLabel.Text = json.d[0].TOTAL_REGULAR_PTO_HOURS;
this.summaryTotalRollover.Text = json.d[0].TOTAL_ROLLOVER_PTO_HOURS;
this.summaryScheduledLabel.Text = json.d[0].TOTAL_USED_PTO_HOURS;
this.summaryRemainingRegular.Text = json.d[0].TOTAL_REMAINING_PTO_HOURS;
}
}
}, httpReq);
}
catch (Exception e)
{
LOG.error("An exception occurred while attempting to call REST web service!", e);
}
}
private void init()
{
// GET GUID FROM PREVIOUS INTENT AND DETERMINE CURRENT YEAR
string guid = Intent.GetStringExtra("guid");
int year = DateTime.Now.Year;
// BUILD URL
this.url = GetString(Resource.String.web_service_url)
+ GetString(Resource.String.ws_get_pto_summary)
+ "?" + "guid='" + HttpUtility.UrlEncode(guid) + "'"
+ "&" + "year=" + HttpUtility.UrlEncode(year.ToString());
// GET THE SUMMARY LABELS
this.summaryTotalRegularLabel = FindViewById<TextView>(Resource.Id.SummaryTotalRegular);
this.summaryTotalRollover = FindViewById<TextView>(Resource.Id.summaryTotalRollover);
this.summaryScheduledLabel = FindViewById<TextView>(Resource.Id.summaryScheduledLabel);
this.summaryRemainingRegular = FindViewById<TextView>(Resource.Id.SummaryRemainingRegular);
}
}
}
When you make a web service call, HttpWebRequest creates a new thread to run the operation on. This is done to keep your user interface from locking up or skip frames. Once your web service call is complete, you need to go back to the UI Thread to update the UI components that live on that thread. You can do that a couple of different ways.
First, you can wrap your code in an anonymous function call like so:
RunOnUiThread(()=>{
this.summaryTotalRegularLabel.Text = json.d[0].TOTAL_REGULAR_PTO_HOURS;
this.summaryTotalRollover.Text = json.d[0].TOTAL_ROLLOVER_PTO_HOURS;
this.summaryScheduledLabel.Text = json.d[0].TOTAL_USED_PTO_HOURS;
this.summaryRemainingRegular.Text = json.d[0].TOTAL_REMAINING_PTO_HOURS;
});
Or you can call a function via RunOnUiThread (jsonPayload is a field on the class):
jsonPayload = json;
RunOnUiThread(UpdateTextViews);
...
void UpdateTextViews()
{
this.summaryTotalRegularLabel.Text = jsonPayload.d[0].TOTAL_REGULAR_PTO_HOURS;
this.summaryTotalRollover.Text = jsonPayload.d[0].TOTAL_ROLLOVER_PTO_HOURS;
this.summaryScheduledLabel.Text = jsonPayload.d[0].TOTAL_USED_PTO_HOURS;
this.summaryRemainingRegular.Text = jsonPayload.d[0].TOTAL_REMAINING_PTO_HOURS;
}
I have a implemented a ContentProvider to serve up a list of geographical locations from an underlying SQLite database.
These locations are actually an output from a model and are available online in the form of a simple JSON string; I don't want to keep downloading them every time the app is started, so I want to store them locally in a database and update them at pre-defined intervals (once a day for instance).
My question is, where should I implement the downloading and parsing code? Should it be part of my ContentProvider implementation? Or part of the SQLiteOpenHelper implementation?
I started to implement it as a public function called updateSiteList in my ContentProvider, but I don't know how to actually call it (the ContentProvider is usually acessed indirectly via CursorLoader)!
I am bit stuck as to how to progress!
Can anyone point me in the right direction?
Thanks!
There are a couple of ways to achieve this.
The two most common ways I use are :-
1) a custom sync adapter.
2) An intent service.
With option 1 you get the benefit of the Android system handling network connection problems and is the recommended approach by the android Developers
With option 2 you get more control over when the data gets downloaded which may or may not be the best time for the user or the Android System.
Either way the solution is the same. At some point in time you will be making, in a background service, an HTTP get request to a url. when your request completes you will need to heck the status of the response and if appropriate you would then make a call to the content provider to wither insert or update your data accordingly. whichever approach you take this part will be the same.
Some further reading for you.
https://sites.google.com/site/andsamples/concept-of-syncadapter-androidcontentabstractthreadedsyncadapter
Be sure to watch that Google I/O video
Regardless of the approach you take, the code to download json and insert to your content provider could look something like this in an IntentService called from either your sync adapter or from somewhere within your app if not using a sync adapter.
public class ServiceInitialiseData extends IntentService {
static final String TAG = "ServiceSyncData";
//ACTION should include application package convention, just to show that this can
//be any string
public static final String SYNC_COMPLETED_ACTION="com.pjmobile.games.fantasyf1.SyncCompleted";
public ServiceInitialiseData() {
super("InitialiseDataService");
}
#Override
protected void onHandleIntent(Intent intent) {
String sJson;
try {
sJson = downloadFromServer("Some parsed url");
int i, x;
boolean res = false;
List <ContentValues> bulkValues = new ArrayList <ContentValues>();
JSONArray entries;
try {
entries = new JSONArray(sJson);
ContentValues cvEntity = null;
JSONObject entity;
x = entries.length();
for (i=0;i<x;i++){
entity = entries.getJSONObject(i).getJSONObject("some_json_key");
bulkValues.add(cvEntity);
}
}
int qCount = getContentResolver().bulkInsert(uri,
(ContentValues[])bulkValues.toArray(new
ContentValues[bulkValues.size()]));
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
private String downloadFromServer(String url) throws ClientProtocolException, IOException {
HttpResponse sJson = getJSONEntityFromURL(this, url);
return EntityUtils.toString(sJson.getEntity());
}
private static HttpResponse getJSONEntityFromURL(Context context, String url) throws ClientProtocolException, IOException {
HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet(url);
httpget.addHeader("Accept", "application/json");
httpget.addHeader("Content-Type", "application/json; charset=UTF-8");
HttpResponse response;
response = httpclient.execute(httpget);
return response;
}
For the above to work you would have to ode up the bulk insert method of your content provider which could look something like this
#Override
public int bulkInsert(Uri uri, ContentValues[] values) {
final SQLiteDatabase db = mDB.getWritableDatabase();
final int match = sURIMatcher.match(uri);
int numInserted= 0;
// Util.log_debug_message("#### URI MATCH - " + match);
switch(match){
case TEAMS:
numInserted = insertTeams(db, values);
break;
default:
throw new UnsupportedOperationException("unsupported uri: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null, false);
return numInserted;
}
private int insertTeams(SQLiteDatabase db, ContentValues[] values) {
int numInserted = 0;
db.beginTransaction();
try {
//standard SQL insert statement, that can be reused
SQLiteStatement insert =
db.compileStatement(INSERT_OR_REPLACE_STRING + TeamModel.TEAM_TABLE_NAME
+ "(" + TeamModel.COL_SERVER_ID
+ "," + TeamModel.COL_BONUS_RACE_ID
+ "," + TeamModel.COL_POINTS
+ "," + TeamModel.COL_POSITION
+ "," + TeamModel.COL_TEAM_NAME + ")"
+" values " + "(?,?,?,?,?)");
for (ContentValues value : values){
//bind the 1-indexed ?'s to the values specified
insert.bindString(1, value.getAsString(TeamModel.COL_SERVER_ID));
insert.bindString(2, value.getAsString(TeamModel.COL_BONUS_RACE_ID));
insert.bindString(3, value.getAsString(TeamModel.COL_POINTS));
insert.bindString(4, value.getAsString(TeamModel.COL_POSITION));
insert.bindString(5, value.getAsString(TeamModel.COL_TEAM_NAME));
insert.execute();
}
db.setTransactionSuccessful();
numInserted = values.length;
} finally {
db.endTransaction();
db.close();
}
return numInserted;
}
This is not a copy and paste solution. Merely an example stripped out of one of my apps and you should look at each line of code and take great care to understand what is going on.