Last active
September 11, 2023 02:47
-
-
Save rogerhu/456017c235f78054c63e to your computer and use it in GitHub Desktop.
Google Image Search
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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