Skip to content

Instantly share code, notes, and snippets.

@rogerhu
Last active September 11, 2023 02:47
Show Gist options
  • Save rogerhu/456017c235f78054c63e to your computer and use it in GitHub Desktop.
Save rogerhu/456017c235f78054c63e to your computer and use it in GitHub Desktop.
Google Image Search
# Google Image Search
- SETUP see 0a_recording.txt BEFORE RECORDING
Keys: Cmd-Shift-O and Cmd-Shift-F
Tabs
- Grid image homework
- Project slides for Grid Image Search
- Guides
- https://developers.google.com/image-search/v1/jsondevguide#json_reference
- Android Studio
- http://loopj.com/android-async-http/
- https://github.com/square/picasso
- Emulator running
- In this video I will show you how to create a Grid based Image Search application for Android
- This app will use data from the Google Image APIs
- In this video, I will be showing you a step-by-step tutorial of how to build this app from scratch
- I am not going to spend too much time focusing on UI polish and proper error handling
- When building your own version, I encourage you to create a better user experience.
- Let's take a look at what we will be building in more detail
- [Show the slides from project slide deck via courses portal]
- Mention the filter options should be optional
- Bring up the Google Image APIs
- Point out that the image search API is deprecated but easier to use than the newer one.
- Show how to make API responses and walkthrough the query parameters needed (v=1.0 and q=)
- Create a project called "GridImageSearch"
- Min SDK is 14
- Use Image Icon png
- Name "SearchActivity"
- Click "Finish"
- Click "Run" and see the blank screen in emulator
- 1: Let's create the view layout for our main image search screen
- Drag Text Fields -> Field to upper left corner
- Drag Form Widgets -> Button to upper right
- Try to align with editText
android:layout_alignBottom="@+id/editText1"
android:layout_alignParentRight="true"
- Drag composite -> GridView
android:layout_below="@+id/editText1"
android:layout_alignParentLeft="true"
- Let's take a look at the XML for this
- Note the relative layout rules applied
- Encourage thinking about fully constraining your views (width, height, and alignment)
- Show all how all screens work across all devices
- Mention RecyclerView can do something similar to GridView
- Rename ids
editText => etQuery
button1 => btnSearch
gridView => gvResults
- Fix view properties
Add hint to edittext "Enter search query"
Button text to "Search"
Mention I18n warning using strings.xml (show demo)
- Let's run this and see what it looks like
- Done with xml layout, let's code
- 2: We've finished the laying out the view, let's switch over the java source to setup the events
SearchActivity.java
EditText etQuery;
GridView gvResults;
Button btnSearch;
// oncreate
setupViews();
public void setupViews() {
etQuery = (EditText) findViewById(R.id.etQuery);
gvResults = (GridView) findViewById(R.id.gvResults);
btnSearch = (Button) findViewById(R.id.btnSearch);
}
- Note how to trigger imports using Cmd + Shift + O
- 3: Attach listeners
// xml
add onClick "onImageSearch" to Button
// java
// let's test the click works with a toast
public void onImageSearch(View v) {
String query = etQuery.getText().toString();
Toast.makeText(this, "Searching for " + query, Toast.LENGTH_LONG).show();
}
- Let's run this and see if we are able to click the button and see the message
- 3b: Explore Google Image Search
// Let's start talking about making the network request to fetch this data from the API
// Review documentation for google image search
// show them the google image search json api in browser
https://ajax.googleapis.com/ajax/services/search/images?q=android&v=1.0
// walk through the json response which returns the data
// we want to send a network request to that url from our app to get these search results
// then we can parse the JSON and use that to display the images in the grid
// Remind people that the results only give back 4 by default, show the optional arguments and point out using rsz=8 and start.
- 3b: Load in Libraries
- Go to Courses Portal
- Setup Gradle files:
- http://loopj.com/android-async-http/
- Apply permission to allow internet access:
<uses-permission android:name="android.permission.INTERNET" />
- 3c: Connect image search network request
- add debug breakpoint to confirm we are reading stuff
- make sure to pick the right onSuccess -> want a JSONObject.
- check out helpful hints
client.addHeader("Accept-Encoding", "identity"); // disable gzip
client.get("https://ajax.googleapis.com/ajax/services/search/images?q=" + query + "&v=1.0", new JsonHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
Log.d("DEBUG", response.toString());
}
});
// and then we need to parse this large JSON response into a more easy to work with form
// so we will create a java class to represent each image result
- 4: Load images from API
// Let's query the API, parse the JSON and load all the urls and print them
// Setup a breakpoint on the onSuccess and onFailure.
// Note the differencebetween JSONObject and JSONArray in the onSuccess handlers, remind
// people about the helpful hints guide.
// Fields
ArrayList<ImageResult> imageResults = new ArrayList<ImageResult>();
// onImageSearch method
String query = etQuery.getText().toString();
Toast.makeText(this, "Searching for " + query + "...", Toast.LENGTH_LONG).show();
AsyncHttpClient client = new AsyncHttpClient();
// https://ajax.googleapis.com/ajax/services/search/images?q=Android&v=1.0
client.get("https://ajax.googleapis.com/ajax/services/search/images?rsz=8&" +
"start=" + 0 + "&v=1.0&q=" + Uri.encode(query), new JsonHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
JSONArray imageJsonResults = null;
try {
imageJsonResults = response.getJSONObject("responseData").getJSONArray("results");
Log.d("DEBUG", imageJsonResults.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
});
- 5: Create ImageResult model
// let's create a simple java object to represent an image result
// it's good to try to adhere to a model - controller - view paradigm
// create ImageResult.java
// we want to create a constructor that takes an individual JSON raw item, and
// a method that will iterate through an entire list and call the constructor on each element in the response.
// we eventually want to pass back this data so it can be used to populate an adapter
// note the JSON models guide as the common pattern
// want to do the deserialization of JSON data inside the model using factory methods
public class ImageResult {
// Note these fields are what we want to set from the API response.
private String fullUrl;
private String thumbUrl;
private String title;
public ImageResult(JSONObject json) {
try {
this.fullUrl = json.getString("url");
this.thumbUrl = json.getString("tbUrl");
this.title = json.getString("title");
} catch (JSONException e) {
this.fullUrl = null;
this.thumbUrl = null;
this.title = null;
}
}
public static ArrayList<ImageResult> fromJSONArray(JSONArray array) {
ArrayList<ImageResult> results = new ArrayList<ImageResult>();
for (int x = 0; x < array.length(); x++) {
try {
results.add(new ImageResult(array.getJSONObject(x)));
} catch (JSONException e) {
e.printStackTrace();
}
}
return results;
}
public String getFullUrl() {
return fullUrl;
}
public String getThumbUrl() {
return thumbUrl;
}
public String toString() {
return this.thumbUrl;
}
}
- Now we can use the fromJSONArray method to load the items into the array
// define at the top
ArrayList<ImgaeResult> imageResults;
// in SearchActivity, onImageSearch, onSuccess method
imageResults.clear(); // remove all existing results for a new search
imageResults.addAll(ImageResult.fromJSONArray(imageJsonResults));
- keep your models clean by doing all the parsing inside the models
- create factory methods inside the models, putting responsibility of parsing Json
- remind people about the importance of organizing your files -> create a new activities folder
- Verify using the debugger that the images are now being loaded into the array as items, setup a breakpoint
- 6: Let's display the images in the grid
// Works similar to a ListView in briding data to views.
// Create an adapter ImageResultArrayAdapter which will populate the grid using the array
public class ImageResultArrayAdapter extends ArrayAdapter<ImageResult> {
public ImageResultArrayAdapter(Context context, List<ImageResult> images) {
super(context, android.R.layout.simple_list_item_1, images);
}
}
// Source, override and implement methods
// select getView, by default this tries to call toString and load the value into the textview
// Fields
ImageResultArrayAdapter imageAdapter;
// oncreate
imageAdapter = new ImageResultArrayAdapter(this, imageResults);
gvResults.setAdapter(imageAdapter);
// change to load images into adapter
// onImageSearch
// talk about how you can add/remove the list and notify the adapter via notifyDataSetChanged().
// Show the other way. You can also manipulate content directly through the adapter.
// you can update the ArrayList through the adapter which has many of the methods (add an item, clear). Important
// to understand that making changes to the adapter that it modifies the underlying data and to the ArrayList.
imageAdapter.addAll(ImageResult.fromJSONArray(imageJsonResults));
Note to point of the columns in the GridView is configurable.
// instead of showing text, let's show an image
// need to create our own template to represent the items in the grid
// we should now see a url in every grid item
// let's make it an image instead though
// create a new layout file item_image_result.xml
// choose linearlayout and then drag in an imageview
// linearlayout is simple stacking of elements vertically or horizontally
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/ivImage"
android:layout_width="75dp"
android:layout_height="75dp"
android:scaleType="fitXY"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"/>
</LinearLayout>
// change adapter --> we now want to create a custom adapter
// refer to the adapter guides to remind people the syntax
// ImageResultArrayAdapter
public class ImageResultArrayAdapter extends ArrayAdapter<ImageResult> {
public ImageResultArrayAdapter(Context context, List<ImageResult> images) {
super(context, R.layout.item_image_result, images);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// get the data item for position
ImageResult imageInfo = this.getItem(position);
// check the existing view being used
// not using a recycled view, need to turn it into a convertView that will be used on the row for the screen
if (convertView == null) {
LayoutInflater inflator = LayoutInflater.from(getContext());
// 3 parameters -- 1st item I want to inflate, root is parent, do not want to attach yet
convertView = inflator.inflate(R.layout.item_image_result, parent, false);
}
// Find the image view -> get access to the ImageView within the template
ivImage = (ImageView) convertView.findViewById(R.id.ivImage);
// Clear out recycled image from convertView from last time
ivImage.setImageResource(0);
TextView tvTitle = (TextView) convertView.findViewById(R.id.tvTitle);
// want to populate the title
tvTitle.setText(imageInfo.getTitle());
// remotely download the image in the background
// Load new image into imageview from url using Picasso
// explain Picasso is a library for remote image fetching and caching
Picasso.with(getContext()).load(imageInfo.getUrl()).into(ivImage);
// Return the view
return convertView;
}
}
- recap this is the 2nd time you probably have seen the adapter view
- inflating, finding the subviews in the root layout, returning the completed view to render on screen
- Add [Picasso](https://github.com/square/picasso)
- Notice that stuff shows up HTML
- Show how to use HTML can be rendered through the TextView:
tvTitle.setText(Html.fromHtml(imageInfo.getTitle()));
- Show also how to fix the title and watch to restrict the length of the title.
android:maxLines="2"
android:ellipsize="end"
- 7: want to see full screen image by clicking
// Create new activity, ImageDisplayActivity
// RelativeLayout with just the imageView
// set the hierarchal parent
// want to make sure it's a full screen image
// Change id to "ivResult"
<ImageView
android:id="@+id/ivResult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:maxWidth="600dp"
android:layout_alignParentTop="true"
android:adjustViewBounds="true"
android:src="@android:drawable/screen_background_light" />
// we now have a full image display
// Hook up listener for grid click
// SearchActivity, onCreate
gvResults.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapter, View parent, int position, long rowId) {
// Create an intent to display full screen image
// normally you can pass in this, but in this case inside an anonymous class.
// you want to specify the activity itself...or you can use getApplicationContext()
Intent i = new Intent(getApplicationContext(), ImageDisplayActivity.class);
// Get the image result to display
// we now have access to the image
ImageResult imageResult = imageResults.get(position);
// Pass image result into intent
i.putExtra("url", imageResults.get(position).getUrl());
// Launch the new activity
// time to review the intent cliff notes!
// a common pattern to pass data between activities
startActivity(i);
}
});
// Load url into imageview in other activity
// ImageDisplayActivity, in onCreate
// pull out the URL
String url = getIntent().getStringExtra("url");
// find the image view to grab access
ImageView ivImage = (ImageView) findViewById(R.id.ivResult);
// use Picasso to load the image
Picasso.with(this).load(url).into(ivImage);
- 8: serialize the ImageResult instead
// Right now we are just passing the URL to display to the image display activity
// What if we want to display other information about the result as well such as the title?
// In that case, we might want to pass the entire result object in the intent through to the other activity
// change intent to serialize result
Intent i = new Intent(getApplicationContext(), ImageDisplayActivity.class);
i.putExtra("result", imageResults.get(position));
startActivity(i);
// show the demo and demonstrate the click listener works!
// Serialize ImageResult
// so we can add it to the bundle directly
// show off what happens if we use putExtra with the object.
// somehow these objects they have to packable. They have to inherit from Serializable or Parcelable.
// much easier right now, go to ImageResult and implement a simple interface
// makes the object able to be encoded into the bundle
public class ImageResult implements Serializable {
// change photoviewactivity, onCreate
ImageResult result = (ImageResult) getIntent().getSerializableExtra("result");
ImageView ivImage = (ImageView) findViewById(R.id.ivResult);
Picasso.with(this).load(result.getFullUrl()).into(ivImage);
// turn off getSupportActionBar().
- 9: Conclusion
- We have completed the most basic version of the grid image search application
- Using async http client to get json from the network
- Parsed the json response
- Used a java object (model) to store the image result data
- Using a custom adapter and overriding GetView to show items
- Using a gridview populated via our adapter
- Loading the images asynchronously using Picasso
- Think about how to add pagination
- Only 8 results right now displayed, but you need to add infinite pagination (note that Google only gives 64 max)
- Check out http://guides.codepath.com/android/Endless-Scrolling-with-AdapterViews cliffnotes
- You'll need to modify the query string with different start parameter to retrieve more results
- As the user scrolls the gridview, the new network request will be called and those results will be appended
- Homework is to also add a settings page with filters
- Show filters in the project slide deck
- You need a separate area which has the different filters (size, color, type)
- When "Save" is pressed, these filters need to be passed back to the Search activity
- Filters then get added into the url query string when the request is sent out
- Take a look at the docs for the google image API for how to add those filters
- Point out the optionals such as checking if Internet is available, use the ActionBar SearchView, share an image, replace filter with a modal
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment