FlickrFetchr.java
package com.example.aster.photogallery;
import android.net.Uri;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class FlickrFetchr {
private static final String TAG = "FlickrFetchr";
private static final String API_KEY = "4f721bgafa75bf6d2cb9af54f937bb70";
public byte[] getUrlBytes(String urlSpec) throws IOException {
URL url = new URL(urlSpec);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = connection.getInputStream();
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new IOException(connection.getResponseMessage() +
": with " +
urlSpec);
}
int bytesRead = 0;
byte[] buffer = new byte[1024];
while ((bytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
}
out.close();
return out.toByteArray();
} finally {
connection.disconnect();
}
}
public String getUrlString(String urlSpec) throws IOException {
return new String(getUrlBytes(urlSpec));
}
public List<GalleryItem> fetchItems() {
List<GalleryItem> items = new ArrayList<>();
try {
String url = Uri.parse("https://api.flickr.com/services/rest/")
.buildUpon()
.appendQueryParameter("method", "flickr.photos.getRecent")
.appendQueryParameter("api_key", API_KEY)
.appendQueryParameter("format", "json")
.appendQueryParameter("nojsoncallback", "1")
.appendQueryParameter("extras", "url_s")
.build().toString();
String jsonString = getUrlString(url);
Log.i(TAG, "Received JSON: " + jsonString);
JSONObject jsonBody = new JSONObject(jsonString);
parseItems(items, jsonBody);
} catch (IOException ioe) {
Log.e(TAG, "Failed to fetch items", ioe);
} catch (JSONException je) {
Log.e(TAG, "Failed to parse JSON", je);
}
return items;
}
private void parseItems(List<GalleryItem> items, JSONObject jsonBody)
throws IOException, JSONException {
JSONObject photosJsonObject = jsonBody.getJSONObject("photos");
JSONArray photoJsonArray = photosJsonObject.getJSONArray("photo");
for (int i = 0; i < photoJsonArray.length(); i++) {
JSONObject photoJsonObject = photoJsonArray.getJSONObject(i);
GalleryItem item = new GalleryItem();
item.setId(photoJsonObject.getString("id"));
item.setCaption(photoJsonObject.getString("title"));
if (!photoJsonObject.has("url_s")) {
continue;
}
item.setUrl(photoJsonObject.getString("url_s"));
items.add(item);
}
}
}
GalleryItem.java
package com.example.aster.photogallery;
public class GalleryItem {
private String mCaption;
private String mId;
private String mUrl;
@Override
public String toString() {
return mCaption;
}
public String getCaption() {
return mCaption;
}
public void setCaption(String caption) {
mCaption = caption;
}
public String getId() {
return mId;
}
public void setId(String id) {
mId = id;
}
public String getUrl() {
return mUrl;
}
public void setUrl(String url) {
mUrl = url;
}
}
PhotoGalleryActivity.java
package com.example.aster.photogallery;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class PhotoGalleryActivity extends SingleFragmentActivity {
@Override
protected Fragment createFragment() {
return PhotoGalleryFragment.newInstance();
}
}
PhotoGalleryFragment.java
package com.example.aster.photogallery;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class PhotoGalleryFragment extends Fragment {
private static final String TAG = "PhotoGalleryFragment";
private RecyclerView mPhotoRecyclerView;
private List<GalleryItem> mItems = new ArrayList<>();
private ThumbnailDownloader<PhotoHolder> mThumbnailDownloader;
public static PhotoGalleryFragment newInstance() {
return new PhotoGalleryFragment();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
new FetchItemsTask().execute();
mThumbnailDownloader = new ThumbnailDownloader<>();
mThumbnailDownloader.start();
mThumbnailDownloader.getLooper();
Log.i(TAG, "Background thread started");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_photo_gallery, container, false);
mPhotoRecyclerView = (RecyclerView) v.findViewById(R.id.photo_recycler_view);
mPhotoRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3));
setupAdapter();
return v;
}
@Override
public void onDestroy() {
super.onDestroy();
mThumbnailDownloader.quit();
Log.i(TAG, "Background thread destroyed");
}
private void setupAdapter() {
if (isAdded()) {
mPhotoRecyclerView.setAdapter(new PhotoAdapter(mItems));
}
}
private class PhotoHolder extends RecyclerView.ViewHolder {
private ImageView mItemImageView;
public PhotoHolder(View itemView) {
super(itemView);
mItemImageView = (ImageView) itemView.findViewById(R.id.item_image_view);
}
public void bindDrawable(Drawable drawable) {
mItemImageView.setImageDrawable(drawable);
}
}
private class PhotoAdapter extends RecyclerView.Adapter<PhotoHolder> {
private List<GalleryItem> mGalleryItems;
public PhotoAdapter(List<GalleryItem> galleryItems) {
mGalleryItems = galleryItems;
}
@Override
public PhotoHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
LayoutInflater inflater = LayoutInflater.from(getActivity());
View view = inflater.inflate(R.layout.list_item_gallery, viewGroup, false);
return new PhotoHolder(view);
}
@Override
public void onBindViewHolder(PhotoHolder photoHolder, int position) {
GalleryItem galleryItem = mGalleryItems.get(position);
Drawable placeholder = getResources().getDrawable(R.drawable.bill_up_close);
photoHolder.bindDrawable(placeholder);
mThumbnailDownloader.queueThumbnail(photoHolder, galleryItem.getUrl());
}
@Override
public int getItemCount() {
return mGalleryItems.size();
}
}
private class FetchItemsTask extends AsyncTask<Void, Void, List<GalleryItem>> {
@Override
protected List<GalleryItem> doInBackground(Void... params) {
return new FlickrFetchr().fetchItems();
}
@Override
protected void onPostExecute(List<GalleryItem> items) {
mItems = items;
setupAdapter();
}
}
}
SingleFragmentActivity.java
package com.example.aster.photogallery;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
public abstract class SingleFragmentActivity extends AppCompatActivity {
protected abstract Fragment createFragment();
@LayoutRes
protected int getLayoutResId() {
return R.layout.activity_fragment;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResId());
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragment_container);
if (fragment == null) {
fragment = createFragment();
fm.beginTransaction().add(R.id.fragment_container, fragment).commit();
}
}
}
ThumbnailDownloader.java
package com.example.aster.photogallery;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class ThumbnailDownloader<T> extends HandlerThread {
private static final String TAG = "ThumbnailDownloader";
private static final int MESSAGE_DOWNLOAD = 0;
private Boolean mHasQuit = false;
private Handler mRequestHandler;
private ConcurrentMap<T,String> mRequestMap = new ConcurrentHashMap<>() ;
private Handler mResponseHandler;
private ThumbnailDownloadListener<T> mThumbnailDownloadListener;
public interface ThumbnailDownloadListener<T> {
void onThumbnailDownloaded(T target, Bitmap thumbnail);
}
public void setThumbnailDownloadListener(ThumbnailDownloadListener<T> listener) {
mThumbnailDownloadListener = listener;
}
public ThumbnailDownloader(Handler responseHandler) {
super(TAG);
mResponseHandler = responseHandler;
}
@Override
protected void onLooperPrepared() {
mRequestHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_DOWNLOAD) {
T target = (T) msg.obj;
Log.i(TAG, "Got a request for URL: " + mRequestMap.get(target));
handleRequest(target);
}
}
};
}
@Override
public boolean quit() {
mHasQuit = true;
return super.quit();
}
public void queueThumbnail(T target, String url) {
Log.i(TAG, "Got a URL: " + url);
if (url == null) {
mRequestMap.remove(target);
} else {
mRequestMap.put(target, url);
mRequestHandler.obtainMessage(MESSAGE_DOWNLOAD, target)
.sendToTarget();
}
}
private void handleRequest(final T target) {
try {
final String url = mRequestMap.get(target);
if (url == null) {
return;
}
byte[] bitmapBytes = new FlickrFetchr().getUrlBytes(url);
final Bitmap bitmap = BitmapFactory
.decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
Log.i(TAG, "Bitmap created");
} catch (IOException ioe) {
Log.e(TAG, "Error downloading image", ioe);
}
}
}
AndroidManifest.java
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.aster.photogallery">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".PhotoGalleryActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
activity_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
fragment_photo_gallery.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/photo_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.aster.photogallery.PhotoGalleryActivity" />
list_item_gallery.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_image_view"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_gravity="center"
android:scaleType="centerCrop">
</ImageView>