Tuesday, April 9, 2013

Android Tutorial: Using the ViewPager

Currently, one of the most popular Widgets in the Android library is the ViewPager.  It's implemented in several of the most-used Android apps, like the Google Play app and one of my own apps, RBRecorder:

RBRecorder

 

 

GooglePlay

 

 

 

 

 

 

 

 

 

 

The ViewPager is the widget that allows the user to swipe left or right to see an entirely new screen. In a sense, it's just a nicer way to show the user multiple tabs. It also has the ability to dynamically add and remove pages (or tabs) at anytime. Consider the idea of grouping search results by certain categories, and showing each category in a separate list. With the ViewPager, the user could then swipe left or right to see other categorized lists. Using the ViewPager requires some knowledge of both Fragments and PageAdapters. In this case, Fragments are "pages". Each screen that the ViewPager allows the user to scroll to is really a Fragment. By using Fragments instead of a View here, we're given a much wider range of possibilities to show in each page. We're not limited to just a List of items. This could be any collection of views and widgets we may need. You can think of PageAdapters in the same way that you think of ListAdapters. The Page Adapter's job is to supply Fragments (instead of views) to the UI for drawing.

I've put together a quick tutorial that gets a ViewPager up and running (with the Support Library), in just a few steps. This tutorial follows more of a top-down approach. It moves from the Application down to the Fragments. If you want to dive straight into the source code yourself, you can grab the project here.

At The Application Level

Before getting started, it's important to make sure the Support Library is updated from your SDK, and that the library itself is included in your project. Although the ViewPager and Fragments are newer constructs in Android, it's easy to port them back to older versions of Android by using the Support Library. To add the library to your project, you'll need to create a "libs" folder in your project and drop the JAR file in. For more information on this step, check out this page on the Support Library help page on the developer site.

Setting Up The Layout File

The next step is to add the ViewPager to your layout file for your Activity. This step requires you to dive into the XML of your layout file instead of using the GUI layout editor. Your layout file should look something like this:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent" >

   <android.support.v4.view.ViewPager
     android:id="@+id/viewpager"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent" />

</RelativeLayout>

Implementing The Activity

Now we'll put the main Activity together. The main takeaways from this activity are as follows:

  • The class inherits from FragmentActivity, not Activity
  • This Activity "has a" PageAdapter object and a Fragment object, which we will define a bit later
  • The Activity needs to initialize it's own PageAdapter
public class PageViewActivity extends FragmentActivity {
  MyPageAdapter pageAdapter;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_page_view);
    List<Fragment> fragments = getFragments();
    pageAdapter = new MyPageAdapter(getSupportFragmentManager(), fragments);
    ViewPager pager = (ViewPager)findViewById(R.id.viewpager);
    pager.setAdapter(pageAdapter);
  }
}

Implementing The PageAdapter

Now that we have the FragmentActivity covered, we need to create our PageAdapter. This is a class that inherits from the FragmentPageAdapater class. In creating this class, we have two goals in mind:

  • Make sure the Adapter has our fragment list
  • Make sure it gives the Activity the correct fragment
class MyPageAdapter extends FragmentPagerAdapter {
  private List<Fragment> fragments;

  public MyPageAdapter(FragmentManager fm, List<Fragment> fragments) {
    super(fm);
    this.fragments = fragments;
  }
  @Override 
  public Fragment getItem(int position) {
    return this.fragments.get(position);
  }

  @Override
  public int getCount() {
    return this.fragments.size();
  }
}

Getting The Fragments Set Up

With the PageAdapter complete, all that is now needed are the Fragments themselves. We need to implement two things:

  1. The getFragment method in the PageViewActivity
  2. The MyFragment class

 

1. The getFragment method is straightforward. The only question is how are the actual Fragments created. For now, we'll leave that logic to the MyFragment class.

private List<Fragment> getFragments(){
  List<Fragment> fList = new ArrayList<Fragment>();
 
  fList.add(MyFragment.newInstance("Fragment 1"));
  fList.add(MyFragment.newInstance("Fragment 2")); 
  fList.add(MyFragment.newInstance("Fragment 3"));
 
  return fList;
}

2. The MyFragment class also has it's own layout file. For this example, the layout file only consists of a simple TextView. We'll use this TextView to tell us which Fragment we are currently looking at (notice in the getFragments code, we are passing in a String in the newInstance method).

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical" >

  <TextView
    android:id="@+id/textView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:textAppearance="?android:attr/textAppearanceLarge" />

</RelativeLayout>

And now the Fragment code itself: The only trick here is that we create the fragment using a static class method, and we use a Bundle to pass information to the Fragment object itself.

public class MyFragment extends Fragment {
 public static final String EXTRA_MESSAGE = "EXTRA_MESSAGE";
 
 public static final MyFragment newInstance(String message)
 {
   MyFragment f = new MyFragment();
   Bundle bdl = new Bundle(1);
   bdl.putString(EXTRA_MESSAGE, message);
   f.setArguments(bdl);
   return f;
 }
 
 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
   Bundle savedInstanceState) {
   String message = getArguments().getString(EXTRA_MESSAGE);
   View v = inflater.inflate(R.layout.myfragment_layout, container, false);
   TextView messageTextView = (TextView)v.findViewById(R.id.textView);
   messageTextView.setText(message);
 
   return v;
 }
}

That's it! with the above code, you can easily get a simple page adapter up and running. You can also get the source code of the above tutorial from GitHub.

For More Advance Developers

There are actually a few different types of FragmentPageAdapters out there. It is important to know what they are and what they do, as knowing this bit of information could save you some time when creating complex applications with the ViewPager. The FragmentPagerAdapter is the more general PageAdapter to use. This version does not destroy Fragments it has as long as the user can potentially go back to that Fragment. The idea is that this PageAdapter is used for mainly "static" or unchanging Fragments. If you have Fragments that are more dynamic and change frequently, you may want to look into the FragmentStatePagerAdapter. This Adapter is a bit more friendly to dynamic Fragments and doesn't consume nearly as much memory as the FragmentPagerAdapter.

 

Monday, March 4, 2013

Reading Logs Disabled in Jelly Bean

Just a heads up for anyone looking to read the standard ADB logs directly within your application. From Jelly Bean and up, you won't be able to access them on a non-rooted device. Although you can still ask for the permission via the READ_LOGS tag, you won't be granted the permission.

For more information check out these links:

Thursday, February 14, 2013

Memory Management For Android Apps (Google IO)

In gearing up for this year's Google IO, I started looking over all of the sessions from the last few years to see what kind of gems are hidden there. I recently watched the latest Google IO session on Memory Management within Android. While I watched the video, I took some notes of what I thought were the more important and interesting points. At a high level, the video has some interesting info about how memory is allocated in Android (and really, Java itself).

Garbage Collection (GC) is the process of reclaiming old memory in your application. Or in other words, taking objects that are no longer in use and freeing the memory used by them so the memory can be used for new objects.

The most interesting points in the video were about the Android Heap. The Heap is where all applications are stored within Android. Each Android app has a limit that is specific to each device. To find that limit, use the method ActivityMonitor,getMemoryClass.

Sometimes that Heap size isn't enough. Luckily, there is a way to increase the Heap size for your app. To increase the size, add the tag bigHeap to your Application tag in the manifest file. However, increasing the Heap can be a double-edged sword. By having a large Heap, GC can take considerably longer because GC has to go through a larger Heap to check for objects that can be freed. So just using this tag to get away from memory leaks in your app is a pretty bad idea and will only make your app performance decrease.

This video also goes into memory leaks. To start off, a memory leak means you still have a reference to an unused object, which means GC can't remove an object. One common memory leak in Android involves keeping a reference to an Activity(or a context) for far too long. This is especially common when handling Activity rotation changes. Finding and fixing memory leaks is more of a dark art of software development. It's a very important skill to have in your pocket simply because the Garbage Collector won't save your memory in all cases. For example, if you happen to pass Context objects around frequently in your application, it's much more likely that a memory leak has crept into your app. By using the Eclipse Memory Analyzer, it's pretty simple to find a leak using a few tricks that are shown in the video (like using Dominator Trees) and to do a few changes to your code to fix it.

Monday, February 4, 2013

Hardware Acceleration in Android - Are You Using It?

Did you know Android has Hardware Acceleration? Did you also know you actually need to enable it for your app first? Suprisingly you do! It's not defaulted to on. Here's another little gem in Android that could have a major impact on your application.

If you allow your app to run on Android versions above 3.0, you should probably enable Hardware Acceleration. By enabling Hardware Acceleration, the performance of your application's UI may improve considerably. To enable Hardware Acceleration on an application, simply add the android:hardwareAccelerated tag to the manifest file.

After adding that tag to the application element, simply recompile and test your app. It is very important to fully test your app after you add this line. Although it's unlikely that Hardware Acceleration will negatively affect your app, it is certainly possible. It is a good idea to make sure all of the views and animations still work as you expect it to.

If you find that certain screens seem to have problems with Hardware Acceleration, you can disable it on a per Activity basis if needed as well. To do so, simply add the tag (set to false) to the activity tag within the manifest. This allows you to enable Hardware Acceleration for the entire application while removing it for certain parts. And this also works in the reverse. You can enable only specific Activities while leaving it off for the majority of the application.

One other interesting feature in the IO Session (linked below) is the concept of a View Layer. By using this new Layer method, you are able to use the GPU within the device to speed up animations (IE ListView scrolling). Check out View.setLayerType for some more information.

For more details about Hardware Acceleration--and really some interesting information on how views are actually drawn in Android--check out this Google IO Session. And like most things Android-related, Google has a detailed page here on Hardware Acceleration.

Monday, January 21, 2013

Android Library: Drag to Sort and Swipe to Delete List View

Here's a pretty nice custom list view that allows the user to sort the items in the list view by dragging rows up and down, or removing a row dynamically with a side swipe.

The source can be found on Github here. The list view runs very smoothly. Carl has also thrown up a simple demo app to show it off, too. Even if you don't need a list view with this type of functionality at the moment, it's a good custom widget to keep in mind for when you do need it.

The repo is fairly active as well. If you have any suggestions or requests, now would be the time to get your comments up there.

Sunday, January 13, 2013

Loaders versus AsyncTask

One of the biggest pieces of Android that I have neglected to learn about would be Loaders. Seeing as it's time for me to learn it, perhaps I can help you out a bit with it as well. My main interest with the Loader concept is how it melds with the tried and true AsyncTask, and if it's really better or not.

AsyncTask

Before getting into the Loader concept, it's important to have a good idea of what the AsyncTask is and what it's used for within Android. If you have written any sort of application for Android, chances are you have played with the AsyncTask, or at the very least heard of it. In Android, the AsyncTask class is one of the core development tools that most apps use. It gives the developer an easy way to do processing on a thread that isn't the UI thread. This keeps the UI thread focused on the UI instead of other time-intensive tasks, such as disk or server calls. There are a few issues with using AsyncTasks, though:
  • Configuration changes can mess things up
  • Pausing an activity doesn't pause the AsyncTask
  • A fair amount of boilerplate code (which means more possible errors)

Loaders

The AsyncTask isn't the only way to do background processing in Android, though. The Loader class is a much newer construct in Android (although now it's getting a bit dated). It was released with Honeycomb(3.0) and is now included in the Support Library. The beauty of the Loader is that it handles some of the "gotchas" that usually are missed when using the AsyncTask. Mainly, it handles activity configuration changes (IE when the user rotates the screen).
Loaders (specifically the CursorLoader) really shine when using Cursors within Android to pull data. The Loader class does an excellent job of updating the Cursor information (and in turn, the UI) whenever the underlying data changes. This is immensely helpful when information changes often and you don't want to interrupt the UI, and whatever the user is currently doing, just to display some new information.
One particular subclass of Loaders is of interest: the AsyncTaskLoader. This class performs the same function as the AsyncTask, but a bit better. It can handle Activity configuration changes more easily, and it behaves within the life cycles of Fragments and Activities. The nice thing is that the AsyncTaskLoader can be used in any situation that the AsyncTask is being used. Anytime data needs to be loaded into memory for the Activity/Fragment to handle, The AsyncTaskLoader can do the job better.
To really get into the details of how to actually implement a AsyncTaskLoader, check out these sources:

Sunday, December 16, 2012

Finding and Fixing Overdraw in Android

I just ran across an excellent article about new performance analysis tools in Android (adding in 4.1), and how to improve list scrolling beyond the standard ViewHolder pattern (which may fade away soon) and other tweaks.

Check it out here on curious-creature! I plan on testing my applications for overdraw in the next few days. One step closer to really smooth lists.