I'm trying to develop a simple application that reads rss feeds from a certain URL and then displays the results in a list view.
Here is my rss reader, which is the main thing in the app:
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
public class RssReader {
private String title = null;
private String link = null;
private String description = null;
private ArrayList<RssItem> posts = new ArrayList<RssItem>();
private Thread thread;
private String urlString = null;
private XmlPullParserFactory xmlFactoryObject;
public volatile boolean parsingComplete = true;
public RssReader(String url) {
this.urlString = url;
}
public boolean getParsingComplete() {
return this.parsingComplete;
}
public ArrayList<RssItem> getPosts() {
return posts;
}
public void parseXML(XmlPullParser parser) {
int event;
try {
event = parser.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
String name = parser.getName();
switch (event) {
case XmlPullParser.START_TAG:
break;
case XmlPullParser.END_TAG:
if (name.equals("title")) {
title = parser.getText();
} else if (name.equals("link")) {
link = parser.getText();
} else if (name.equals("description")) {
description = parser.getText();
}
break;
}
if(title != null && link != null && description != null) {
RssItem item = new RssItem(this.title,this.description,this.link);
posts.add(item);
this.title = this.description = this.link = null;
}
event = parser.next();
}
parsingComplete = false;
} catch (Exception e) {
e.printStackTrace();
}
}
public void fetchXML() {
thread = new Thread(new Runnable() {
#Override
public void run() {
try {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
conn.setReadTimeout(10000 /* milliseconds */);
conn.setConnectTimeout(15000 /* milliseconds */);
conn.setRequestMethod("GET");
conn.setDoInput(true);
// Starts the query
conn.connect();
InputStream stream = conn.getInputStream();
xmlFactoryObject = XmlPullParserFactory.newInstance();
XmlPullParser myparser = xmlFactoryObject.newPullParser();
myparser.setFeature(
XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
myparser.setInput(stream, null);
parseXML(myparser);
stream.close();
} catch (Exception e) {
} finally {
parsingComplete = true;
}
}
});
thread.start();
}
And here is my MainActivity:
package com.example.ynetrssproject;
import java.util.ArrayList;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.widget.ListView;
import android.os.Bundle;
public class MainActivity extends ActionBarActivity {
private ListView news;
private String rssUrl = "http://www.themarker.com/cmlink/1.144";
private ArrayList<RssItem> list;
private RssItemAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
news = (ListView) findViewById(R.id.news);
RssReader reader = new RssReader(rssUrl);
reader.fetchXML();
while(true) {
Log.d("Runnning", "Run");
if(reader.getParsingComplete()) {
list = reader.getPosts();
break;
}
}
adapter = new RssItemAdapter(this, R.layout.post_item_list, list);
news.setAdapter(adapter);
}
}
The problem is that everytime I call fetchXML, eventually it returns me an empty ArrayList. Therefore, my listview keeps being empty.
My adapter isn't such a big deal. It works fine. The problem is that I keep getting an empty array list from the object RssReader. I know this because I performed a little if statement at the end of the code just to check if the ArrayList is empty.
P.S I have tried with multiple RSS urls but none of them works. Also, I added the permission of Internet in my manifest.
What's wrong with my code?
Your approach is a classic java approach. With android, you should use an IntentService or an AsyncTask that perform the RSS-fetch and then sends the read items to your activity.
http://developer.android.com/reference/android/app/IntentService.html
http://developer.android.com/reference/android/os/AsyncTask.html
Don't use while(true) to poll if the reader is ready.
Be aware that, when the user rotates the screen, onCreate is executed again.
Related
This app is supposed to display movie's tittles fetched from an API into a RecyclerView. I know I am getting the data right because of logs I made on the movies Arraylist, but for some reason, I can't display the info. The app does not crash, it just shows an empty view. I do really appreciate your help.
Disclaimers: I am a newbie, and this solution might be quite simple for experienced people. I usually don't ask questions unless I can't solve the issue, actually this is my first question ever. I've been around this for a few days now and I can't find a way. I understand that this code might have more than one problem on it, and you are very welcome to point them all out.
SO, this is my main activity
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
public static final String LOG_TAG = MainActivity.class.getSimpleName();
private static final String MOST_POPULAR_MOVIES = "http://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=XXXXXXXXXXXXXXXXXXX";
ArrayList<Pelicula> ArrayDePeliculas;
private RecyclerView mPelisList;
private AdapterRV adapterRV;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PeliculasAsyncTask task = new PeliculasAsyncTask();
task.execute(createUrl(MOST_POPULAR_MOVIES));
adapterRV = new AdapterRV(getApplicationContext(),/*Aca puede faltar algo*/ new ArrayList<Pelicula>());
mPelisList = findViewById(R.id.rv_pelis);
GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
mPelisList.setLayoutManager(layoutManager);
mPelisList.setAdapter(adapterRV);
}
public void updateUi(ArrayList<Pelicula> peliculas) {
}
/**
* Returns new URL object from the given string URL.
*/
private URL createUrl(String stringUrl) {
URL url = null;
try {
url = new URL(stringUrl);
} catch (MalformedURLException exception) {
Log.e(LOG_TAG, "Error with creating URL", exception);
return null;
}
return url;
}
/**
* Make an HTTP request to the given URL and return a String as the response.
*/
private String makeHttpRequest(URL url) throws IOException {
String jsonResponse = "";
// If the URL is null, then return early.
if (url == null) {
return jsonResponse;
}
HttpURLConnection urlConnection = null;
InputStream inputStream = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setReadTimeout(10000 /* milliseconds */);
urlConnection.setConnectTimeout(15000 /* milliseconds */);
urlConnection.connect();
// If the request was successful (response code 200),
// then read the input stream and parse the response.
if (urlConnection.getResponseCode() == 200) {
inputStream = urlConnection.getInputStream();
jsonResponse = readFromStream(inputStream);
} else {
Log.e(LOG_TAG, "Error response code: " + urlConnection.getResponseCode());
}
} catch (IOException e) {
Log.e(LOG_TAG, "Problem retrieving the earthquake JSON results.", e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (inputStream != null) {
// function must handle java.io.IOException here
inputStream.close();
}
}
return jsonResponse;
}
/**
* Convert the {#link InputStream} into a String which contains the
* whole JSON response from the server.
*/
private String readFromStream(InputStream inputStream) throws IOException {
StringBuilder output = new StringBuilder();
if (inputStream != null) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
BufferedReader reader = new BufferedReader(inputStreamReader);
String line = reader.readLine();
while (line != null) {
output.append(line);
line = reader.readLine();
}
}
return output.toString();
}
/**
* TODO modificar la url que le estas pasando
*/
public ArrayList<Pelicula> extractFeatureFromJson(String jsonResponse) {
ArrayList<Pelicula> arraydePelis = new ArrayList<>();
try {
JSONObject baseJsonResponse = new JSONObject(jsonResponse);
JSONArray resultsArray = baseJsonResponse.getJSONArray("results");
for (int i = 0; i < resultsArray.length(); i++) {
// If there are results in the features array
if (resultsArray.length() > 0) {
// Extract out the first feature (which is an earthquake)
JSONObject firstResult = resultsArray.getJSONObject(i);
String tittle = firstResult.getString("title");
Pelicula pelicula = new Pelicula(tittle);
arraydePelis.add(pelicula);
}
}
} catch (JSONException e) {
Log.e(LOG_TAG, "Problem parsing the earthquake JSON results", e);
}
return arraydePelis;
}
private class PeliculasAsyncTask extends AsyncTask<URL, Void, ArrayList<Pelicula>> {
#Override
protected ArrayList<Pelicula> doInBackground(URL... urls) {
// Create URL object
URL url = createUrl(MOST_POPULAR_MOVIES);
// Perform HTTP request to the URL and receive a JSON response back
String jsonResponse = "";
try {
jsonResponse = makeHttpRequest(url);
} catch (IOException e) {
Log.e(LOG_TAG, "Problem making the HTTP request.", e);
}
// Extract relevant fields from the JSON response and create an {#link Event} object
ArrayList<Pelicula> ArrayPeliculas = extractFeatureFromJson(jsonResponse);
// Return the {#link Event} object as the result fo the {#link TsunamiAsyncTask}
return ArrayPeliculas;
}
#Override
protected void onPostExecute(ArrayList<Pelicula> data) {
if (data == null) {
return;
}
adapterRV.notifyDataSetChanged();
mPelisList.setAdapter(adapterRV);
Log.e("Nada", data.get(6).getTittle());
super.onPostExecute(data);
}
}
}
The Movie class
public class Pelicula {
//Fields
private String mTittle;
//Constructor
public Pelicula(String Tittle){
mTittle= Tittle;
}
//Methods
public String getTittle (){
return mTittle;
}
}
And finally, the recyclerView adapter
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class AdapterRV extends RecyclerView.Adapter<AdapterRV.PelisViewHolder> {
//Fields
List<Pelicula> mPeliculas;
private Context context;
/* TODO, comprobar que no necesitas esto: mNumberItems = numberOfItems; y poner autofit en grid view */
//Constructor
public AdapterRV (Context context, ArrayList<Pelicula> peliculas){
mPeliculas = peliculas;
this.context= context;
}
//Metodos
#NonNull
#Override
public PelisViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
Context context = parent.getContext();
int layoutForListItem = R.layout.item_layout;
LayoutInflater inflater= LayoutInflater.from(context);
View view = inflater.inflate(layoutForListItem, parent, false);
PelisViewHolder viewHolder = new PelisViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull PelisViewHolder holder, int position) {
Pelicula peliculas = mPeliculas.get(position);
TextView textView = holder.listItemTittleView;
textView.setText(peliculas.getTittle());
}
#Override
public int getItemCount() {
return mPeliculas.size();
}
public class PelisViewHolder extends RecyclerView.ViewHolder{
TextView listItemTittleView;
public PelisViewHolder(#NonNull View itemView) {
super(itemView);
listItemTittleView = itemView.findViewById(R.id.tv_title);
}
}
}
The problem is that you are passing new ArrayList<Pelicula>() to your adapter, and this list is remaining empty. I suggest you declare a global reference of Pelicula list inside your MainActivity and then pass it to the adapter. Whenever that list changes, then notifyDataSetChanged.
If you don't want to declare a global reference, just declare a method inside your adapter:
public void setList(ArrayList list){
this.list = list;
}
and in your MainActivity, just everything you get a new list, setList(newPeliculaList) and adapterRV.notifyDataSetChanged()
Edit:
in onCreate() before declaring the AdapterRV:
just put this:
ArrayDePeliculas = new ArrayList<Pelicula>();
Do you see these 2 lines in onCreate()
adapterRV = new AdapterRV(getApplicationContext(),new ArrayList<Pelicula>());
...
...
mPelisList.setAdapter(adapterRV);
Move them to onPostExecute() of the asyncTask and pass the data instead of a new ArrayList<Pelicula>():
#Override
protected void onPostExecute(ArrayList<Pelicula> data) {
//assuming the list is full and all is good
if (data == null) {
return;
}
//here...
adapterRV = new AdapterRV(getApplicationContext(),data);
mPelisList.setAdapter(adapterRV);
Log.e("Nada", data.get(6).getTittle());
super.onPostExecute(data);
}
UPDATE:
Its because you don't initialize the list in the adapter:
public class AdapterRV extends RecyclerView.Adapter<AdapterRV.PelisViewHolder> {
//Fields
//here you must initialize like this:
List<Pelicula> mPeliculas = new ArrayList<Pelicula>();
.....
Aim: Building app on Google API to fetch the data about the books the user searches
Problem Explanation:
Whenever I hit the submit Button, my app crashes.
This is my first approach in making a network request app and I need guidance.
MainActivityClass
package com.example.vidit.books;
import android.app.Activity;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText query = (EditText) findViewById(R.id.query);
Button submit= (Button) findViewById(R.id.submit);
submit.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent= new Intent(MainActivity.this,Request.class);
intent.putExtra ( "text", query.getText().toString() );
startActivity(intent);
}
});
}
}
Second Class
package com.example.vidit.books;
import android.content.Intent;
public class Request {
Intent i = getIntent();
String text = i.getStringExtra ("text");
public static final String LOG_TAG = Request.class.getSimpleName();
String APIURL="https://www.googleapis.com/books/v1/volumes?q= " + text;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_request);
}
public void UpdateUi(Book book)
{
BookAdapter bookAdapter = new BookAdapter(this,book);
ListView listView= (ListView) findViewById(R.id.listview_all);
}
private class BookAsyncTask extends AsyncTask<URL,Void,Book>
{
#Override
protected Book doInBackground(URL... urls) {
URL url = createUrl(APIURL);
String jsonResponse = "";
try {
jsonResponse = makeHttpRequest(url);
} catch (IOException e) {
// TODO Handle the IOException
}
final Book book = extractFeatureFromJson(jsonResponse);
return book;
}
/**
* Make an HTTP request to the given URL and return a String as the response.
*/
private String makeHttpRequest(URL url) throws IOException {
String jsonResponse = "";
HttpURLConnection urlConnection = null;
InputStream inputStream = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setReadTimeout(10000 /* milliseconds */);
urlConnection.setConnectTimeout(15000 /* milliseconds */);
urlConnection.connect();
if(urlConnection.getResponseCode()==200) {
inputStream = urlConnection.getInputStream();
jsonResponse = readFromStream(inputStream);
}
} catch (IOException e) {
// TODO: Handle the exception
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (inputStream != null) {
// function must handle java.io.IOException here
inputStream.close();
}
}
return jsonResponse;
}
/**
* Convert the {#link InputStream} into a String which contains the
* whole JSON response from the server.
*/
private String readFromStream(InputStream inputStream) throws IOException {
StringBuilder output = new StringBuilder();
if (inputStream != null) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
BufferedReader reader = new BufferedReader(inputStreamReader);
String line = reader.readLine();
while (line != null) {
output.append(line);
line = reader.readLine();
}
}
return output.toString();
}
/**
* Returns new URL object from the given string URL.
*/
private URL createUrl(String stringUrl) {
URL url = null;
try {
url = new URL(stringUrl);
} catch (MalformedURLException exception) {
Log.e(LOG_TAG, "Error with creating URL", exception);
return null;
}
return url;
}
private Book extractFeatureFromJson(String bookJSON) {
try {
JSONObject baseJsonResponse = new JSONObject(bookJSON);
JSONArray items = baseJsonResponse.getJSONArray("items");
// If there are results in the features array
for(int i=0;i<10;i++)
{
JSONObject firstFeature = items.getJSONObject(i);
JSONArray author=firstFeature.getJSONArray("author");
for(int j=0;j<author.length();j++)
{
JSONObject authorFeature=author.getJSONObject(j);
}
String title = items.getString(Integer.parseInt("title"));
// Create a new {#link Event} object
return new Book(title,author);
}
} catch (JSONException e) {
Log.e(LOG_TAG, "Problem parsing the earthquake JSON results", e);
}
return null;
}
}
}
BookAdapter Class:
package com.example.vidit.books;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.List;
public class BookAdapter extends ArrayAdapter<Book> {
public BookAdapter(Activity context, Book book)
{
super(context,0, (List<Book>) book);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View listItemView = convertView;
if(listItemView == null) {
listItemView = LayoutInflater.from(getContext()).inflate(
R.layout.list_item, parent, false);
}
Book cbook=getItem(position);
TextView title = (TextView) listItemView.findViewById(R.id.title);
title.setText(cbook.getmTitle());
TextView author=(TextView) listItemView.findViewById(R.id.author);
author.setText((CharSequence) cbook.getmAuthor());
return listItemView;
}
}
Showing error in statement:
String text = i.getStringExtra ("text");
Need guidance
I don't know how your code gets compiled when you have overridden onCreate() in Request class and the Request class isn't extending Activity or AppCompatActivity.
Secondly, this line :
Intent i = getIntent();
String text = i.getStringExtra ("text");
should be inside the onCreate() method.
Showing error in statement : String text = i.getStringExtra ("text");
Request for Guidance
Well you need to get the data passed inside onCreate like below.
String APIURL;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_request);
Bundle bundle = getIntent().getExtras();
String text = bundle.getString("text");
APIURL="https://www.googleapis.com/books/v1/volumes?q= " + text;
}
And although you have the asyncTask class i can't see where exactly you execute the class. You need to do that inside onCreate as well.
Try moving this code to your onCreate method
Intent i = getIntent();
String text = i.getStringExtra ("text");
The intent extras is not available in the constructor for your Request class.
I'm developing an app and now I have to pass a parameter to a RESTful Service's URL. I'm using AsyncTask, and I need to pass a text from a list view as a parameter to the URL, for example: the URL is http://ip:7001/product?product_name=PARAM I need to get the text from the selected item from my list view, and pass as a parameter in PARAM, using AsyncTask. I've already got the text from the item in the listView, now I just need to pass it as a parameter.
This is my AsycTask class:
package com.tumta.henrique.teste;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import com.tumta.henrique.teste.ProdutoFragment;
/**
* Created by Henrique on 18/05/2015.
*/
public class FiltraProduto extends AsyncTask<String, Void, List<String>> {
private ConsultaConcluidaFiltroProdutoListener listener;
public static String URL_STRING = "http://192.168.0.20:7001/com.henrique.rest/api/v1/status/pro_filtro?pro_nome=";
public FiltraProduto(ConsultaConcluidaFiltroProdutoListener listener) {
this.listener = listener;
}
private List<String> InterpretaResultado(String resultado) throws JSONException {
JSONObject object = new JSONObject(resultado);
JSONArray jsonArray = object.getJSONArray("produto");
//JSONObject jsonProduto = jsonArray.getJSONObject(0);
// String id = jsonProduto.getString("pro_id");
//proId = id;
List<Object> listaNomes = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonProdutoInfo = jsonArray.getJSONObject(i);
String proNome= jsonProdutoInfo.getString("pro_nome");
double proPreco = jsonProdutoInfo.getDouble("pro_preco");
double proSdAtual = jsonProdutoInfo.getDouble("pro_sdAtual");
listaNomes.add(i, proNome);
listaNomes.add(i, proPreco);
listaNomes.add(i, proSdAtual);
}
List<String> strings = new ArrayList<String>();
for (Object o : listaNomes) {
strings.add(o != null ? o.toString() : null);
}
return strings;
}
private String ConsultaServidor() throws IOException {
InputStream is = null;
try {
URL url = new URL(URL_STRING);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10000);
conn.setReadTimeout(15000);
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.connect();
conn.getResponseCode();
is = conn.getInputStream();
Reader reader = null;
reader = new InputStreamReader(is);
char[] buffer = new char[2048];
reader.read(buffer);
return new String(buffer);
} finally {
if (is != null) {
is.close();
}
}
}
#Override
protected List<String> doInBackground(String... params) {
try {
String resultado = ConsultaServidor();
return InterpretaResultado(resultado);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(List<String> result) {
listener.onConsultaConcluida(result);
super.onPostExecute(result);
}
public interface ConsultaConcluidaFiltroProdutoListener {
void onConsultaConcluida(List<String> result);
}
}
In the URL_STRING I need to pass the param at pro_nome=?
Here I get the item text. This is in my Fragment that has the List View:
public String retornaParam(String param){
return param;
}
#Override
public void onConsultaConcluida(List<String> result) {
final ListView listaProdutos = (ListView) getView().findViewById(R.id.listaprodutos);
ArrayAdapter arrayAdapter = new ArrayAdapter<>(getView().getContext(),android.R.layout.simple_list_item_1, result);
listaProdutos.setAdapter(arrayAdapter);
listaProdutos.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parentAdapter, View view, int position,
long id) {
String nomeProduto = listaProdutos.getItemAtPosition(position).toString();
retornaParam(nomeProduto);
Intent intent = new Intent(getActivity(), DetalhesProdutoActivity.class);
//intent.putExtra("pro_nome", listaProdutos.getItemAtPosition(position).toString());
startActivity(intent);
}
});
}
I get the text and store it in param from the retornaParam method.
Does somebody know how to do it?
If you need more information, just let me know.
You pass in params to an AsyncTask using:
YourAsyncTask.execute(yourview.getText(), "and", "more", "params");
You can then access them in
#Override
protected String doInBackground(String... params) {
URL_STRING += params[0];
...
Just add the following code before sending executing your httpClient:
URL_STRING + = textInsideYourTextView;
It should work, just avoid to manipulate your ui elements outside your UI thread.
I'm following the android xml parsing tutorial (http://developer.android.com/training/basics/network-ops/xml.html#analyze), however when stepping through it, it seems to fail at:
parser.require(XmlPullParser.START_TAG, ns, "rss");
and then ends up dropping to the finally clause. Just wondering if anyone could spot what the issue could be as I've played around with it for hours without much success.
The feed I am trying to read is: http://feeds.bbci.co.uk/news/rss.xml
Any help is greatly appreciated, please find the code below:
package com.proxama.news_bbc;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.support.v7.app.ActionBarActivity;
import android.support.v4.app.Fragment;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.util.Xml;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.provider.DocumentsContract.Document;
public class MainActivity extends ActionBarActivity {
private static final String ns = null;
/*
* Only interested in the content of the following XML tags for each news
* item:
*
* item/media:thumbnail item/pubDate item/link item/description item/title
*/
private void skip(XmlPullParser parser) throws XmlPullParserException,
IOException {
if (parser.getEventType() != XmlPullParser.START_TAG) {
throw new IllegalStateException();
}
int depth = 1;
while (depth != 0) {
switch (parser.next()) {
case XmlPullParser.END_TAG:
depth--;
break;
case XmlPullParser.START_TAG:
depth++;
break;
}
}
}
public static class Entry {
public final String title;
public final String link;
public final String summary;
private Entry(String title, String summary, String link) {
this.title = title;
this.summary = summary;
this.link = link;
}
}
// Parses the contents of an entry. If it encounters a title, summary, or
// link tag, hands them off
// to their respective "read" methods for processing. Otherwise, skips the
// tag.
// Processes title tags in the feed.
private String readTitle(XmlPullParser parser) throws IOException,
XmlPullParserException {
parser.require(XmlPullParser.START_TAG, ns, "title");
String title = readText(parser);
parser.require(XmlPullParser.END_TAG, ns, "title");
return title;
}
// Processes link tags in the feed.
private String readLink(XmlPullParser parser) throws IOException,
XmlPullParserException {
String link = "";
parser.require(XmlPullParser.START_TAG, ns, "link");
String tag = parser.getName();
String relType = parser.getAttributeValue(null, "rel");
if (tag.equals("link")) {
if (relType.equals("alternate")) {
link = parser.getAttributeValue(null, "href");
parser.nextTag();
}
}
parser.require(XmlPullParser.END_TAG, ns, "link");
return link;
}
// Processes summary tags in the feed.
private String readSummary(XmlPullParser parser) throws IOException,
XmlPullParserException {
parser.require(XmlPullParser.START_TAG, ns, "summary");
String summary = readText(parser);
parser.require(XmlPullParser.END_TAG, ns, "summary");
return summary;
}
// For the tags title and summary, extracts their text values.
private String readText(XmlPullParser parser) throws IOException,
XmlPullParserException {
String result = "";
if (parser.next() == XmlPullParser.TEXT) {
result = parser.getText();
parser.nextTag();
}
return result;
}
private Entry readEntry(XmlPullParser parser)
throws XmlPullParserException, IOException {
parser.require(XmlPullParser.START_TAG, ns, "item");
String title = null;
String summary = null;
String link = null;
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
if (name.equals("title")) {
title = readTitle(parser);
} else if (name.equals("summary")) {
summary = readSummary(parser);
} else if (name.equals("link")) {
link = readLink(parser);
} else {
skip(parser);
}
}
return new Entry(title, summary, link);
}
private List readFeed(XmlPullParser parser) throws XmlPullParserException,
IOException {
List entries = new ArrayList();
String xmlContents = parser.toString();
parser.require(XmlPullParser.START_TAG, ns, "rss");
Log.d("className", "Up to here ");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
// Starts by looking for the news item tag
if (name.equals("item")) {
entries.add(readEntry(parser));
} else {
skip(parser);
}
}
return entries;
}
private List parseXml(InputStream in) throws XmlPullParserException,
IOException {
try {
XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(in, null);
return readFeed(parser);
} finally {
in.close();
}
}
private String downloadUrl(String myurl) throws IOException {
// TODO allow for internal storage if external is unavailable or full
File root = android.os.Environment.getExternalStorageDirectory();
String Directory = root.getAbsolutePath() + "/xmls";
String xmlFileName = "bbc_news_rss.xml";
String fullXmlPath = Directory + "/" + xmlFileName;
File dir = new File(Directory);
if (dir.exists() == false) {
dir.mkdirs();
}
/*URL url = new URL(myurl);
InputStream is = url.openStream();
OutputStream os = new FileOutputStream(fullXmlPath);
byte[] b = new byte[2048];
int length;
while ((length = is.read(b)) != -1) {
os.write(b, 0, length);
}
is.close();
os.close();*/
URL url = new URL(myurl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000 /* milliseconds */);
conn.setConnectTimeout(15000 /* milliseconds */);
conn.setRequestMethod("GET");
conn.setDoInput(true);
// Starts the query
conn.connect();
InputStream in = conn.getInputStream();
Log.d("className", "Saved XML file to: " + fullXmlPath);
Log.d("className", "Starting to parse XML file: " + fullXmlPath);
//InputStream in = new FileInputStream(fullXmlPath);
try {
parseXml(in);
} catch (XmlPullParserException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return fullXmlPath;
}
// Uses AsyncTask to create a task away from the main UI thread. This task
// takes a
// URL string and uses it to create an HttpUrlConnection. Once the
// connection
// has been established, the AsyncTask downloads the contents of the webpage
// as
// an InputStream. Finally, the InputStream is converted into a string,
// which is
// displayed in the UI by the AsyncTask's onPostExecute method.
private class DownloadWebpageTask extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... stringUrl) {
try {
Log.d("className", "Downloading url: " + stringUrl[0]);
return downloadUrl(stringUrl[0]);
} catch (IOException e) {
return "Unable to retrieve file.";
}
}
// onPostExecute displays the results of the AsyncTask.
#Override
protected void onPostExecute(String result) {
Log.d("className",
"In onpostexecute and result of downloadUrl is: " + result);
// If the file has been saved correctly, parse it.
if (result != "Unable to retrieve file.") {
Log.d("className", "Creating list view");
createList();
}
}
}
/** Pull in the XML from http://feeds.bbci.co.uk/news/rss.xml */
public void pull_xml(View view) {
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
// can connect so fetch XML file
Log.d("className", "Successfully connected to network!");
new DownloadWebpageTask()
.execute("http://feeds.bbci.co.uk/news/rss.xml");
} else {
// display error
Log.d("className",
"Unable to connect to network, trying to use a previously saved local xml file");
}
}
public void createList() {
setContentView(R.layout.activity_main);
// Get ListView object from XML
ListView listView = (ListView) findViewById(R.id.listView1);
// Defined Array values to show in ListView
String[] values = new String[] { "Ukraine begins 'anti-terror' action" };
// Define a new Adapter
// First parameter - Context
// Second parameter - Layout for the row
// Third parameter - ID of the TextView to which the data is written
// Forth - the Array of data
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, android.R.id.text1, values);
// Assign adapter to ListView
listView.setAdapter(adapter);
}
// Original classes and methods that came by default with new android
// projects.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// retrieve the XML and create list view
pull_xml(null);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false);
return rootView;
}
}
}
Your code has several mistakes:
DownloadWebpageTask.onPostExecute: here you are trying to compare Strings with != operator. It's wrong, you'll always get false. The correct way is to use equals method:
if ("Unable to retrieve file.".equals(result)) {
Log.d("className", "Creating list view");
createList();
}
createList() method: what's the purpose of calling setContentView(R.layout.activity_main); here if you already did it at the onCreate?
pull_xml(View view) method: what's the purpose of view argument? You are not using it at this method.
parseXml method: please add parser.nextTag()
I am new to Android. I have an AsyncTask that is downloading the content of a URL.
I didn't want the AsyncTask to manipulate the UI directly and want it to have it as a reusable peice of code so I have put it in a file of its own and it returns a string.
The problem is that the the return happens before the AsyncTask is finished (even though I am using the .get() of the .excecute()), so I get nothing back.
Here is waht I have at the moment:
package com.example.mypackage;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutionException;
import android.os.AsyncTask;
public class URLContent {
private String content = "default value";
public String getContent(String URL){
try {
new getAsyncContent().execute(URL).get();
} catch (InterruptedException e) {
content = e.getMessage();
} catch (ExecutionException e) {
content = e.getMessage();
}
return content;
}
private class getAsyncContent extends AsyncTask<String, Integer, String>
{
#Override
protected void onPostExecute(String result) {
content = result;
}
#Override
protected String doInBackground(String... urls) {
try{
return URLResponse(urls[0]);
} catch (Exception e){
return e.getMessage();
}
}
}
private String IStoString(InputStream stream) throws IOException, UnsupportedEncodingException {
try {
return new java.util.Scanner(stream, "UTF-8").useDelimiter("\\A").next();
} catch (java.util.NoSuchElementException e) {
return "";
}
}
private String URLResponse(String URLToget) throws IOException {
InputStream is = null;
try {
URL url = new URL(URLToget);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000 /* milliseconds */);
conn.setConnectTimeout(15000 /* milliseconds */);
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.connect();
is = conn.getInputStream();
// Convert the InputStream into a string
String contentAsString = IStoString(is);
return contentAsString;
} finally {
if (is != null) {
is.close();
}
}
}
}
What would be the best way to solve that so that my main thread somehow gets back the results?
I have come accross some articles mentioning events and callbacks. Is that the best way..?
Don't do get(). Simply have whatever logic deals with result of your URL execution right inside doInBackground method. From which you don't have to return anything. In your particular case the problem is that get() is executed right away before the result is delivered
This is asynchronous execution and you can't apply linear logic to it
Thanks for all your help. I will try the solution for extending the asyncTask class by Alex, sounds like a clean solution.
I managed to do what I was trying to by using an interface in the class with the AsyncTask and adding an event listener on the main class.
So my class that gets the content now looks like this:
package com.example.test;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import android.os.AsyncTask;
public class URLContent {
public void getContent(String URL){
new getAsyncContent().execute(URL);
}
public interface OnContentDownloadedListener{
public abstract void onContentDownloaded(String content);
}
private OnContentDownloadedListener contentDownloadedListener = null;
public void setOnContentDownloadedListener(OnContentDownloadedListener content){
contentDownloadedListener = content;
}
private class getAsyncContent extends AsyncTask<String, Integer, String>
{
#Override
protected void onPostExecute(String result) {
contentDownloadedListener.onContentDownloaded(result);
}
#Override
protected String doInBackground(String... urls) {
try{
return URLResponse(urls[0]);
} catch (Exception e){
return e.getMessage();
}
}
}
private String IStoString(InputStream stream) throws IOException, UnsupportedEncodingException {
try {
return new java.util.Scanner(stream, "UTF-8").useDelimiter("\\A").next();
} catch (java.util.NoSuchElementException e) {
return "";
}
}
private String URLResponse(String URLToget) throws IOException {
InputStream is = null;
try {
URL url = new URL(URLToget);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000 /* milliseconds */);
conn.setConnectTimeout(15000 /* milliseconds */);
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.connect();
is = conn.getInputStream();
// Convert the InputStream into a string
String contentAsString = IStoString(is);
return contentAsString;
} finally {
if (is != null) {
is.close();
}
}
}
}
And then in my main class I just have an event listener that handles the update of the UI
urlContent.setOnContentDownloadedListener(new URLContent.OnContentDownloadedListener(){
public void onContentDownloaded(String content){
//update UI or do something with the data
}
Updating UI with the results of AsyncTask is the main purpose of onPostExecute(). To keep the implementation and the way it's used in UI separate, your Activity can extend your getAsyncContent class and override the onPostExecute() method. This method will be executed on UI thread.
If You do not need UI updates from Your asynchronous task, just create a simple thread to do the things. When complete, just send a broadcast.
new Thread(new Runnable() {
#Override
public void run() {
// Do the download here...
....
//
sendBroadcast(some_intent);
}).start();