Mobile technology for the masses: words and gestures from the Overheard Word

Mobile technology for the masses: words and gestures from the Overheard Word

[[120670]]

Programmatically integrate third-party code into your Android UI

Grabbing third-party code from GitHub or another repository might make you feel like a kid in a candy store, but it takes some finesse to integrate that code with your Android UI. This month, Andrew Glover shows you how to take the Overheard Word demo app to the next level with a JSON-based word engine and some pre-baked swipe gesture functionality. Android, it turns out, can accommodate third-party code with ease, but you still have to implement some careful logic if you want your app's UI to run with it.

If you've read and followed the demonstrations in this series so far, you've built up some basic Android development skills. In addition to setting up an Android development environment and writing your first Hello World application, you've learned how to replace button taps with swipe gestures and implement menus (or toolbars) and icons in a custom mobile app. In this article, you continue along this trajectory and learn how to use third-party libraries to increase or enhance the functionality of your application. First, we'll install some open source libraries and read documentation, and then we'll programmatically integrate new functionality with the UI of a demo application.

As I did in previous articles, I'll demonstrate using my Overheard Word application. If you haven't already cloned the Overheard Word GitHub repository, you should do so first so that you can follow the steps that follow.

About this series

Mobile app releases are exploding, and mobile development technology is here to stay. This series of articles will introduce developers who have programming experience but are new to the mobile space to what's happening in this space. This series starts with writing native apps in Java code, then expands your toolbox to include JVM languages, scripting frameworks, HTML5/CSS/JavaScript, third-party tools, and more. You'll gradually gain the skills you need to meet the needs of almost any mobile development scenario.

Thingamajig: A pluggable word engine

Overheard Word is an English language app that helps users learn new words and build up their vocabulary on the fly. In previous articles, we first developed a basic app, then added swipe gestures for easier navigation and icons for a prettier UI. So far, so good, but this app won't go very far because it's missing a certain ingredient: Overheard Word needs some words!

To make Overheard Word a true multi-word app, I've built a small word engine, Thingamajig, that encapsulates the concept of a word and its corresponding definition. Thingamajig handles creating words and their definitions via JSON documents, and has absolutely no Android dependencies. Like the Overheard Word app, my word engine is hosted on GitHub. You can clone the repository, or download the source code and run ant .

You will get a lightweight 8KB JAR file that you can copy to your libs directory. If your Android project is set up correctly, the IDE will automatically recognize any file in libs directory as a dependency.

At the time, the code in my word engine was initialized with a JSON document instance. The JSON document could be a file residing in a file system on a device, a response to an HTTP request, or even a response to a database query — it doesn't really matter for now. What matters is that you have a useful library that lets you work with Word objects. Each Word object contains a collection of Definition objects, and a corresponding part of speech. While the relationship between a word and its definition is far from that simple, it's doable for now. Later, we can add example sentences, synonyms, and antonyms.

Third-party libraries in Android

Integrating third-party libraries into an Android project is easy; in fact, every Android starter project includes a special directory libs where you can place third-party JARs. During the Android build process, both normal JVM files and third-party JARs are converted to be compatible with Dalvik (which is a specialized Android VM).

Environmental constraints

While you can imagine how to add any third-party libraries you like to your application, never forget the limitations of the mobile environment. The device running your application is not a reliable web server after all! Users will thank you for improving data processing capabilities and minimizing the download size of third-party add-ons.

In addition to plugging the word engine library into my application, I'm also going to add another third-party application called Gesticulate. Just like with the word engine library, you can get Gesticulate by cloning or downloading its source code from GitHub. Then run ant and you'll get a JAR file that you can put into the libs directory of your application.

Update UI

Now we get to the meat of this article, updating the UI to integrate all that third-party code you just snagged for free. Fortunately, I had planned ahead for this moment with my Overheard Word application. Back when I wrote the first iteration of the application, I defined a simple layout that included a word, its part of speech, and a definition, as shown in Figure 1:

Figure 1. Overheard Word's default view

The layout is defined with placeholder text, which I'm going to replace with actual words fetched from my pluggable word engine. So, I'll initialize an array of words, grab one of them, and use its value to update the UI accordingly.

In order to update the UI, I must be able to get a handle to each view element and provide values ​​for those elements. For example, a displayed word (such as Pedestrian ) is defined as a TextView with an ID of word_study_word , as shown in Listing 1:

Listing 1. TextView defined in a layout

  1. <TextView
  2. android:id= "@+id/word_study_word"  
  3. android:layout_width= "wrap_content"  
  4. android:layout_height= "wrap_content"  
  5. android:layout_gravity= "center"  
  6. android:layout_marginBottom= "10dp"  
  7. android:layout_marginTop= "60dp"  
  8. android:textColor= "@color/black"  
  9. android:textSize= "30sp"   
  10. android:text= "Word" />

If you look closely, you'll see that I've set the text to " Word " in the XML; however, I was able to set the value programmatically by getting a reference to TextView instance and calling setText .

Before I can proceed, I need to build a word list. You may remember that my word engine can create Word instances from JSON documents, so all I need to do is set up my Android application to include and read these custom files.

Using files: res and raw directories

By default, an Android application has permission to read the device's underlying file system, but you can only access the local file system underneath the application itself. Therefore, you can include files in your application and reference them accordingly. (This means that you can read and write files that are local to your application ; writing to the SD card, etc., a file system outside of your application requires special permissions.) Because my word engine can take a JSON document to initialize the word list, there is nothing to stop me from including a JSON document that the application will read at runtime.

Just like I demonstrated with the icon assets in my previous article, you can store files in the res directory. If I find myself needing custom files, I like to add them to a directory called raw . Any files I place in this directory can be referenced through a generated R file, which I have used a few times in Overheard Word. Basically, the Android platform takes the assets from the res directory and builds a class called R , which then provides a handle to those assets. If the asset is a file, then the R file will provide a reference to open the file and get its contents.

I create a raw directory in the res directory structure and place a JSON document in that directory, as shown in Figure 2:

Figure 2. The raw directory containing new words

Next, Eclipse rebuilds the project, and my R file can easily reference the new file, as shown in Figure 3:

Figure 3. Updated R file

Once I have a handle to a file, I can open it, read it, and ultimately build a JSON document as the basis for generating a word list.

Building a word list

When the application starts, I start a series of steps to load the original JSON document and build a word list. I will create a method called buildWordList to handle these steps, as shown in Listing 2:

Listing 2. Reading the contents of a file in Android

  1. private List<Word> buildWordList() {
  2. InputStream resource =
  3. getApplicationContext().getResources().openRawResource(R.raw.words);
  4. List<Word> words = new ArrayList<Word>();
  5. try {
  6. StringBuilder sb = new StringBuilder();
  7. BufferedReader br = new BufferedReader( new InputStreamReader(resource));
  8. String read = br.readLine();
  9. while (read != null ) {
  10. sb.append(read);
  11. read = br.readLine();
  12. }
  13. JSONObject document = new JSONObject(sb.toString());
  14. JSONArray allWords = document.getJSONArray( "words" );
  15. for ( int i = 0 ; i < allWords.length(); i++) {
  16. words.add(Word.manufacture(allWords.getJSONObject(i)));
  17. }
  18. } catch (Exception e) {
  19. Log.e(APP, "Exception in buildWordList:" + e.getLocalizedMessage());
  20. }
  21. return words;
  22. }

You should notice a few things that happen in the buildWordList method. First, notice how InputStream is created using a call from the Android platform that ultimately references the words.json file found in the raw directory. I am not using String to represent the path, which makes the code portable across a variety of devices. Next, I use a simple JSON library included with the Android platform to convert the content (represented by String ) into a series of JSON documents. The static method manufacture on Word reads a JSON document that represents a word.

The JSON format for a word is shown in Listing 3:

Listing 3. JSON representation of a word

  1. {
  2. "spelling" : "sagacious" ,
  3. "definitions" :[
  4. {
  5. "part_of_speech" : "adjective" ,
  6. "definition" :"having or showing acute mental discernment
  7. and keen practical sense; shrewd"
  8. }
  9. ]
  10. }

My WordStudyEngine class is the main facade for Thingamajig. It generates random words and functions from a list of Word instances. So, my next step is to initialize the engine with the newly created Word List , as shown in Listing 4:

Listing 4. Initializing WordStudyEngine

  1. List<Word> words = buildWordList();
  2. WordStudyEngine engine = WordStudyEngine.getInstance(words);

Once I have an initialized engine instance, I can ask it for a word (randomized automatically) and then update the three elements of the UI accordingly. For example, I can update the word portion of the definition, as shown in Listing 5:

Listing 5. Updating UI elements programmatically

  1. Word aWord = engine.getWord();
  2. TextView wordView = (TextView) findViewById(R.id.word_study_word);
  3. wordView.setText(aWord.getSpelling());

findViewById in Listing 5 is an Android platform call that reads an integer ID that you can get from your app's R class. You can set text to TextView programmatically. You can also set the font type, font color, or size of the text display, like this: wordView.setTextColor(Color.RED) .

In Listing 6, I follow essentially the same process to update the definition and part-of-speech elements of the application's UI:

Listing 6. More programmatic updates

  1. Definition firstDef = aWord.getDefinitions().get( 0 );
  2. TextView wordPartOfSpeechView = (TextView) findViewById(R.id.word_study_part_of_speech);
  3. wordPartOfSpeechView.setText(firstDef.getPartOfSpeech());
  4.  
  5. TextView defView = (TextView) findViewById(R.id.word_study_definition);
  6. defView.setText(formatDefinition(aWord));

Notice in Listings 5 ​​and 6 how the layout elements are referenced by name using the R file. The formatDefinition method, which resides in my Activity class, reads a definition string and capitalizes the first letter. The method also formats the string so that it uses a period at the end of a sentence if there isn't one already. (Note that Thingamajig has nothing to do with formatting—it's just a word engine!)

Now that I have updated these UI elements, I can launch my application and check the results. Voila! I am now learning a legal word!

Figure 4. Overheard Word There is a word!

Add gestures: connect swipes to words

Now that I can effectively display a word, I want to give the user the ability to quickly swipe through all the words in my word engine. To make this easier, I'm going to use Gesticulate, my own third-party library that calculates swipe speed and direction. I've also put the swipe logic into a method called initializeGestures .

After initiating the swipe gesture, the first step is to move the logic for displaying a word into a new method that I can call to display a new word when someone swipes. The updated onCreate method (which is originally called when Android creates an instance of the application) is shown in Listing 7:

Listing 7. onCreate displays a word when the gesture is initiated

  1. protected   void onCreate(Bundle savedInstanceState) {
  2. super .onCreate(savedInstanceState);
  3. Log.d(APP, "onCreated Invoked" );
  4. setContentView(R.layout.activity_overheard_word);
  5.    
  6. initializeGestures();
  7.    
  8. List<Word> words = buildWordList();
  9.    
  10. if (engine == null ) {
  11. engine = WordStudyEngine.getInstance(words);
  12. }
  13. Word firstWord = engine.getWord();
  14. displayWord(firstWord);
  15. }

Notice the engine variable, I defined it as a private static member variable of OverheardWord Activity itself. I will briefly explain why I did this.

Next, I go into the initGestureDetector method, which, if you follow the cloned code, is referenced in initializeGestures method. If you recall, the initGestureDetector method has logic that performs an action when the user swipes up, down, left, or right on the device screen. In Overheard Word, I want to display a new word when the user swipes from right to left (this is a left swipe ). I start by removing the Toast message, which is a placeholder for this code, and replacing it with a call to displayWord , as shown in Listing 8:

Listing 8. initGestureDetector displays a word when swiping left

  1. private GestureDetector initGestureDetector() {
  2. return   new GestureDetector( new SimpleOnGestureListener() {
  3. public   boolean onFling(MotionEvent e1, MotionEvent e2,
  4. float velocityX, float velocityY) {
  5. try {
  6. final SwipeDetector detector = new SwipeDetector(e1, e2, velocityX, velocityY);
  7. if (detector.isDownSwipe()) {
  8. return   false ;
  9. } else   if (detector.isUpSwipe()) {
  10. return   false ;
  11. } else   if (detector.isLeftSwipe()) {
  12. displayWord(engine.getWord());
  13. } else   if (detector.isRightSwipe()) {
  14. Toast.makeText(getApplicationContext(),
  15. "Right Swipe" , Toast.LENGTH_SHORT).show();
  16. }
  17. } catch (Exception e) {}
  18. return   false ;
  19. }
  20. });
  21. }

My word engine is represented by the engine variable, and it needs to be accessible throughout the entire Activity . That's why I defined it as a member variable to ensure that a new word is displayed every time there is a left swipe.

Slide back and forth

Right now, when I open the app and start swiping, every time I swipe left, I see a new word and definition appear. However, when I swipe in other directions, I just get a tiny message saying I've swiped. Wouldn't it be better if I could go back to the previous word by swiping right?

Backtracking doesn't seem like it should be difficult. Maybe I could use a stack and just pop the last element that was put there by the previous left swipe? However, this idea doesn't hold up when the user swipes left again after backtracking. If the last position was popped, a new word would be displayed instead of the word the user previously saw. After thinking it over, I'm inclined to try a linked list approach that gets rid of previously viewed words as the user swipes back and forth.

I'll start by creating a LinkedList of all the words that have been shown, as shown in Listing 9. I'll then keep a pointer to the index of the element in the list that I can use to retrieve the word that has been seen. Of course, I'll define these as static member variables. I'll also initialize my pointer to -1 , and whenever a new word is added to LinkedList instance, I'll increment it. Like most array-backed collections in any language, LinkedList is indexed starting at 0.

Listing 9. New member variables

  1. private   static LinkedList<Word> wordsViewed;
  2. private   static   int viewPosition = - 1 ;

In Listing 10, I initialize LinkedList in my application's onCreate :

Listing 10. Initializing LinkedList

  1. if (wordsViewed == null ) {
  2. wordsViewed = new LinkedList<Word>();
  3. }

Now, when the first word is displayed, I need to add it to LinkedList instance ( wordsViewed ) and increment the pointer variable viewPosition , as shown in Listing 11:

Listing 11. Don't forget to increment the view position

  1. Word firstWord = engine.getWord();
  2. wordsViewed.add(firstWord);
  3. viewPosition++;
  4. displayWord(firstWord);

Sliding Logic

Now, I get to the main part of the logic, which requires some thinking. When the user swipes left, I want to display the next word, and when he or she swipes right, I want to display the previous word. If the user swipes right again, then the second previous word should be displayed, and so on. This should continue until the user gets back to the first displayed word. Then, if the user starts swiping left again, the list of words should appear in the same order as before. The first part is a little tricky because the default implementation of my WordStudyEngine returns the words in a random order.

In Listing 12, I feel like I have figured out the logic for what I want to do. The first Boolean assignment is encapsulated in a method called listSizeAndPositionEql , and it's simple: wordsViewed.size() == (viewPosition + 1) .

If listSizeAndPositionEql is assigned a value of true , then the application displays a new word through displayWord method. However, if listSizeAndPositionEql is false, then the user must swipe back; therefore, viewPosition pointer is used to retrieve the corresponding word from the list.

Listing 12. LinkedList for scrolling back and forth

  1. public   boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) {
  2. try {
  3. final SwipeDetector detector = new SwipeDetector(e1, e2, velX, velY);
  4. if (detector.isDownSwipe()) {
  5. return   false ;
  6. } else   if (detector.isUpSwipe()) {
  7. return   false ;
  8. } else   if (detector.isLeftSwipe()) {
  9. if (listSizeAndPositionEql()) {
  10. viewPosition++;
  11. Word wrd = engine.getWord();
  12. wordsViewed.add(wrd);
  13. displayWord(wrd);
  14. } else   if (wordsViewed.size() > (viewPosition + 1)) {
  15. if (viewPosition == -1) {
  16. viewPosition++;
  17. }
  18. displayWord(wordsViewed.get(++viewPosition));
  19. } else {
  20. return   false ;
  21. }
  22. } else   if (detector.isRightSwipe()) {
  23. if (wordsViewed.size() > 0 && (listSizeAndPositionEql() || (viewPosition >= 0))) {
  24. displayWord(wordsViewed.get(--viewPosition));
  25. } else {
  26. return   false ;
  27. }
  28. }
  29. } catch (Exception e) {}
  30. return   false ;
  31. }

Note that if viewPosition is -1 , then the user has swiped all the way back to the beginning — in this case, the pointer in the list is incremented back to 0. This is because there is no -1 element in the Java-based LinkedList implementation. (In some other languages, negative position values ​​start at the back of the list, so -1 is the tail element.)

Once you've mastered the left swipe, handling the right swipe logic is fairly easy. Essentially, if there is a word in the wordsViewed instance, you need to use a decremented pointer value to access the previously viewed word.

Try it: Start an instance of Overheard Word and swipe back and forth to learn some words. Each time you swipe, the UI will be updated accordingly.

Conclusion

Overheard Word has been working pretty well, and now we've started to add some more interesting features on top of the basic Android shell. Of course, there's more to do, but we now have a functioning application that handles scrolling, has icons and menus, and can even read from a custom third-party word file. Next month, I'll show you how to add more styles to Overheard Word, and then implement a flexible quiz feature.

<<:  Activities and icons in the Android application life cycle

>>:  Mobile technology for the masses: signing, packaging, and distributing Android apps

Recommend

Fan Deng: Decoding the advanced code of spiritual growth

Fan Deng: Decoding the advanced code of spiritual...

Your app has no users mainly because of the following reasons...

[[144037]] Recently, I have been in contact with ...

If you understand these 5 points, your live broadcast will sell out!

For a hundred years, there has been a famous &quo...

Advertising and marketing creative poster collection

For every designer Posters are the most common an...

Facebook dominates mobile! Daily active users reach 800 million

[[132525]] As Internet users around the world swi...

Are women's periods contagious? The truth may be different from what you think!

There are usually two reasons why menstruation ap...

A great migration in the sky

“No migratory bird flies in a straight line” This...

How much is the investment price of Hami Mechanical Equipment Mini Program?

Starting a business requires costs, and mini prog...