Phone, phablet, and tablet

A well-known pattern for phones is the list or the recycler view that show you some details when you click on any of the rows. On a small screen, the app will navigate you to a different view. This pattern simply exists because of the lack of space on the screen of a phone. If you run the same app on a device that has sufficient space, we can show the list on the left-hand side of the screen and the details on the right-hand side.

Multiple layouts are what we need, combined with multiple fragments. If we do this, we can reduce the amount of code we need to write. We just do not want to repeat ourselves, do we?

Fragments are a powerful but also an often misunderstood component of Android development. Fragments are (little) pieces of functionality and most of the time do have their own layouts. Using fragment containers, a fragment may reside in multiple places and on multiple activity-related layouts. This is how we can reuse functionality and layouts.

Fragments should be used carefully though. Without a proper strategy, an app that uses fragments can cause you a lot of trouble. Code within a fragment frequently refers to an activity. While this code may still be running, the fragment may be detached from the activity in between (for example, because the user has pressed the back button). This could result in a crash of your app.

Getting ready

To go through this recipe, you need to have Android Studio up and running, and a phone, phablet, and/or tablet device (physical ones are recommended as always; however, you can use Genymotion to create virtual ones).

Since we will be using the YouTube Android API, you need to have the latest YouTube Android app installed on your device as well. Check on your device whether it is there, or install or update it using the Google Play app in case it is not on your device or an update for it is available.

Finally, you need to have a developer's account. In case you do not have one yet, you need to create one first from http://developer.android.com/distribute/googleplay/start.html.

In addition to buying this book, getting yourself a developer's account is a very good investment, and I strongly recommend you to get one. You will need one in order to be able to submit your app to the Google Play store anyway!

How to do it...

Let's see how we can create our own wearable app and make it run on a device:

  1. Start a new Android Studio project. Name your application YouTubeMediaApp and enter packt.com in the Company Domain field. Click on the Next button.
  2. In the following dialog, only check the Phone and Tablet option and click on the Next button.
  3. In the next dialog, choose Blank activity and click on the Next button.
  4. In the Customize the Activity dialog, click on the Finish button.
  5. Android Studio will create the new project for you. From the Project view on the left-hand side of Android Studio, locate build.gradle within the app folder and open it.
  6. Open the build.gradle file within the app folder and add a dependency to the dependencies section for the YouTube services API. We are going to use this API to search for videos on YouTube:
    compile 'com.google.apis:google-api-services-youtube:v3-rev120-1.19.0'
  7. Synchronize the project (click on the Sync now link or use the Sync project with Gradle files button from the toolbar).
  8. Open the activity_main.xml layout. Create a frame layout that will act as a container for the fragment that we want to display here later. We will give it a nice background color for demonstration purposes. Let's pick orange:
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android=
      "http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:background="@android:color/holo_orange_light"
       android:id="@+id/main_container_for_list_fragment">
    </FrameLayout>
  9. Add a new layout and name it fragment_list.xml. Create a list view within a container. This list will contain the title and other information about the videos that we will find on YouTube:
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"     
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <ListView
        android:id="@+id/main_video_list_view"
    	android:visibility="visible"
    	android:padding="6dp"
    	android:layout_marginTop="0dp"
    	android:layout_width="match_parent"
    	android:layout_height="match_parent">
    	</ListView>
    </FrameLayout>
  10. Add a new Java class, name it ListFragment, and click on the OK button to continue.
  11. Make the new class a Fragment descendant and override the onCreate method. Create a private member for the list view and add a reference to the list view in the layout as shown in the following code:
    public class ListFragment extends Fragment {
      private ListView mListView;
      @Override
      public View onCreateView(LayoutInflater inflater,    
       ViewGroup container, Bundle savedInstanceState) 
        final View view= inflater.inflate(  
          R.layout.fragment_list, container, false);
        mListView = (ListView)view.findViewById(
         R.id.main_video_list_view);
        return view;
      }
    }

    Note

    Besides ListActivity, there is also a ListFragment class that you can descend from. For demo purposes, we will descend from Fragment class here and do some things ourselves.

  12. While adding the correct import statements (using the Alt + Enter shortcut or otherwise) you will be able to choose which package to import. You can choose between the android.app.Fragment and android.support.v4.app.Fragment packages. The last one is for backward compatibility purposes only. Since we will be using the latest SDK for our app, choose this import statement if asked:
    import android.app.Fragment;
  13. Add another private member for YouTube and a YouTube list and create a method named loadVideos. First, we will initialize the YouTube member:
    private YouTube mYoutube;
    private YouTube.Search.List mYouTubeList;
    private void loadVideos(String queryString){
     mYoutube = new YouTube.Builder(new NetHttpTransport(),
      new JacksonFactory(), new HttpRequestInitializer() {
       @Override
       public void initialize(HttpRequest hr) throws  
        IOException {}
     }).setApplicationName( 
      getString(R.string.app_name)).build();
    }
  14. Next, we will tell YouTube what we are looking for and what information we want the API to return. We need to wrap our code in a try catch construction as we do not know in advance whether we will be able to connect to YouTube. Add this to the end of the loadVideos method:
    try{
     mYouTubeList = mYoutube.search().list("id,snippet");      
     mYouTubeList.setType("video");
     mYouTubeList.setFields( 
      "items(id/videoId,snippet/title,snippet/   
          description,snippet/thumbnails/default/url)");
    }
    catch (IOException e) {
      Log.d(this.getClass().toString(), "Could not 
        initialize: " + e);
    }
  15. To use the YouTube API, you must register your app first. To do so, navigate your browser to https://console.developers.google.com/project.
  16. Click on the Create a project button. Enter YouTubeApp as the project name and click on the Create button.
  17. Once the project is created, the dashboard will be shown on the webpage. On the left-hand side, expand APIs and auth and click on APIs.
  18. On the right-hand side of the page, click on YouTube Data API. Click on the Enable API button.
  19. On the left-hand side again, click on Credentials just after APIs. Under Public API access, click on the Create new Key button.
  20. On the Create new key popup dialog box, click on the Android key button.
  21. Since this app is for demo purposes only, we do not need to look up the requested SHA1 value. Just click on the Create button.
    How to do it...
  22. Now, an API key will be created for you. Copy the value for API key.
  23. In the AndroidManifest.xml file, add a permission to access the Internet:
    android:name="android.permission.INTERNET"/>

Glue it together!

  1. Now back in the ListFragment class, tell the API about your key that is just next to the search call on the YouTube object:
    mYouTubeList.setKey("Your API key goes here");
  2. Create a new VideoItem class and add members to hold the requested information for each video. Note that we are using getters and setters here:
    private String title;
    private String description;
    private String thumbnailURL;
    private String id;
    public String getId() {
     return id;
    }
    public void setId(String id) {
     this.id = id;
    }
    public String getTitle() {
     return title;
    }
    public void setTitle(String title) {
     this.title = title;
    }
    public String getDescription() {
     return description;
    }
    public void setDescription(String description) {
     this.description = description;
    }
    public String getThumbnailURL() {
     return thumbnailURL;
    }
    public void setThumbnailURL(String thumbnail) {
     this.thumbnailURL = thumbnail;
    }
  3. Create a new layout and name it adapter_video.xml. Then, add text views to display the video information:
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="vertical"
       xmlns:android= 
        "http://schemas.android.com/apk/res/android"
      android:padding="6dp">
    <TextView
      android:id="@+id/adapter_video_id"android:textSize="14sp"android:textStyle="bold"android:layout_width="match_parent"android:layout_height="wrap_content" />
    <TextView
      android:id="@+id/adapter_video_title"android:textSize="20sp"android:layout_marginTop="2dp"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout>
  4. Create a new VideoAdapter class and make it an ArrayAdapter descendant that will be holding entries of the VideoItem type. A view holder will help us fill the text views with the properties of the listed VideoItem object:
    public class VideoAdapter extends ArrayAdapter<VideoItem> {
     private Context mContext;
     private int mAdapterResourceId;
     public ArrayList<VideoItem>mVideos = null;
     static class ViewHolder{
            TextView videoId;
            TextView videoTitle;
        }
    @Override
     public int getCount(){
     super.getCount();
     int count = mVideos !=null ? mVideos.size() : 0;
     return count;
    }
    public VideoAdapter (Context context, int  
     adapterResourceId, ArrayList<VideoItem> items)
    {
     super(context, adapterResourceId, items);
     this.mVideos = items;
     this.mContext = context;
     this.mAdapterResourceId = adapterResourceId; 
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent)
    {
     View v = convertView;
    if (v == null){LayoutInflater vi =   
         (LayoutInflater)this.getContext().getSystemService(
          Context.LAYOUT_INFLATER_SERVICE);
        v = vi.inflate(mAdapterResourceId, null);
        ViewHolder holder = new ViewHolder();
        holder.videoId = (TextView)  
         v.findViewById(R.id.adapter_video_id);
        holder.videoTitle = (TextView) 
         v.findViewById(R.id.adapter_video_title);     
        v.setTag(holder);
     }
     final VideoItem item = mVideos.get(position);
     if(item != null){
      final ViewHolder holder = (ViewHolder)v.getTag();
      holder.videoId.setText(item.getId());
      holder.videoTitle.setText( item.getTitle());
     }
     return v;
    }
  5. Now back to the ListFragment class. Add two more private members in it, one for the list of videos that we have found and one for the adapter that we have just created:
    private List<VideoItem>mVideos;
    private VideoAdapter mAdapter;
  6. Add a search method to the ListFragment class:
    public List<VideoItem> search(String keywords){
     mYouTubeList.setQ(keywords);
    try{
       SearchListResponse response = mYouTubeList.execute();
       List<SearchResult> results = response.getItems();
       List<VideoItem>  items = new ArrayList<VideoItem>();
        for(SearchResult result:results){
         
        VideoItem item = new VideoItem();
        item.setTitle(result.getSnippet().getTitle());
        item.setDescription(result.getSnippet().
         getDescription());
                   
        item.setThumbnailURL(result.getSnippet().
         getThumbnails().getDefault().getUrl());
        item.setId(result.getId().getVideoId());
        items.add(item);
      }
      return items;
     }
    catch(IOException e){
      Log.d("TEST", "Could not search: " + e);
     }
    }
  7. Toward the end of the loadVideos method, add the implementation to call the search method and initialize the adapter:
    mVideos =search(queryString§);
    mAdapter = new VideoAdapter(getActivity(), R.layout.adapter_video, (ArrayList<VideoItem>) mVideos);
  8. Tell the list view about the adapter and call the notifyDataSetChanged method of the adapter to inform that new entries are available to be shown. For this, we will use a Runnable instance that will be running on the UI thread:
    getActivity().runOnUiThread(new Runnable() {
    public void run() {
       mListView.setAdapter(mAdapter);
       mAdapter.notifyDataSetChanged();
     }
    });
  9. Now we will load the video information asynchronously, as we do want the app to be responsive while getting data from the Internet. Create a new thread and call loadVideos inside within the run method. Let's assume we want to look at Android development videos:
    @Override
     public void onActivityCreated(Bundle bundle){
     super.onActivityCreated(bundle);
     new Thread(new Runnable() {
       public void run(){
          loadVideos("Android development");
       }
    }).start();
    }
  10. Create a new layout and name it fragment_details.xml. In this fragment, we will display a thumbnail and the description of a video that the user has selected from the list. Since we are here anyway, let's add a play button as well. We will need it in the next recipe:
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout  xmlns:android=  
     "http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent">
    <Button
    android:id="@+id/detail_button_play"android:text="@string/play"android:layout_width="match_parent"android:layout_height="wrap_content" />
    <ImageView
    android:id="@+id/detail_image"android:layout_width="match_parent"android:layout_height="wrap_content"android:src="@android:drawable/gallery_thumb"/>
    <TextView
    android:layout_marginTop="16dp"android:id="@+id/detail_text"android:minHeight="200dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />
    </LinearLayout>
  11. Create the DetailsFragment class:
    public class DetailsFragment  extends Fragment {
      @Override
      public View onCreateView(LayoutInflater inflater,
       ViewGroup container, Bundle savedInstanceState) {
        final View view= inflater.inflate(
          R.layout.fragment_details, container, false);
         return view;
      }
    }
  12. Add the showDetails private method to DetailsFragment class. In this method, we will set the text for the description and create a new runnable instance to load the thumbnail for the video. Also, add the setVideo method and override the onResume method:
    private void showDetails(){
    if (getView()!=null &&mVideo != null)
     {
       TextView tv = (TextView) 
        getView().findViewById(R.id.detail_text);
       final ImageView iv = (ImageView)    
        getView().findViewById(R.id.detail_image);
       tv.setText(mVideo.getDescription());
      new Thread(new Runnable() {
       public void run() {
          loadThumbnail(mVideo, iv);
        }
       }).start();
      }
    }
    public void setVideo(VideoItem video)
    {
      mVideo = video;
      showDetails();
    }
    @Override
      public void onResume(){
      super.onResume();
      showDetails();
    }
  13. Now, add the loadThumbnail method to DetailsFragment class and the implementation to load the thumbnail image from the given URL:
    private void loadThumbnail(VideoItem video,final  
     ImageView iv){
    try 
     {
        URL url = new URL(video.getThumbnailURL());
       final Bitmap bmp = BitmapFactory.decodeStream(   
        url.openConnection().getInputStream());
           
       getActivity().runOnUiThread(new Runnable() {
        public void run() {
          iv.setImageBitmap(bmp);
         }
        });
     }
     catch (Exception ex){
        Log.d(this.getClass().toString(), ex.getMessage());
     }
    }
  14. If the user selects an item from the list view in the ListFragment class, we need to tell DetailFragment to display the corresponding details. In the onCreateView method of the ListFragment class, add the onItemClick handler:
    mListView.setOnItemClickListener(new 
     AdapterView.OnItemClickListener() 
    {
      @Override
      public void onItemClick(AdapterView<?> adapterView,    
        View view, int i, long l) 
        {
            VideoItem video = mVideos.get(i);
            onVideoClicked(video);
        }
    });
    return view;
  15. In the MainActivity class, add two static members that will represent the tags for both the ListFragment and DetailsFragment classes:
    public static String TAG_LIST_FRAGMENT = "LIST";
    public static String TAG_DETAILS_FRAGMENT = "DETAILS";
    

    Create the onVideoClicked method in the ListFragment class. If DetailsFragment exists (there is a fragment out there with the DETAILS tag), it will call the showDetails method of DetailsFragment:

    private void onVideoClicked(VideoItem video) {  
      DetailFragment detailsFragment = (DetailFragment)   
       getFragmentManager().findFragmentByTag(   
        MainActivity.TAG_DETAILS_FRAGMENT);
    if (detailsFragment != null) { 
      detailsFragment.setVideo(video);}
    }
  16. We are almost done. In the activity_main.xml layout, we created a container for our fragment. Now we will add some code to show the content for ListFragment in that container. In the MainActivity class, add two private members for both the fragments:
    private DetailFragment mDetailsFragment;
    private ListFragment mListFragment;
  17. Create ListFragment and add it to the container:
    mListFragment = new ListFragment();
    FragmentTransaction ft =  
     getFragmentManager().beginTransaction();
    ft.add(R.id.main_container_for_list_fragment, 
     mListFragment, TAG_LIST_FRAGMENT);
    ft.commit();
  18. Let's create another layout for the main activity but this time it will be one for the large screens, let's say tablets. To the res folder, add a new Android resource directory by right-clicking on the res item. Choose layout for resource type, name the directory layout-large, and click on the To button.
  19. Within the new layout-large directory, add a new layout and name it activity_main as well. A tablet device is big enough to hold both our fragments so for this layout, we will create two containers: one for the list and one for the details:
    <?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android=  
     "http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/main_container">
    <FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="300dp"
    android:layout_height="match_parent"
    android:background="@android:color/holo_orange_light"
    android:id="@+id/main_container_for_list_fragment">
    </FrameLayout>
    <FrameLayout
    android:id="@+id/main_container_for_detail_fragment"android:background="@android:color/holo_blue_light"
    android:layout_marginLeft="300dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    </FrameLayout>
    </FrameLayout>
  20. Modify the onCreate implementation for MainActivity. If the container is available, we will load the details fragment as well. Move the commit call to the end:
    mListFragment = new ListFragment();
    FragmentTransaction ft =  
     getFragmentManager().beginTransaction();
    ft.add(R.id.main_container_for_list_fragment,  mListFragment, TAG_LIST_FRAGMENT);
    if (findViewById(  
     R.id.main_container_for_detail_fragment)!= null){
      mDetailsFragment = new DetailFragment();ft.add(R.id.main_container_for_detail_fragment,  
      mDetailsFragment, TAG_DETAILS_FRAGMENT);
    }
    ft.commit();
  21. One more thing, if you'll allow me to explain. Well, a couple of things actually. If the app is running on a phone, we need to have some kind of navigation from the list fragment view to the details fragment view. Modify the onVideoClicked method in the MainActivity file so that in case it does not exist yet, the detail fragment will be created there:
    private void onVideoClicked(VideoItem video) {
      DetailFragment detailsFragment = (DetailFragment)    
       getFragmentManager().findFragmentByTag(  
        MainActivity.TAG_DETAILS_FRAGMENT);
     if (detailsFragment != null) {
       detailsFragment.setVideo(video);
     }
     else
     {
       FragmentTransaction ft =  getFragmentManager().beginTransaction();
       detailsFragment = new DetailFragment();
       ft.add(R.id.main_container_for_list_fragment,  
        detailsFragment, MainActivity.TAG_DETAILS_FRAGMENT);
       ft.addToBackStack(MainActivity.TAG_DETAILS_FRAGMENT); 
       ft.commit();
       detailsFragment.setVideo(video);
     }
    }
  22. The call to addToBackStack that we added in the previous step informs the fragment manager about all fragments being on stack, so we can provide a way of navigation. We need to tell our activity how to behave in case the back button is being pressed: do we want to leave the activity or do we want to pop a fragment from stack? We will override the onBackPressed method of the MainActivity, just like this:
    @Override 
    public void onBackPressed() {
    if (getFragmentManager().getBackStackEntryCount()>0){
            getFragmentManager().popBackStack();
        }
    else {
    this.finish();
        }
    }

And we are done! We had some work to do but now we have got an app that will work on a phone with navigation and that will display both the fragments if there is sufficient space as is the case with a tablet.

To see the differences, run the app on a smart phone and on a tablet as well. On a phone, it will look similar to the following screenshot. On a tablet (you can use Genymotion for that if you do not have one available) both the list and details are shown in a single view:

Glue it together!

There's more...

The next recipe will show how to implement the functionality that allows us to watch the video that we have just found. After all, playing videos is what we want!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset