-
Google Glass is a headset you wear like a pair of eyeglasses, except it has optical display.
Not only does it make you feel like a bad-ass Terminator, but you can make apps for it too!
In this tutorial, you’ll get a head start with Google Glass development by learning how to make a simple Google Glass shopping list app. In the process, you’ll learn:
- The difference between Simple Live Cards and Immersion Activities
- Using Google’s GDK (Glass Development Kit)
- Testing on the device
- How to change the app’s theme
- How to create custom voice commands
Come with me if you want to live – in the world of Google Glass!
Note: This Google Glass app tutorial assumes you know the basics of Android development. If you are new to Android development, check out our Android Tutorial for Beginners series first.Developing for Google Glass
Google Glass development is unlike any other kind of mobile development.
Because the device is on your face and not in your hand, touch isn’t the main source of input. Imagine tapping, pinching and swiping on a pair of glasses; you’d have to clean them every two seconds!
Google Glass is meant to free your hands, and as such, voice input is the main source of interaction with Glass apps.
Another distinct difference is that because the display is so much smaller than hand-held devices, you have less real estate to work to show information. As a result, user interfaces tend to be plain and simple.
You may have heard that Google is no longer accepting new “Explorers” to purchase early copies of Google Glass. Although this means you can’t get access to a Google Glass if you don’t have one already, the project is far from dead.
Think of it as Google Glass’s way of saying “I’ll be back.”
There’s been a recent change of leadership and direction, so you can expect Glass 2.0 to surface within the next year or two. Wearables are the future of technology, and Glass is anything but a thing of the past.
Note: Because there is no simulator, you’ll need an actual Google Glass to test your work. If you don’t have one, you can at least read along to see what’s involved :]
Getting Started
Google Glass runs a modified version of Android, and much of the code and many of tools you’ll use are the same.
Install Android Studio by following the instructions in this Android for Beginners tutorial.
When you launch Android Studio for the first time, the main menu will come up. By default, Android Studio doesn’t install all the tools necessary for Glass development.
To download these tools, press ConfigureSDK Manager. Under Android 4.4.2 (API 19), choose SDK Platform and Glass Development Kit Preview. Deselect everything else.
Press Install 2 packages and accept the license for each tool. Once downloading finishes, close out of the SDK manager.
Two Types of Glass Apps
There are two different types of Google Glass apps:
- Live Cards exist inside the timeline, so the user doesn’t have to enter or exit the app. They are minimally dependent on user interaction. One example is Google+, which uses a Live Card to show posts on the timeline.
- Immersion Activities take up the entire Glass display and require the user exit to return to the main timeline. These behave more like apps for your phone.
You’ll be creating an Immersion Activity in this Google Glass app tutorial.
Note: If you worked through our WatchKit book or tutorials, you may notice that these two different types are a little familiar; they very much resemble WatchKit’s apps and glances!Interactions In the Moment
Though they are rivals, Glass apps are remarkably similar to Apple Watch apps. From a design perspective, both primarily show white text on a black background. Both platforms allow for limited require user interaction.
In the Apple Watch Human Interface Guidelines, Apple states, “If you measure interactions with your iOS app in minutes, you can expect interactions with your WatchKit app to be measured in seconds.”
The user will not be using your app for very long, so you need it to be fast, simple and straightforward. This is why I say Apple Watch and Google Glass have a lot in common.
Google’s Glass Design Principles state: “Glass works best with information that is simple, relevant and current.” Glass is meant to be in the moment, not stuck in the past hour, month or year.
What you can take away about wearable tech is it’s not meant to have complex apps that do everything. Wearable demands short, frequent and periodic use in order to show the user current, relevant information.
So let’s get get going on creating your Google Glass app!
Creating the Project
On the Welcome screen, select Start a new Android Studio project.
Name the application “Shopping List” and press Next.
This next screen is where you’ll choose what devices you want to run the app. Deselect the default Phone and Tablet and select Glass.
As mentioned before, you’ll be creating an Immersion Activity. The next page of the project setup lets you select this option.
Keep the default Activity Name, MainActivity, but change the Activity Title from Hello World Immersion toShopping List. This title is what shows in the user’s list of activities.
When you’re done, press Finish.
Running your Glass Activity
Before you can actually run activity on your Glass, you first need to enable testing on the device. On your Glass, go to SettingsDevice InfoTurn on debug. Now you’re ready to make some magic.
Plug your Google Glass into your computer’s USB port. You’ll hear a dinging sound come from the Glass when it is connected properly.
In Android Studio, press the Run button on the toolbar, which is the right arrow to the left of the bug icon.
An Edit Configuration screen will appear. Make sure that your settings on this screen are the same as below:
- Module: app
- Package: Deploy default APK
- Activity: Launch default Activity
- Target Device: USB Device
Press Run. If a pop-up informs you that Configuration is still incorrect, select Continue Anyway.
Android Studio will now build and run the app on your device. Go you! Your app is now running on your Google Glass.
When you first run it, it won’t show up on screen automatically; you have to have a chat with it. So, while wearing the Glass, say “OK Glass,” then ask it to “Show me a demo.” This phrase is the default voice trigger that launches your app.
Your screen should now look like this:
Basic Project Settings
Now take Glass off and get back to work. :]
The template project is not ready for real-time. You didn’t think we’d let you off that easily in this tutorial, did you? It’s missing some pretty important pieces of code.
Open AndroidManifest.xml, which you’ll find in the manifests directory. Under the <manifest> tag and above the <application> tag, add this line of code:
<uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />
That code requests permission to test new, unlisted voice commands in your app.
Note: This line of code is for development purposes only. To launch Glassware with unique commands, you’ll need to submit a new voice command for approval before you release the app.
Open voice_triggers.xml (in the res/xml folder). This file declares the voice command that launches your Glassware.
When you first run the app, you needed to say, “OK Glass, show me a demo.” That command is currently declared in this file.
I’m going out on a limb here and guessing that you don’t want the user to launch your app by saying, “Show me a demo.”
For a more contextual trigger, simply change this line to the following:
<trigger keyword="@string/app_name" />
This changes the starting voice trigger to be the same as your app’s name. So in this case, the trigger is “Shopping List.” Note that
commandis replaced bykeyword.Another thing that sticks out is the strange appearance of the app. Google Glass apps are generally white text on a black background. However, if you look at the app you have, it’s just the opposite.
In order for the app to look normal, you’ll need to specify what theme to use. Open res/values/styles.xml and look for this line:
<style name="AppTheme" parent="android:Theme.Holo.Light"></style>
Currently, the app uses the
Holo.Lighttheme, which is more appropriate for the screen of an Android phone or tablet than it is for Glass.Light backgrounds tend to make it more difficult to see the content because the screen is semi-transparent, so the text can get lost in background. Besides, your app will look like a bit of an oddball when compared to other apps.
Replace the above line with the following to implement the default theme.
<style name="AppTheme" parent="android:Theme.DeviceDefault"></style>
Now, run the app again on your Google Glass. Notice how the launch command is no longer Show me a demo, but Open Shopping List instead. Also, when you open the app, it should now be a little easier on the eyes.
Foundation of Voice Commands
Although you just told Android that your app will test your custom voice commands, each individual activity that uses them must explicitly request permission to use them.
In MainActivity.java, find
onCreate(Bundle bundle). Right below the call tosuper.onCreate(bundle);, add the following line:getWindow().requestFeature(WindowUtils.FEATURE_VOICE_COMMANDS);
You need to import
WindowUtils, so add this line of code at the top of the file:import com.google.android.glass.view.WindowUtils;
Alternatively, you can press Option-Enter and Android Studio will automatically add the import.
Next, you need to declare which voice commands your app must listen to. Glass apps practice selective hearing unless you force their hands, very much like teenagers. :]
Open strings.xml inside res/values. It should predefine the following values:
<string name="app_name">Shopping List</string> <string name="title_activity_main">Shopping List</string> <string name="hello_world">Hello world!</string>
For your shopping list, you’ll need two basic functions: the ability to add and remove an item from the list.
Add the following lines under the
"hello_world"declaration:<string name="add_item">Add item</string> <string name="remove_item">Remove item</string>
On its own, these declarations don’t mean a thing. They only give you a constant string that can be referenced anywhere in the project so you don’t accidentally type in the wrong value.
When using Google Glass, one phrase that you use frequently is “OK Glass.” This is the command that tells your device to listen up. The commands that are available afterwards depend on how the app was designed or context.
Menus are created using XML. (I promise this is the last XML you’ll have to look at!) In the Project Navigator, select res. Press ⌘N (or FileNew) and select Directory. When prompted for the name of the directory, entermenu.
Again, select this new directory and press ⌘N. This time, select Menu resource file. Name the fileactivity_menu.
Replace everything beneath the XML version header line with the following:
<menu xmlns:android="http://bit.ly/1I9HKuD"> <item android:id="@+id/add_menu_item" android:title="@string/add_item"> </item> <item android:id="@+id/remove_menu_item" android:title="@string/remove_item"> </item> </menu>Not only will this file let you create a menu with these two voice commands, it also generates a unique identifier for each one to help you figure out which command was used. This way, you know whether the user wanted to add or remove an item from the list.
For the app to know that you want these options in the “OK Glass” menu, you need to inflate the menu in two designated methods.
To inflate a menu means that you convert an XML file into a Menu object.
In MainActivity.java, add the following method after
onCreate():@Override public boolean onCreatePanelMenu(int featureId, Menu menu) { if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS){ getMenuInflater().inflate(R.menu.activity_menu, menu); return true; } return super.onCreatePanelMenu(featureId, menu); }
At the top of the file, import the following file, or use Option-Enter to automatically import:
import android.view.Menu;
These methods inflate your menu options into the
menuparameter, so that it knows what voice commands are available.Run the app on your Google Glass. When you get into the app, say, “OK Glass.” A menu with the options Add Item and Remove Item will appear.
Try to use the Add Item command. Glass will recognize the voice command, but nothing will happen. Why is that?
You’ve not yet told the app what to do when it hears the voice command.
Asking User for an Item
After the user triggers a voice command,
onMenuItemSelectedis called. Add the following code underonCreatePanelMenu:@Override public boolean onMenuItemSelected(int featureId, MenuItem item) { // 1 if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS) { // 2 Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); // 3 startActivityForResult(intent, item.getItemId()); return true; } return super.onMenuItemSelected(featureId, item); }
At the top of the file, import these classes (or do three Option-Enters):
import android.content.Intent; import android.view.MenuItem; import android.speech.RecognizerIntent;
It’s short and sweet, but there’s quite a bit of action in those three lines:
- First, you check to make sure that the feature ID is for voice commands – this means the menu item was selected from the “OK Glass” menu.
- When the voice commands are called, this finds out what you want the app to do. If the user wants to add an item, you need to find out what that item is! In order to do this, you create an Intent.
- This Intent, when started, shows the “Speak your message” screen with the microphone on the right side, a screen most Glass users know well. After the user dictates the item they want to add (or remove), you’re able to use the transcribed text in your app.
Depending on the action you need to call when the intent is completed, you need to pass a different constant.
When the intent completes, it gives you back the constant you pass in; from there, you can act according to which action you originally intended.
But you’ve already thought of this! The way you set up activity_menu.xml has already defined a unique constant for each action. This item ID is attached to the MenuItem parameter in the method. You launch the intent using
startActivityForResult, and pass the MenuItem’s ID as the second parameter.If the feature isn’t for voice commands, then it is best practice to pass on the method call to the superclass.
Run and launch the app and say “OK Glass,” followed by “Add Item.” A new screen will appear that lets you say the item you want to add.
You know you’re craving a sugar rush right about now, so why not tell Glass to add a 3 Musketeers or your favorite candy bar to the list?
Note: Glass needs a working network connection to understand voice commands.
But when you try to add an item nothing in the app changes. This makes sense because you haven’t set anything to use the dictated text. Seems that you won’t be getting your sugar fix until you work through this tutorial.
Even though it’s empty, your shopping list is almost done! All that you have left to do is save and load some items in the list.
Saving & Retrieving Items
Select the MainActivity file in the Project Navigator (this makes sure your new file goes into the right group) then go to FileNew… and create a new Java class named DataManager. Replace everything in the file after the
packagestatement with the following:import android.content.Context; import android.content.SharedPreferences; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Set; //1 public class DataManager { private Context context; private static final String StoredStringsKey = "com.raywenderlich.shoppinglist.storedstringskey"; private static final String PreferencesLocation = "com.raywenderlich.shoppinglist"; public DataManager(Context c){ context = c; } //2 public ArrayList<String> getStoredStrings(){ SharedPreferences preferences = context.getSharedPreferences(PreferencesLocation, Context.MODE_PRIVATE); Set<String> stringSet = preferences.getStringSet(StoredStringsKey, Collections.<String>emptySet()); return new ArrayList<>(stringSet); } //3 public void setStoredStrings(ArrayList<String> strings){ SharedPreferences preferences = context.getSharedPreferences(PreferencesLocation, Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); Set<String> stringSet = new HashSet<>(strings); editor.putStringSet(StoredStringsKey, stringSet); editor.apply(); } }This code creates a utility class that saves and retrieves the user’s shopping list items. Here’s a breakdown of what’s going on:
- The Context is a way for this class to access the
SharedPreferencesfor this application, and this variable is set in the constructor (the publicDataManager(...) code). SharedPreferencesis a key-value store, and it acts like a dictionary — if you’ve ever used Cocoa’s NSUserDefaults it will look a little familiar. The key for the stored values isStoredStringsKey, and the values are set in thesetStoredStrings function.- When you want to get these values back in
getStoredStrings()this just asks theSharedPreferences, “Hey, what are the values that are attached to the string here inStoredStringsKey?”
Replace the two occurrences of “com.raywenderlich” with whatever identifier you used when you created the project.
Open MainActivity.java. After the declaration of
onMenuItemSelected, add the following method:protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == R.id.add_menu_item && resultCode == RESULT_OK) { // 1 } else if (requestCode == R.id.remove_menu_item && resultCode == RESULT_OK) { // 2 } super.onActivityResult(requestCode, resultCode, data); }onActivityResultis called when the speech-recognizing Intent completes. The Intent usually completes with a result code ofRESULT_OKofRESULT_CANCEL. You only care about what happens if the Intent completes successfully, so in both cases, you check to make sure thatresultCode == RESULT_OK.requestCodeis the same code that you gave the Intent when you calledstartActivityForResult, which is eitherR.id.add_menu_itemorR.id.remove_menu_item.If the code is
R.id.add_menu_item, you want to take the item that the user dictated and add it to the strings stored in the DataManager class.Add the following code at // 1:
//Part A List<String> results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); String spokenText = results.get(0); //Part B DataManager dataManager = new DataManager(getBaseContext()); ArrayList<String> storedStrings = dataManager.getStoredStrings(); storedStrings.add(spokenText); dataManager.setStoredStrings(storedStrings); //Part C mView = buildView(); mCardScroller.getAdapter().notifyDataSetChanged();
Again, at the top of the file import these classes:
import java.util.ArrayList; import java.util.List;
A lot of this code should look new to you, so let me explain each part:
- Part A: Take the Intent and get the spoken text from its “extra results.”
- Part B: Create a new DataManager instance using the base context. After you get a copy of the strings already stored in the DataManager, you add the spoken text to this copy. Then, you set the DataManager’s stored strings to the new values.
- Part C: You update the view to show the new data.
Now, add the following code at //2:
List<String> results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); String spokenText = results.get(0); DataManager dataManager = new DataManager(getBaseContext()); List<String> storedStrings = dataManager.getStoredStrings(); if (storedStrings.contains(spokenText)){ storedStrings.remove(spokenText); dataManager.setStoredStrings(new ArrayList<>(storedStrings)); } mView = buildView(); mCardScroller.getAdapter().notifyDataSetChanged();
This code is similar to the code added at // 1, except that you remove the spoken text instead of adding it.
What About the UI?
Both of these code snippets end by updating the user interface. Because you haven’t really looked at that yet, let’s look through the automatically generated code that shapes the app’s appearance.
Inside the
onCreateimplementation, you have the following code:mView = buildView(); mCardScroller = new CardScrollView(this); mCardScroller.setAdapter(new CardScrollAdapter() { @Override public int getCount() { return 1; } @Override public Object getItem(int position) { return mView; } @Override public View getView(int position, View convertView, ViewGroup parent) { return mView; } @Override public int getPosition(Object item) { if (mView.equals(item)) { return 0; } return AdapterView.INVALID_POSITION; } }); mCardScroller.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // Plays disallowed sound to indicate that TAP actions are not supported. AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); am.playSoundEffect(Sounds.DISALLOWED); } }); setContentView(mCardScroller);The app gives you two variables from the get-go:
mViewandmCardScroller, andmViewis just a generic view.mCardScrolleris a CardScrollView. If you have multiple views that should be displayed side by side, you change the adapter methods, which are located in thesetAdapterblock, to display these views. The individual views shown in a CardScroller are called “cards.”Next, you call
setOnItemClickListeneron yourmCardScroller, and the code inside the block plays an alert to inform the user that tapping is not allowed in this app. When users tap, they hear the sound and that’s it.Finally, you set the content view to be the card scroller, and it only has one view —
mView— so that is the view/card that shows on-screen.When you create the
mView, you callbuildView(). Jump down to the implementation ofbuildView()to see how this is implemented:private View buildView() { CardBuilder card = new CardBuilder(this, CardBuilder.Layout.TEXT); card.setText(R.string.hello_world); return card.getView(); }
Hello world? That’s not a 3 Musketeers. You want to show the list of items, not a default “Hello World!”
Replace this method with the following:
private View buildView() { // 1 CardBuilder card = new CardBuilder(this, CardBuilder.Layout.TEXT); // 2 DataManager dataManager = new DataManager(getBaseContext()); ArrayList<String> strings = dataManager.getStoredStrings(); // 3 StringBuilder builder = new StringBuilder(); if (strings.size() == 0){ builder.append("No Items!"); // 4 } else { for (String s : strings) { builder.append("- ").append(s).append("n"); } } // 5 card.setText(builder.toString()); return card.getView(); }
Let’s review how this works:
- First, you create a new CardBuilder with a Text layout. For reference, CardBuilders generate cards or views, for your CardScroller to present.
- The next two lines of code should look familiar; you’re simply getting the stored strings from the DataManager.
- Next, you instantiate a new StringBuilder instance to create the card’s text. If there’s nothing in the shopping list, then you want to show “No Items!”
- Otherwise, you create a bulleted list comprised of each string in the shopping list by adding a hyphen, the item and then a new line.
- Finally, you set the CardBuilder’s text to the string you just created, and return the CardBuilder’s view.
Run your Shopping List, and you’ll find everything working as expected!
In the Shopping List, speak “OK Glass… Add Item… Samsung Galaxy Gear.” Someone has to want one, right?
Now, the app should show this screen, with the smart watch in the list:
After thinking about it a little bit, you realize that you really want an Apple Watch. Tell your Glass, “OK Glass… Add Item… Apple Watch.” Your list should have two items in it, as shown below:
Now you have two smart watches in your list at once! Maybe you should reconsider that Samsung.
Tell your Glass, “OK Glass… Remove Item… Samsung Galaxy Gear.” Make sure that you say the item’s name exactly as it appears in the app, because otherwise it will stay on the list.
Your shopping list is now completed — congratulations on creating your very own Google Glass app!
Where To Go From Here?
Here is the download for the final project.
If you’re interested in learning more about Glass development, check out Google’s Glass Developer page.
Google also has a Glass Github page with several really cool sample projects that I recommend you check out.
Challenge: You noticed that if you have too many items in the sample app, you cannot see the last few items on the list. To keep the tutorial more general and approachable, I did not address this issue.There is an open-sourced control that you can implement, called HeadScrollView. This view scrolls when the user raises and lowers his/her head. If you’re up for a challenge, try it out and let me know how it turns out in the comments.New platforms are always fun to start developing for, and Glass is no exception. I hope you had fun and learned a lot!
Do you have questions about this Google Glass app tutorial – or do you just want to share your favorite Terminator movie? Either way, post it to the comments below! :]
The post Google glass app Tutorial appeared first on Codzcook.
-
In this final part of the tutorial, you’ll learn how to leverage the powerful capabilities of the web to search and display data and images. More specifically, you’ll make an app that searches the Open Library API — a database of over 20 million books —, displays information and cover images of the books it finds, and allows you to recommend books to friends!
When you’re done, you’ll know how to:
- Add powerful third-party libraries to your app via Gradle;
- Access a typical RESTful API;
- Understand JSON format and parse JSON results;
- Responsibly download web images without affecting your UI performance;
- Customize list cells with multiple views; and
- Build intuitive native app navigation between activities.
These are very useful and transferable skills for all sorts of Android apps you’ll want to make in the future.
To begin this part of the tutorial, you should be at the point where you have an app that takes user input, lists names, and shares messages through social networks. Your app should also ask for your name when you first open the app and greet you by name thereafter. The final version of the source from Part 2 is also available on GitHub or as a .zip.
So the personal part is done — now it’s time to interwebify!
Getting Started
It’s time to do a bit of rebranding. No longer is this just a little demo app — it is a book search and recommendation engine!
The default Android icon can only take you so far. Download the following files and drag them onto thesrc/main/res/drawable-hdpi directory to add them to your project:
Click OK when asked to confirm the move.
Your src/main/res/drawable-hdpi directory should now look something like this:
Note: When importing your images you may receive an error saying “Refactoring cannot be performed when importing images”. If that is the case try to drag your images into the project again whilst holding the Alt key to resolve it.Only thehdpiassets are provided here. For future projects, it is a good idea to add assets for the other dpi values as well.If you have trouble finding the hdpi folder in the drawables folder. Studio may be defaulting to it’s “Android” project view structure. Change this to “Project” by clicking on the large button to the left just above the project hierarchy on the left. Once you have imported the images you can change back if you prefer
Open AndroidManifest.xml. As you recall, this is “the boss” of your app. If you want to change the app’s icon, you need to talk to the boss.
Find the opening
applicationtag and change theiconattribute line from:android:icon="@drawable/ic_launcher"
To:
android:icon="@drawable/ic_books"
From now on, the app icon will be a stack of books instead of the default Android icon. Depending on your Studio version (and/or SDK version) you may also see that the
applicationhas an attribute for setting the application name. You’re going to update the application name as well, but not in the manifest.First, while you’re still looking at the manifest, you need to let the manifest know that you plan to start accessing the Internet. Between the
uses-sdkandapplicationtags, add the following:<!-- NEED TO ADD TO BE ABLE TO GO ONLINE AND GET DATA --> <uses-permission android:name="android.permission.INTERNET"/>
If you don’t let “the boss” know your plans, it won’t file the appropriate paperwork with Android to make the web call happen. But now you’re good to go.
Now it’s time to change your app’s name. Open res/values/strings.xml and replace the strings for everything except
action_settingswith the following new values:<string name="app_name">Bookmaster General</string> <string name="textview">Search For Books!</string> <string name="button">Search</string> <string name="hint">Title and/or Author</string>
That’s right — I used the title Bookmaster General.
Finally, remove this line from
onCreatein MainActivity.java:mainTextView.setText("Set in Java!");Run your app, your device or emulator’s home screen should reflect the name and icon updates. For example:
Now that you’ve rebranded your app, it’s time to start adding the web interactions!
Networking Considerations
There are a lot of things to keep in mind when adding networking capabilities to your app.
For example, you need to consider how to keep all the network interactions off the UI thread, so that the user can continue to use your app without everything locking up until a download completes. If you were using an app and it became completely unresponsive for lengths of time, you’d get pretty frustrated!
You will also find Android will ask your user if they would like to quit an unresponsive App, which isn’t great for your App if people are scrambling to leave it as quick as they can.
Thankfully, you can solve issues such as this by simply using third-party libraries. These have been specially designed and supported by Android experts to facilitate networking. Some of the most well-regarded includeRetrofit, Volley, and Android Async Http. For the purposes of this tutorial, you’ll use Android Async Http.
Image downloads are also a consideration since each image takes time to download. Also, in the case of a list of books, if you have your list set to download images as needed, you might find that you end up downloading the same image over and over as your user scrolls through the list of items. You really don’t want that type of behavior. You’ll use another third-party library called Picasso to manage image downloads for you.
Note: The old-fashioned way to incorporate third-party libraries into your code was to download a zipped-up .jar file, copy it into your code and then include it in your project’s build path. It’s not too difficult, but the larger inconvenience is what to do when the library updates, or if you need to share your project with teammates.A way to easily manage your project’s dependencies would sure be great! That leads us to Gradle.
A Glance at Gradle
When you created your project in Android Studio, you may remember mentions of Gradle and Maven. Refer toPart One if you need a reintroduction. Now you’ll see them in action.
Open build.gradle. Note that there are two
build.gradlefiles in your project. You want the one that’s within theappfolder – not the one at the project root level:Some of the stuff happening in here is beyond the scope of this tutorial, but if you become serious about Android development, I recommend looking into Gradle a little more, starting of course, with the Gradle website. For now, just notice that the Android plugin is being applied to the project (
apply plugin: 'com.android.application').Scroll down to the area labeled
dependencies. There may already be a support library listed there, or it may be empty. You’re going to add two new libraries.Add the libraries like this:
dependencies { ... // there may or may not be a support library above these compile 'com.loopj.android:android-async-http:1.4.4' compile 'com.squareup.picasso:picasso:2.1.1' }Then find the Sync Project with Gradle Files button on the Studio toolbar and press it. It looks like this:
Note: If you get an error that says “requires compiling with JDK 7″, you should upgrade your SDK to JDK 7. You can download it here, then follow the prompts to set your SDK folder to/Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/Contents/Home.Believe it or not, that’s it! Just like that, you’ve included the Android Async Http and Picasso libraries in your project and you can start using them whenever you like.
It’s so easy because both of these libraries are available via the Maven Central Repository, to which your project already contains a reference. You can see this in your root folder’s build.gradle file, it looks like this.
repositories { jcenter() }So, when you tell Gradle which libraries you’d like to use, it simply grabs them from the source and you’re good to go.
Note:jcenter()is a Gradle method that connects to a repository which is a superset of the Maven Central Repository. In early versions of Android Studio the default repository set by Gradle was Maven Central, you still change it back by changingjcenter()tomavenCentral().jcenter however is thought of as being faster and more responsive whilst still providing all the libraries available on the Maven Central Repository. So lets leave this alone.If you need to include any libraries which are not available on the Maven Central Repository, then you’d still have to go through the old school method of copying the source (or the library) into your project. But most of the time, you won’t have to go through all that pain since the Maven Repository contains a lot of third-party libraries for you to use. So you’ll probably be able to find an alternative to the library you’re interested in. If interested, you can even browse all the libraries available on the Maven Repository.
JSON Basics
Great! Now it’s time to meet your datasource: the Open Library API. It’s a constantly-updated database of books, searchable by author and title. The wealth of data is enormous!
Try this query as an example: http://bit.ly/1QtwCMQ
This is a relatively simple URL to understand; whatever is typed in after the
?q=is the query string that will be used to search the database. Feel free to change it to a different author name/title and compare results, remembering to use+to separate words.The result is in the form of a large JSON response. Take a minute to look around at the sort of data the response includes.
If you’re not familiar with the JSON format, I suggest a quick glance through the JSON page. The basics are that there are two ways data can be arranged in JSON: arrays and objects.
JSONArrayslist objects of the same type. For example, a list of books might look like this:["The Hunger Games", "Harry Potter and the Sorcerer's Stone", "A Game Of Thrones"]JSONObjectsare a collection of key-value pairs. For example, a very simple object describing a book might be:{"title" : "The Hunger Games", "author_name" : "Suzanne Collins"}The results from your queries to the Open Library API are really just expansions on those two basic structures. They are larger, and nested into several levels, but the ideas remain the same.
Creating a Query
Now that you know roughly what to expect from the datasource, it’s time to set up the code to go make a sample query!
Add the following variable to your growing list at the top of MainActivity.java:
private static final String QUERY_URL = "http://bit.ly/1QtwCMR";
The above is simply a reference to the URL you’ll be calling. It’s much better to hold this URL as a static
String, so you don’t have to go searching through your code for it. Remember that you’re going to append the search string after the?q=.Next, add this new method anywhere in your MainActivity.java Class:
private void queryBooks(String searchString) { // Prepare your search string to be put in a URL // It might have reserved characters or something String urlString = ""; try { urlString = URLEncoder.encode(searchString, "UTF-8"); } catch (UnsupportedEncodingException e) { // if this fails for some reason, let the user know why e.printStackTrace(); Toast.makeText(this, "Error: " + e.getMessage(), Toast.LENGTH_LONG).show(); } // Create a client to perform networking AsyncHttpClient client = new AsyncHttpClient(); // Have the client get a JSONArray of data // and define how to respond client.get(QUERY_URL + urlString, new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject jsonObject) {} @Override public void onFailure(int statusCode, Throwable throwable, JSONObject error) {} }); }
queryBookshandles the network call to the API. It encodes the inputsearchStringinto URL format, then appends that to the base URL you specified at the top of the class.Calling
new AsyncHttpClient()simply creates an instance of the HTTP client. It’s got a lot of great methods built-in, but the only one you need here isget(String url, ResponseHandlerInterface responseHandler).The
getmethod takes in two parameters:String urlis simply the URL from which you’d like to fetch data. This parameter will be made up of base URL defined at the top of your class, plus the search string you will add later.JsonHttpResponseHandler, which you define now, even though you don’t know whether the network call will succeed or fail, or how long it will take either way. It contains methods calledonSuccessandonFailureto respond to the two cases once the handler does get a response from the server.
You might have noticed that
onSuccessandonFailureare currently method stubs with no code. Fix that by fleshing out theonSuccessto match the following:@Override public void onSuccess(JSONObject jsonObject) { // Display a "Toast" message // to announce your success Toast.makeText(getApplicationContext(), "Success!", Toast.LENGTH_LONG).show(); // 8. For now, just log results Log.d("omg android", jsonObject.toString()); }Next, implement
onFailureas follows:@Override public void onFailure(int statusCode, Throwable throwable, JSONObject error) { // Display a "Toast" message // to announce the failure Toast.makeText(getApplicationContext(), "Error: " + statusCode + " " + throwable.getMessage(), Toast.LENGTH_LONG).show(); // Log error message // to help solve any problems Log.e("omg android", statusCode + " " + throwable.getMessage()); }
In both cases, you simply present a
Toastand log the results. Soon, though, the success case will get a lot more exciting.Making the API Call
queryBooksis a great method and all, but it still needs to be hooked up to theEditTextandButtonto complete the search capability.Fortunately, that’s pretty simple. In MainActivity.java, find
onClickand replace everything inside the Method with this:// 9. Take what was typed into the EditText and use in search queryBooks(mainEditText.getText().toString());
Now, every time the user taps the button, this method takes the user’s input in the
EditTextcontrol and queries the Open Library for books and authors matching that string. Run your app and try it out to see what happens!Note: If Studio runs into any issues with its Gradle sync, you may get aNoClassDefFoundErrorupon querying. If so, not to worry — simply select Build > Rebuild Project and try again!Remember that you haven’t yet hooked up the results to anything that will display them on the screen. You will still see something happening though, open LogCat and you can see the resulting JSON spilled out whenever the API call finishes.
This is already exciting and clearly has the potential to be something cool very soon! But it’s still just a jumble of data. Your next challenge, then, is to display this data on the screen in a more organized manner.
Creating the List Rows
First, you need to set up the layout for each of the rows in your list. A simple row of text won’t cut it anymore — you need space for a thumbnail image on the left and then two rows of text for the title and author, respectively. A layout which would look something like this:
Right-click on the res/layout folder in the Studio left pane, and select New > Layout resource file.
Name your file row_book.xml with a root element of
RelativeLayout, then click OK.The new file will open in Design mode. So, switch to Text mode, as before. Now change the
layout_heightattribute from this:android:layout_height="match_parent"
To this:
android:layout_height="75dp"
You simply set the layout to have a specific height instead of matching the height of the parent container.
Next, you’re going to add three views inside the
RelativeLayout, and then you’ll witness a few of the capabilities of this type of layout in action. First, add the thumbnail view:<ImageView android:id="@+id/img_thumbnail" android:layout_width="50dp" android:layout_height="50dp" android:layout_marginLeft="25dp" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:scaleType="centerInside"/>The
ImageViewhas the width and height set and there’s a little margin to the left of the picture. This is stuff you’ve seen before.Then it gets interesting with
layout_alignParentLeftandlayout_centerVertical. These attributes are available since thisImageViewis a child of aRelativeLayout. Because of these two attributes, theImageViewstays tight to the left of the cell with that margin intact, and centers vertically.The last attribute,
scaleType, specifies how you’d like the image to display within the amount of space it’s given. Especially given the unpredictable list of screen sizes you’d have to support, it’s often important to set this beforehand.Using
centerInsidemeans that you’ll preserve the aspect ratio of the image and that both dimensions will fit inside the given space. You might, however, have some blank space above and below the image if it’s too short, or space on the sides if it’s too thin. If you’re interested, you can read up on the variousScaleTypeoptions.Next, add a
TextViewfor the book’s title:<TextView android:id="@+id/text_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="25dp" android:layout_toRightOf="@+id/img_thumbnail" android:layout_alignTop="@+id/img_thumbnail"/>Read through the XML first and see if you can tell what’s going on. The commands should be starting to make a bit of sense by now. The only thing that might give you pause might be the
@+id/img_thumbnailbit – but that’s just a reference to another control by ID. In this case, the ID refers to theImageViewyou added previously.Basically, the title will sit to the right of the thumbnail, such that the top of the title will be at the same height as the top of the thumbnail. There’s some space in between the two controls, as well.
Finally, add the
TextViewfor the author’s name:<TextView android:id="@+id/text_author" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/text_title" android:layout_alignLeft="@+id/text_title"/>By now, these attributes should all make sense. The author’s name will be below the title, with its left side aligned to that of the title.
Note: One quick thing before moving on. Attributes for children ofRelativeLayouts often reference other children using IDs, as you saw just now. Make sure that the references don’t get circular (two attributes that depend on each other), or else your XML won’t inflate!Adapting JSON for a ListView
In Part Two, you made a
ListView, at which point I mentioned thatListViews are a bit picky. They don’t want to deal with the data directly — you can hardly blame them after seeing that confusing JSON response that popped into LogCat earlier. The simple, built-in adapter you used in Part Two won’t cut it here; you need a custom one.Right-click on the com.example.omgandroid folder (or whatever package name you used when creating the project) and select New > Java Class.
Then type in JSONAdapter as the new class name, make sure the Kind is set to Class and then hit OK.
Once you have your new class open in the editor, add the following code so the class looks like this:
public class JSONAdapter { private static final String IMAGE_URL_BASE = "http://bit.ly/1DwlbLr"; Context mContext; LayoutInflater mInflater; JSONArray mJsonArray; public JSONAdapter(Context context, LayoutInflater inflater) { mContext = context; mInflater = inflater; mJsonArray = new JSONArray(); } }This is still just a basic class, beginning with the first part of the URL you’ll use to download images — more on that when you implement the image download code.
Next, there are three simple variables:
- A
Context. Your old friend Context. Remember Context is the object that lets other objects know what is happening in your App. Here Context is going to help Picasso, the image downloader Library, display the images you download in your App - A
LayoutInflater. You need this to inflate aViewout of that list item XML you just wrote. - A
JSONArray. This is the datasource that will be coming in from the server in response to your query!
The
JSONAdaptermethod is the class constructor – that’s what you call when you create a new instance ofJSONAdapter. So, anyone who wants to askJSONAdapterto do anything has got to create an instance of it first, which in turn requires submitting theContextandLayoutInflatervia the constructor.The constructor currently simply saves the passed in references and creates an empty
JSONArray. You’ll pass the real data to the class after the search results are in.Now you need to convert this class into an actual
Adapterclass. This is quite easy in an object-oriented programming language like Java – simply change the top line of the class from:public class JSONAdapter {To:
public class JSONAdapter extends BaseAdapter {Note: Those with a fuzzy grasp of object inheritance and other object-oriented programming concepts may want a refresher like this one, but in essence, you’re saying thatJSONAdapteris going to build on the basics provided by theBaseAdapterclass.Right away, Android Studio will underline the line you just modified in red to let you know that you need to add more to
JSONAdapterbefore it accurately extendsBaseAdapter. Studio isn’t just a naysayer, though — it can help, too! Click the underlined line, then click the red light bulb that pops up next to it, and then selectImplement Methods from the menu.When asked to select methods to implement, make sure all four methods are highlighted and click OK.
Magically, Android Studio creates four methods for you and the red underlining disappears. This means that you’ve satisfactorily extended
BaseAdapter.But… all the methods are empty. It’s time to go through each one in turn and make them do what you want.
So, first replace the current implementation for
getCountwith the following:@Override public int getCount() { return mJsonArray.length(); }getCountanswers the question: How long does yourListViewneed to be? In this example, the answer is simply the length of yourJSONArray. Each entry in that array represents a book and so each one gets a row in theListView.Now replace
getItemwith this version:@Override public JSONObject getItem(int position) { return mJsonArray.optJSONObject(position); }getItemreturns the book for a given position, counting up from 0. A single book is represented by aJSONObject. They all just happen to be stored in aJSONArray. So all you have to do is complete a lookup on the array for theJSONObjectat the given position.Next, replace the stub for
getItemIdwith this code:@Override public long getItemId(int position) { // your particular dataset uses String IDs // but you have to put something in this method return position; }This can be a very helpful method in some situations, but in this case, you don’t really need it. So, you just set it to
position. Imagine a situation where you have a list of books, as a subset of a larger database, and each book has an ID in the larger database. If you needed to go back and query for more information based on a certain item’s ID number, this method would be helpful for you.Putting Together the Insta-Row
The last method,
getView, answers theListViewwhen it comes to the adapter and asks: What should I show at position X?To begin to answer that question, you first need to create what’s called a view holder. Add the following to the end of your
JSONAdaptercode (but before the final closing curly brace):// this is used so you only ever have to do // inflation and finding by ID once ever per View private static class ViewHolder { public ImageView thumbnailImageView; public TextView titleTextView; public TextView authorTextView; }This class is simply a packager of the three subviews that every row in your list will have. Think of it as a Do-It-Yourself kit for your list cells. All each row needs to do is get one of these, update it with the right data based on the row and presto: an Insta-Row!
The trick is that as you scroll around through who-knows-how-many books in your list, the app shows the data using the same cells, over and over. There are only just enough list cells to fill the screen, plus a few extras. Keeping all of the list cells in memory, even while they’re off-screen, would get crazy!
As a view scrolls out of sight, the recycling crew comes by and dumps out everything inside the view, but hangs onto the
ViewHolder. That same view, and theViewHolder, then get handed over to a list cell about to scroll intosight.The re-used view is handed one of these ready-made Insta-Row kits (aka a
ViewHolder), and simply fills the contents of each subview as needed, rather than inflating a brand new view from XML and creating all those subviews from scratch every single time.For more details on the view recycling process, here is a helpful blog post about it.
With that in mind, replace the stub for
getViewwith this code:@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; // check if the view already exists // if so, no need to inflate and findViewById again! if (convertView == null) { // Inflate the custom row layout from your XML. convertView = mInflater.inflate(R.layout.row_book, null); // create a new "Holder" with subviews holder = new ViewHolder(); holder.thumbnailImageView = (ImageView) convertView.findViewById(R.id.img_thumbnail); holder.titleTextView = (TextView) convertView.findViewById(R.id.text_title); holder.authorTextView = (TextView) convertView.findViewById(R.id.text_author); // hang onto this holder for future recyclage convertView.setTag(holder); } else { // skip all the expensive inflation/findViewById // and just get the holder you already made holder = (ViewHolder) convertView.getTag(); } // More code after this return convertView; }
If it happens to be the first time for the view, then you need to use your custom row XML using
mInflaterand find all your subviews usingfindViewById. But as mentioned earlier, the view might already exist — in which case you want to skip all that from-scratch stuff.You use the
setTagandgetTagmethods to hang onto theViewHolderand easily pack/unpack it while scrolling around.Next, you need to handle the image thumbnail of the book’s cover. Put this new code right after the
// More code after thiscomment line:// Get the current book's data in JSON form JSONObject jsonObject = (JSONObject) getItem(position); // See if there is a cover ID in the Object if (jsonObject.has("cover_i")) { // If so, grab the Cover ID out from the object String imageID = jsonObject.optString("cover_i"); // Construct the image URL (specific to API) String imageURL = IMAGE_URL_BASE + imageID + "-S.jpg"; // Use Picasso to load the image // Temporarily have a placeholder in case it's slow to load Picasso.with(mContext).load(imageURL).placeholder(R.drawable.ic_books).into(holder.thumbnailImageView); } else { // If there is no cover ID in the object, use a placeholder holder.thumbnailImageView.setImageResource(R.drawable.ic_books); }In this section, you first get the
JSONObjectfor the precise book whose data you want to display. Of course, this is dependent on the item’s position in the list.Next, you check to see if there’s a cover ID for that book. Unfortunately, many books don’t have covers in the Open Library database. So, you look to see if a cover is there by calling
has("cover_i"), which returns a true-or-falseboolean. If it returnstrue, then you parse out the cover ID from theJSONObjectand use it to construct a URL specific to Open Library.Note: An example URL from this operation: http://bit.ly/1I9LZ9NYou can change the “-S.jpg” to “-L.jpg” for a larger version of the same image:http://bit.ly/1b1mdXMOnce you have the URL, you simply tell Picasso to download it and display it in your
ImageView. You also specify a placeholder image to show while the cover image is downloading.If the book doesn’t have a cover assigned, you show the standard icon.
Finally, you need to populate the book title and author name. So, add the following code immediately after the block of code you added above:
// Grab the title and author from the JSON String bookTitle = ""; String authorName = ""; if (jsonObject.has("title")) { bookTitle = jsonObject.optString("title"); } if (jsonObject.has("author_name")) { authorName = jsonObject.optJSONArray("author_name").optString(0); } // Send these Strings to the TextViews for display holder.titleTextView.setText(bookTitle); holder.authorTextView.setText(authorName);This step is similar to the last. As long as the
JSONObjectcontains the title and author name, you parse the values and set the text of eachTextView!Connecting the List to the Adapter
The last thing you need to do before you can test your newly-webified app is connect the
ListViewto theJSONAdapter.Remove the following code from
onCreatein MainActivity.java:// Create an ArrayAdapter for the ListView mArrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, mNameList); // Set the ListView to use the ArrayAdapter mainListView.setAdapter(mArrayAdapter);Also remove the following from
onItemClickin MainActivity.java:// Log the item's position and contents // to the console in Debug Log.d("omg android", position + ": " + mNameList.get(position));You don’t need any of that simple stuff now that you’ve got your own souped-up
Adapter!Now, to start using the your adapter class, replace this line at the beginning of MainActivity.java:
ArrayAdapter mArrayAdapter;
With this:
JSONAdapter mJSONAdapter;
Next, add the following to the end of
onCreate:// 10. Create a JSONAdapter for the ListView mJSONAdapter = new JSONAdapter(this, getLayoutInflater()); // Set the ListView to use the ArrayAdapter mainListView.setAdapter(mJSONAdapter);
Great! You just created an instance of your snazzy new
JSONAdapter, feeding it aContextand aLayoutInflater. Your Activity can be used as a Context parameter (via the this keyword) because Activity is a Subclass ofContext. Now your adapter is hooked up and can provide yourListViewwith the data it needs.If you were to build and run, though, you would be rather underwhelmed by the results. Even after inputting a search
String, theListViewremains empty. Why?Because, if you recall, you created your
Adapterusing its constructor,public JSONAdapter(Context context, LayoutInflater inflater). That method creates an emptyJSONArrayas a placeholder.An empty list is OK to start with, of course, but it sure would be great to update the list when your search is done! That’s not happening yet, so that’s next on your agenda.
Updating the List Data
To update the list, you need to add an update method to your adapter and then call it from your activity.
First, add the following method to JSONAdapter.java:
public void updateData(JSONArray jsonArray) { // update the adapter's dataset mJsonArray = jsonArray; notifyDataSetChanged(); }This method accepts a
JSONArrayinput, sets it as the adapter’s datasource, and callsnotifyDataSetChangedto refresh the list. The adapter is already set up to know what to do with the data, so that’s all you need!Now go back to MainActivity.java. You’re going to use your new method to update the list when the network call comes back. Find the following code in
onSuccess, which is embedded withinqueryBooks:// 8. For now, just log results Log.d("omg android", jsonObject.toString());Replace it with this instead:
// update the data in your custom method. mJSONAdapter.updateData(jsonObject.optJSONArray("docs"));This is simply a call to
updateDatawith the newly-returned query response. As soon as the data comes back, you don’t waste any time — you send it straight to the adapter, which whips it into shape for theListView!It’s finally time — run your app, and search away!
Now you can type a search string into your
EditText, tap the Search button and let theListView(somewhat) magically populate from your web search. Not only that — you can scroll through all the results and look at book titles, author names, and thumbnails of the cover images. This is already a pretty cool app!Showing Progress
One nice feature you may notice missing is some kind of progress bar or spinner to let the user know your app is “thinking.” Lets solve that now.
Add the following line to the list of variables at the top of MainActivity.java:
ProgressDialog mDialog;
ProgressDialogis an Android class that provides a convienent solution for times when you want to provide feedback that your App is performing some sort of intensive task. That could be fetching large files and reading their contents, setting up basic values for the first time the App run or fetching online content like your current App does.Lets set some initial values, go to your onCreate Method and add these lines at the end:
mDialog = new ProgressDialog(this); mDialog.setMessage("Searching for Book"); mDialog.setCancelable(false);Good. You’ve told your Activity you want a
ProgressDialogto be created with a particular message and that you don’t want the user to have the ability to cancel it. Lets put this to work, add this line toqueryBooks, immediately after creating yourAsyncHttpClient:// Show ProgressDialog to inform user that a task in the background is occurring mDialog.show();
You want the Dialog to disappear when the request is over, which could actually be in one of two spots –
onSuccessoronFailure. Add the following line at the very beginning ofonSuccess:// 11. Dismiss the ProgressDialog mDialog.dismiss();
Then add the same line to the beginning of
onFailure:// 11. Dismiss the ProgressDialog mDialog.dismiss();
Run the app again and do another search.
This time, your Dialog will appear in the middle of your screen and will spin as your networking call is happening. Much better! Your App users now know when something is happening behind the scenes thanks to you.
The Detail Activity
Seeing the list of all the books is exciting! The next logical step is to let the user select a book from the list to see more details or a larger version of the cover.
For this app, you’ll only show a larger version of the cover. But the techniques you use will pave the way for the additional challenge of displaying any further details that may interest you. Let’s get started!
First, add the following line to res/values/strings.xml:
<string name="activity_details">Book Details</string>
This is simply to provide a title for the activity. As mentioned before, it’s good to keep all the strings in one file!
Next is the layout XML. It won’t be complicated. Right-click on res/layout and select New > Layout Resource File.
Name it activity_detail.xml, with a Root Element of
ImageView.That’s almost it right there. All you need to do now is give the
ImageViewanid, a default image, and a bit of margin space for good measure. Edit activity_detail.xml in Text mode to look like this:<?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://bit.ly/1I9HKuD" android:id="@+id/img_cover" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="25dp" android:src="@drawable/img_books_large"/>That’s simple enough. This screen will now show a single
ImageViewwith a 25dp margin all around, and the default image isimg_books_large.Next, you need to make a new
Activity. Right-click on the com.example.omgandroid package (or the package name you set originally) and select New > Java Class, as before.Name the class DetailActivity.
This creates a simple, empty class for you. Next, modify the class definition so that your new class extends
Activity, like this:public class DetailActivity extends ActionBarActivity {You already know you need to access the
ImageViewfrom your layout, so add the following method to your activity:@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Tell the activity which XML layout is right setContentView(R.layout.activity_detail); // Enable the "Up" button for more navigation options getSupportActionBar().setDisplayHomeAsUpEnabled(true); // Access the imageview from XML ImageView imageView = (ImageView) findViewById(R.id.img_cover); }The above code simply tells the
Activityto use the simple XML layout you made earlier, and then grabs theImageViewyou need from it. But wait: what is thatgetSupportActionBarstuff doing in there?The Up and Back Buttons
You may have noticed, or simply used it without really thinking about it, the Up button in many Android apps. The proper Android design of the Up and Back buttons is well-documented here and is worth a read, but it all begins with enabling the button as you just did.
The other steps for enabling the Up button take place in your manifest. Open AndroidManifest.xml and add the
launchModeattribute toMainActivityso that it looks something like this:<activity android:name=".MainActivity" android:label="@string/app_name" android:launchMode="singleTop">Note: Depending on your Android Studio version, the package name you selected when you created the project, and a few other factors, the above might not match what you see in your own
AndroidManifest.xmlfile exactly. The only thing you need to really worry about is adding the newlaunchModeattribute as shown above. You can leave the rest as is.So, do you recall from earlier in this tutorial about how the manifest is “the boss” who takes in “jobs” in the form of
Intentsand checks if there is a team member right for the task? Normally, the manifest would arrange for the system to start a brand-new instance of thatActivityevery time.But, by setting the
launchModeattribute tosingleTop, you’re telling the manifest to use an already-existing instance of thatActivity, if possible. That way, when you use either the Back or Up button to return to the main screen, your most recent search results will still be there and you won’t have to start from scratch!Next, add a definition for the
DetailActivityto the manifest, immediately after the one forMainActivity:<activity android:name=".DetailActivity" android:label="@string/activity_details" android:parentActivityName=".MainActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".MainActivity"/> </activity>This looks pretty similar to the definition for
MainActivity, except for the stuff about “parent activity.” Setting the parent activity tells the manifest which activity should be displayed when there’s a request to go up fromDetailActivity.An Intent to Show the Detail Activity
Now that you’ve set up your manifest to be aware of your
DetailActivity, you need to send it anIntentto start it! ThisIntentis going to originate from theMainActivity, when the user selects a book from the list.If you recall, the method that executes when a cell is selected from the
ListViewisonItemClickinMainActivity.java. It used to log information originally but now is empty. Add the following code to it:// 12. Now that the user's chosen a book, grab the cover data JSONObject jsonObject = (JSONObject) mJSONAdapter.getItem(position); String coverID = jsonObject.optString("cover_i",""); // create an Intent to take you over to a new DetailActivity Intent detailIntent = new Intent(this, DetailActivity.class); // pack away the data about the cover // into your Intent before you head out detailIntent.putExtra("coverID", coverID); // TODO: add any other data you'd like as Extras // start the next Activity using your prepared Intent startActivity(detailIntent);
Here, you create an
Intentto take you from where you are now (this) to an instance ofDetailActivity. But before you fire off the command usingstartActivity, there’s one more thing to remember to pack away.As you’ve previously seen when you created
setShareIntent, you can pack “extras” into anIntentin the form of key-value pairs. Since yourDetailActivityneeds to know the cover ID to display, you extract that ID from the book’s JSON data and send it along.Note: I put in aTODOreminder for an optional challenge to you. If you want yourDetailActivityto do anything more than show an image, you should send along additional data here.Build and run your app, and you will be able to click on a list item from your search results to see your new
DetailActivity! You can also navigate back to the main screen using either the Up or Back button.Right now, you only see the placeholder image, but you know where this is headed :]
Add the following variables at the beginning of DetailActivity.java (right after the class definition line):
This sets the base URL for cover images on the Open Library API. You also create
mImageURLto hang onto any specific URL so that different methods within yourActivitycan use it without needing to create the image URL all over again each time.Next, add the following code to the end of
onCreate:// 13. unpack the coverID from its trip inside your Intent String coverID = this.getIntent().getExtras().getString("coverID"); // See if there is a valid coverID if (coverID.length() > 0) { // Use the ID to construct an image URL mImageURL = IMAGE_URL_BASE + coverID + "-L.jpg"; // Use Picasso to load the image Picasso.with(this).load(mImageURL).placeholder(R.drawable.img_books_loading).into(imageView); }
The above code digs into the
Intentthat brought you to thisActivityand sees if it contains aStringwith the namecoverID. If so, the Picasso Library will download the image, just as you did in all the row cells. You display a “loading” image until the desired image is ready.Build and run, and you’ll see the actual cover for the book you chose from the list!
Sharing the Image
The last thing to do is allow your users to share these cover images. You’ve already seen sharing in action and the code is almost identical here.
First, add another variable to the top of DetailActivity.java:
ShareActionProvider mShareActionProvider; // 14
This is just another variable to hold a reference to the
ShareActionProviderfor yourActivity. Make sure you import the right ShareActionProvider. It should match the import in your MainActivity.java class.Next, add this new method to the class:
private void setShareIntent() { // create an Intent with the contents of the TextView Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("text/plain"); shareIntent.putExtra(Intent.EXTRA_SUBJECT, "Book Recommendation!"); shareIntent.putExtra(Intent.EXTRA_TEXT, mImageURL); // Make sure the provider knows // it should work with that Intent mShareActionProvider.setShareIntent(shareIntent); }This should look very familiar, as it is nearly the same as the method added to
MainActivityearlier. The only difference is the use ofmImageURLas the text to be shared.Note: Some share service providers, like Facebook, will intelligently interpret the URL as an image and display it to the user’s friends. Others, like e-mail, will simply include the link. Either way, you’re enabling your users to share dynamic content about a book they think others might enjoy!One final thing left to do – adding the share button to the Action Bar. You can again reuse code from
MainActivity. Add this method to DetailActivity.java:@Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu // this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); // Access the Share Item defined in menu XML MenuItem shareItem = menu.findItem(R.id.menu_item_share); // Access the object responsible for // putting together the sharing submenu if (shareItem != null) { mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(shareItem); } setShareIntent(); return true; }
Here, you could potentially use different menu XML files to populate the Action Bar on different screens/activities. But for your purposes, the same menu/menu_main.xml file will do.
Build and run your app, and you’ll have a pretty powerful app! Your app now takes in your search query, returns a list of books,allows you to take a closer look at a book cover, and share that cover image with friends!
Where to Go From Here?
Congratulations on putting together your app! This tutorial has introduced you to a variety of Android capabilities and techniques, and I hope you feel comfortable with the basics of development on the Android platform.
You can get the full source code for this app on GitHub or as a zip.
If you’re looking for a few more challenges, how about trying some of these?
- Add a
contentDescriptionattribute to yourImageViews for accessibility. Here’s an explanation. - Display more details about the book in the Detail View, like more information about the book or even the first few lines of the book.
- Reposition some of the views to a different layout structure.
- Investigate
android:backgroundand add background colors to your layouts and views. - Add a hashtag to the share text.
Thank you for following this tutorial series. Please leave any comments or questions below!
The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.
The post Android Tutorial for Beginners: Part 3 appeared first on Codzcook.
We are Learncodz.
Posts
Comments
The Team
Blog Codz Author
Popular Codz Article
-
I rewrite this tutorial from forum.xda-developers.com : The users of Micromax A116 Canvas Hd can now update their handsets to Android 5.0 Lo...
-
Build Your Own URL Shortener With YOURLS
What You'll Be Creating In this tutorial, I'll show you how to install your own open source, PHP-based URL shortener, called YOU... -
Wifi Hacking – WEP – Kali Linux Aircrack-ng suite
Alright, this post is written assuming you have Kali Linux up and running on your computer. If not, here is a post on hacking with kali linu... -
Top 4 Affordable Android One Smartphones from Rs. 6000
If you’re looking for an affordable smartphone to buy this holiday season and aren’t too keen on a Windows Phone, look no further than the A... -
[MOD][4.1 4.2] Extend Phone Storage 1.5, 2.5GB A116 (Other MT6589 Devices on request)
READ EVERYTHING CAREFULLY I Will Not Responsible For Any Brick or Any Problem So Do It At Your Own Risk. This Will Extend your Device's ... -
want to build an chat application..(parse)
Introduction The Parse platform provides a complete backend solution for your mobile application. Our goal is to totally eliminate the nee... -
Distributing iOS Apps With iTunes Connect
Once you've developed your iOS or OS X app, it's time to submit it to Apple for release in the App Store. This process is done throu...
Portfolio
- 2015 at 02:00AM
- 2015 at 02:03AM
- 2015 at 02:07AM
- 2015 at 02:09AM
- 2015 at 03:51AM
- 2015 at 03:57AM
- 2015 at 04:03AM
- 2015 at 04:08AM
- 2015 at 06:38AM
- 2015 at 08:03PM
- 2015 at 08:09PM
- 2015 at 08:13PM
- 2015 at 08:18PM
- 2015 at 08:23PM
- 2015 at 08:32PM
- 2015 at 08:33PM
- 2015 at 08:42PM
- 2015 at 08:50PM
- 2015 at 09:08AM
- 2015 at 09:12AM
- 2015 at 09:20AM
- 2015 at 09:22AM
- 2015 at 09:25AM
- 2015 at 09:27PM
- 2015 at 09:28AM
- 2015 at 09:31PM
- 2015 at 09:34AM
- 2015 at 09:58AM
- 2015 at 10:31AM
- 2015 at 10:45AM
- 2015 at 10:46PM
- 2015 at 10:50AM
- 2015 at 10:57AM
- 2015 at 10:57PM
- 2015 at 10:58AM
- 2015 at 11:04PM
- 2015 at 11:07AM
- 2015 at 11:17AM
- 2015 at 11:20AM
- 2015 at 11:31AM
- 2015 at 11:32AM
- 2015 at 11:33AM
- 2015 at 11:39AM
- 2015 at 11:44AM
- 2015 at 11:45AM
- 2015 at 11:45PM
- 2015 at 11:46PM
- 2015 at 11:50PM
- 2015 at 11:51PM
- 2015 at 11:52AM
- 2015 at 11:57AM
- 2015 at 11:58PM
- 2015 at 12:02PM
- 2015 at 12:04AM
- 2015 at 12:08PM
- 3d maxx
- and
- Android
- android developer
- android developr
- android labels
- android sdk
- android studio
- android tutorial
- android tutorials
- apk
- apple
- application
- apps
- April 19
- April 20
- April 21
- April 22
- April 23
- April 24
- April 25
- At its simplest
- bac
- backtrack
- battery
- biotechnology is technology based on biology - biotechnology harnesses cellular and biomolecular processes to develop technologies and products that help improve our lives and the hea
- Blog
- blogger
- browser
- build an chat application
- C
- chrome app
- command prompt
- designing
- Developing App
- earn money
- games
- hack
- hack Facebook
- Hacking
- help
- How To
- htc
- IFTTT
- intel xdk
- Ios
- ios 8
- ios8
- ips 8
- Java
- Javascript
- lenovo
- mac
- Magento
- Mysql
- new launch
- nexus
- operating system
- OS
- phone
- Photoshop
- Php
- pivotal tracker
- Review
- Reviews
- roms
- root
- Ruby On Rails
- samsung
- sdk
- security
- swift
- Tech. News
- Tools:Tips
- tutorial
- Uncategorized
- unity
- update
- Visual Studio
- windows phone
- Wordpress
- wordpress tutorial