Top Stories
from the New York Times API

Download the top stories from the New York Times API in JSON format, and display them in a TextView and a touch-sensitive ListView. We saw a ListView whose Views contained a title and subtitle in CursorAdapter and in Sqlite. See the API (Application Program Interface), Requesting a Key, and the Top Stories API.

Source code in Top.zip

  1. MainActivity.java. Class MainActivity contains the inner class DownloadTask.
  2. activity_main.xml is a RelativeLayout containing a TextView. The TextView is scrollable because of android:scrollbars="vertical" and the setMovementMethod in useTheResult.
  3. strings.xml
  4. dimens.xml
  5. AndroidManifest.xml has uses-permission INTERNET.
  6. build.gradle (Module: app).

Create the project

Get a key from the New York Times.

Things to try

  1. Display the stories in a ListView.
    1. In activity_main.xml, replace the TextView with
          <ListView
              android:id="@+id/listView"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"/>
      
          <TextView
              android:id="@android:id/empty"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:text="No articles."/>
      
    2. Create a new file ArticleAdapter.java. It uses a layout file on your hard disk, ~/Library/Android/sdk/platforms/android-22/data/res/layout/simple_list_item_2.xml which contains an object of the deprecated class TwoLineListItem. But I was too lazy to write my own layout file containing a LinearLayout.
      package edu.nyu.scps.top;
      
      import android.content.Context;
      import android.view.LayoutInflater;
      import android.view.View;
      import android.view.ViewGroup;
      import android.widget.BaseAdapter;
      import android.widget.TextView;
      import android.widget.TwoLineListItem;
      
      
      public class ArticleAdapter extends BaseAdapter {
          private Context context;
          String[] title;
          String[] abs;
      
          public ArticleAdapter(Context context, String[] title, String[] abs) {
              this.context = context;
              this.title = title;
              this.abs = abs;
          }
      
          @Override
          public int getCount() {
              return title.length;
          }
      
          @Override
          public Object getItem(int position) {
              return null;
          }
      
          @Override
          public long getItemId(int position) {
              return 0;
          }
      
          @Override
          public View getView(int position, View convertView, ViewGroup parent) {
              TwoLineListItem twoLineListItem;
              if (convertView != null) {
                  twoLineListItem = (TwoLineListItem)convertView;
              } else {
                  LayoutInflater inflater = LayoutInflater.from(context);
                  twoLineListItem = (TwoLineListItem)inflater.inflate(android.R.layout.simple_list_item_2, null);
              }
              TextView text1 = (TextView)twoLineListItem.findViewById(android.R.id.text1);
              text1.setText(title[position]);
              TextView text2 = (TextView)twoLineListItem.findViewById(android.R.id.text2);
              text2.setText(abs[position]);
              return twoLineListItem;
          }
      }
      
    3. Change useTheResult to the following. When you touch an item, it will launch a web browser with the full article. That’s because when you search for an Activity that’s capable of performing an ACTION_VIEW with a piece of information whose Uri starts with http:, the Intent will find the web browser for you. See the browser’s AndroidManifest.xml, lines 69 and 83.
          private void useTheResult(String json) {
              TextView empty = (TextView)findViewById(android.R.id.empty);
              int n;  //number of articles
              String[] title;
              String[] abs;  //"abstract" is Java keyword
              final String[] url;
      
              if (json == null) {
                  empty.setText("Couldn't get JSON string from server.");
                  return;
              }
      
              try {
                  JSONObject jSONObject = new JSONObject(json);
                  JSONArray results = jSONObject.getJSONArray("results");
                  n = results.length();
                  title = new String[n];
                  abs = new String[n];
                  url = new String[n];
      
                  for (int i = 0; i < n; ++i) {
                      JSONObject result = results.getJSONObject(i);
                      title[i] = result.getString("title");
                      abs[i] = result.getString("abstract");
                      url[i] = result.getString("url");
                  }
              } catch (JSONException exception) {
                  empty.setText(exception.toString());
                  return;
              }
      
              ArticleAdapter adapter = new ArticleAdapter(this, title, abs);
              ListView listView = (ListView)findViewById(R.id.listView);
              listView.setAdapter(adapter);
              listView.setEmptyView(empty);
      
              listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                  @Override
                  public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                      Uri uri = Uri.parse(url[position]);
                      Intent intent = new Intent(Intent.ACTION_VIEW, uri); //find web browser
                      startActivity(intent); //To return to here, touch Android back button.
                  }
              });
          }
      

  2. Don’t use the deprecated class TwoLineListItem. Create your own layout file containing a LinearLayout as we did in CursorAdapter.

  3. Are the titles and abstracts too long to look good in a ListView? What about in landscape orientation? If they’re still too long, display each article’s thumbnail image and title instead. See WeatherGrid for downloading an icon. The JSON will be parsed in the second thread.
    1. Create article.xml to replace simple_list_item_2.xml.
      <?xml version="1.0" encoding="utf-8"?>
      
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:gravity="center"
          android:orientation="horizontal"
          android:paddingBottom="8dp"
          android:paddingLeft="0dp"
          android:paddingRight="?attr/listPreferredItemPaddingRight"
          android:paddingTop="8dp">
      
      
          <RelativeLayout
              android:layout_width="40dp"
              android:layout_height="40dp">
      
              <ImageView
                  android:id="@+id/imageView"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_centerInParent="true"/>
      
          </RelativeLayout>
      
      
          <TextView
              android:id="@+id/textView"
              android:layout_width="match_parent"
              android:layout_height="wrap_content" />
      
      </LinearLayout>
      
    2. Add an extra field to class ArticleAdapter:
          Bitmap[] bitmap;
      
      Initialize the field with an extra argument of the constructor of class ArticleAdapter.
          public ArticleAdapter(/* etc. */, Bitmap[] bitmap) {
              //etc.
              this.bitmap = bitmap;
      
      Change the getView method of the adapter to
          @Override
          public View getView(int position, View convertView, ViewGroup parent) {
              LinearLayout linearLayout;
              if (convertView != null) {
                  linearLayout = (LinearLayout)convertView;
              } else {
                  LayoutInflater inflater = LayoutInflater.from(context);
                  linearLayout = (LinearLayout)inflater.inflate(R.layout.article, null);
              }
      
              ImageView imageView = (ImageView)linearLayout.findViewById(R.id.imageView);
              if (bitmap[position] != null) {
                  imageView.setImageBitmap(bitmap[position]);
              }
              TextView textView = (TextView)linearLayout.findViewById(R.id.textView);
              textView.setText(title[position]);
              return linearLayout;
          }
      
    3. The local variables n, title, abs, and url in useTheResult will become fields of class MainActivity. Add an additional fields to this class:
          Bitmap[] bitmap;
      
      Add this method to class MainActivity.
          private Bitmap downloadThumbnail(String urlString) {
              HttpURLConnection connection = null;
              InputStream inputStream = null;
              byte[] byteArray = null;
      
              try {
                  URL url = new URL(urlString);
                  connection = (HttpURLConnection)url.openConnection();
                  connection.setRequestMethod("GET");
                  connection.setDoInput(true);
                  connection.connect();
      
                  //Read the response.
                  inputStream = connection.getInputStream();
                  byte[] buffer = new byte[1024];
                  ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
      
                  while (inputStream.read(buffer) != -1) {
                      outputStream.write(buffer);
                  }
                  byteArray = outputStream.toByteArray();
              } catch (Exception exception) {
                  Log.e("myTag", "downloadThumbnail", exception);
              } finally {
                  try {
                      inputStream.close();
                  } catch (Exception exception) {
                      Log.e("myTag", "downloadThumbnail", exception);
                      return null;
                  }
                  try {
                      connection.disconnect();
                  } catch (Exception exception) {
                      Log.e("myTag", "downloadThumbnail", exception);
                      return null;
                  }
              }
              if (byteArray == null || byteArray.length == 0) {
                  return null;
              }
              return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
          }
      
    4. Change doInBackground to
              @Override
              protected Void doInBackground(String... urlString) {
                  String json;
                  //Must be declared outside of try block,
                  //so we can mention them in finally block.
                  HttpURLConnection httpURLConnection = null;
                  BufferedReader bufferedReader = null;
      
                  try {
                      URL url = new URL(urlString[0]);
                      httpURLConnection = (HttpURLConnection) url.openConnection();
                      httpURLConnection.setRequestMethod("GET");
                      httpURLConnection.connect();
      
                      InputStream inputStream = httpURLConnection.getInputStream();
                      if (inputStream == null) {
                          return null;
                      }
                      InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                      bufferedReader = new BufferedReader(inputStreamReader);
      
                      String line;
                      StringBuffer buffer = new StringBuffer();
                      while ((line = bufferedReader.readLine()) != null) {
                          buffer.append(line);
                      }
      
                      if (buffer.length() == 0) {
                          json = null;
                      }
                      json = buffer.toString();
                  } catch (IOException exception) {
                      Log.e("myTag", "doInBackground ", exception);
                      return null;
                  } finally {
                      if (httpURLConnection != null) {
                          httpURLConnection.disconnect();
                      }
                      if (bufferedReader != null) {
                          try {
                              bufferedReader.close();
                          } catch (final IOException exception) {
                              Log.e("myTag", "doInBackground ", exception);
                          }
                      }
                  }
      
                  if (json == null) {
                      //empty.setText("Couldn't get JSON string from server.");
                      return null;
                  }
      
                  try {
                      JSONObject jSONObject = new JSONObject(json);
                      JSONArray results = jSONObject.getJSONArray("results");
                      n = results.length();
                      Log.d("myTag", "n = " + n);
                      title = new String[n];
                      abs = new String[n];
                      url = new String[n];
                      bitmap = new Bitmap[n];
      
                      for (int i = 0; i < n; ++i) {
                          JSONObject result = results.getJSONObject(i);
                          title[i] = result.getString("title");
                          abs[i] = result.getString("abstract");
                          url[i] = result.getString("url");
                          try {
                              JSONArray multimedia = result.getJSONArray("multimedia");
                              JSONObject medium = multimedia.getJSONObject(0);
                              String thumbnailUrl = medium.getString("url");
      			bitmap[i] = downloadThumbnail(thumbnailUrl);
                          } catch (JSONException exception) {
                              bitmap[i] = null;
                          }
                      }
                  } catch (JSONException exception) {
                      //empty.setText(exception.toString());
                      return null;
                  }
                  return null;
              }
      
    5. The argument of onPostExecute is Void v and the return type is void. It calls useTheResult with no argument. The AsyncTsk is <String, Void, Void>.
    6. useTheResult is
          private void useTheResult() {
              TextView empty = (TextView) findViewById(android.R.id.empty);
      
              ArticleAdapter adapter = new ArticleAdapter(this, title, abs, bitmap);
              ListView listView = (ListView) findViewById(R.id.listView);
              listView.setAdapter(adapter);
              listView.setEmptyView(empty);
      
              listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                  @Override
                  public void onItemClick(AdapterView parent, View view, int position, long id) {
                      Uri uri = Uri.parse(url[position]);
                      Intent intent = new Intent(Intent.ACTION_VIEW, uri); //find web browser
                      startActivity(intent); //To return to here, touch Android back button.
                  }
              });
          }