Search This Blog

03 January 2012

Fragments

Well, I've been needing to look into Fragments for awhile now...

First, to generate a blank project...

malachi@onyx:~/work$ mvn archetype:generate -DarchetypeCatalog=http://repository-malachid.forge.cloudbees.com/public-snapshot/archetype-catalog.xml
[INFO] Scanning for projects...
[INFO]                                                                        
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom <<<
[INFO]
[INFO] --- maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: http://repository-malachid.forge.cloudbees.com/public-snapshot/archetype-catalog.xml -> org.eoti.kryten:kryten-archetype (kryten-archetype)
2: http://repository-malachid.forge.cloudbees.com/public-snapshot/archetype-catalog.xml -> org.eoti.galatea:galatea-archetype (galatea-archetype)
3: http://repository-malachid.forge.cloudbees.com/public-snapshot/archetype-catalog.xml -> org.eoti.archtest:archtest-archetype (archtest-archetype)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 2
Define value for property 'groupId': : org.eoti.android.test.fragments
Define value for property 'artifactId': : FragmentTest
Define value for property 'version':  1.0-SNAPSHOT: :
Define value for property 'package':  org.eoti.android.test.fragments: :
Confirm properties configuration:
groupId: org.eoti.android.test.fragments
artifactId: FragmentTest
version: 1.0-SNAPSHOT
package: org.eoti.android.test.fragments
 Y: :
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: galatea-archetype:1.1-SNAPSHOT
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: org.eoti.android.test.fragments
[INFO] Parameter: artifactId, Value: FragmentTest
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: org.eoti.android.test.fragments
[INFO] Parameter: packageInPathFormat, Value: org/eoti/android/test/fragments
[INFO] Parameter: package, Value: org.eoti.android.test.fragments
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: groupId, Value: org.eoti.android.test.fragments
[INFO] Parameter: artifactId, Value: FragmentTest
[WARNING] Don't override file /home/malachi/work/FragmentTest/src/main/android/res/values/strings.xml
[WARNING] Don't override file /home/malachi/work/FragmentTest/src/main/android/res/layout/main.xml
[INFO] project created from Archetype in dir: /home/malachi/work/FragmentTest
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 21.579s
[INFO] Finished at: Tue Jan 03 08:30:32 PST 2012
[INFO] Final Memory: 8M/245M
[INFO] ------------------------------------------------------------------------
malachi@onyx:~/work$ cd FragmentTest/
malachi@onyx:~/work/FragmentTest$ tree
.
├── pom.xml
└── src
    ├── AndroidManifest.xml
    └── main
        ├── android
        │   └── res
        │       ├── drawable
        │       │   └── icon.png
        │       ├── layout
        │       │   └── main.xml
        │       └── values
        │           └── strings.xml
        └── java
            └── org
                └── eoti
                    └── android
                        └── test
                            └── fragments
                                └── FragmentTestActivity.java

13 directories, 6 files


While it should be possible to use the Fragments with pre-HONEYCOMB (http://android-developers.blogspot.com/2011/03/fragments-for-all.html); but for now, let's just try it in the ICS emulator.

Based on the available jars in search.maven.org (http://search.maven.org/#artifactdetails|com.google.android|android|4.0.1.2|jar) let's set the version of the Android dependency in the pom to 4.0.1.2.

Also, change the <sdk><platform>10</platform></sdk> to <sdk><platform>14</platform></sdk>.

For now, comment out the maven-jarsigner-plugin as that requires setting up a keystore.
You'll also need to comment out the <sign /> portion of the android-maven-plugin.

Start up an ICS emulator and try 'mvn clean install' just to make sure we are able to grab the dependencies.
Launch the FragmentTest and you should see your hello world.

Let's move on to adding fragments... this part is derived from http://www.vogella.de/articles/Android/article.html#fragments_tutorial
I've modified their instructions quite a bit to fit into our Maven structure as well as to make things more clear (they had ListFragment extends ListFragment which tends to confuse people).

First, copy src/main/android/res/layout/main.xml to src/main/android/res/layout/detail.xml
On the new detail.xml, give the TextView an id:
android:id="@+id/ftDetailText"

Now, we'll deal the with portrait layout for FragmentTestActivity:
Replace the TextView in the layout/main.xml with:
    <fragment
        android:id="@+id/ftListFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="org.eoti.android.test.fragments.FTListFragment"
    />

Now, let's modify it for landscape...

Copy src/main/android/res/layout/main.xml to src/main/android/res/layout-land/main.xml [note: you will have to create the layout-land directory]

Now, for the layout-land/main.xml:
1. change the LinearLayout to Horizontal:
android:orientation="horizontal"
2. copy/paste the fragment so you now have 2 of them
3. for the first one. let's change the width to:
android:layout_width="300dip"
4. for the second one, change the id to:
android:id="@+id/ftDetailFragment"
5. Change the class to
class="org.eoti.android.test.fragments.FTDetailFragment"



Note: I changed the name of these classes so it would be obvious that these are our classes, and not some default Android implementation. Perhaps a redundant point since we also set the package name.


We'll need to have a separate activity for the detail portion in portrait mode, so let's create the layout for that.
Copy src/main/android/res/layout-land/main.xml to src/main/android/res/layout/details.xml [note: plural details not detail]

For the new details.xml:
1. change the LinearLayout back to Vertical:
android:orientation="vertical"
2. remove the ftListFragment <fragment />

We already have our main FragmentTestActivity.  We are missing our DetailActivity. Let's do some more copy/paste.
Copy FragmentTestActivity.java to DetailActivity.java (in the same directory)
Let's edit DetailActivity.java:
1. Change the class name to DetailActivity [I'd change the TAG to match]
2. change the setContentView to R.layout.details
3. add the following just after setContentView:
        Bundle extras = getIntent().getExtras();
        if (extras != null) {
            TextView view = (TextView) findViewById(R.id.ftDetailText);
            view.setText(System.getProperty(extras.getString("key")));
        }
4. Make sure to add the import android.widget.TextView;

We've defined two fragments in the layouts.  We need to create those.
First, we'll create our src/main/java/org/eoti/android/test/fragments/FTDetailFragment.java
This one is pretty simple:

package org.eoti.android.test.fragments;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class FTDetailFragment extends Fragment
{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.detail, container,  false);
    }

    public void setText(String key)
    {
        ((TextView)getView().findViewById(R.id.ftDetailText)).setText(System.getProperty(key));
    }

}


Next, we'll create our src/main/java/org/eoti/android/test/fragments/FTListFragment.java
This one isn't much more difficult:

package org.eoti.android.test.fragments;

import android.*;
import android.R;
import android.app.ListFragment;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import java.util.ArrayList;

public class FTListFragment extends ListFragment
{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ArrayList keys = new ArrayList(System.getProperties().keySet());
        ArrayAdapter adapter = new ArrayAdapter(getActivity(), R.layout.simple_list_item_1, keys);
        setListAdapter(adapter);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        String key = getListAdapter().getItem(position).toString();
        FTDetailFragment fragment = (FTDetailFragment)getFragmentManager().findFragmentById(org.eoti.android.test.fragments.R.id.ftDetailFragment);
        if(fragment != null)
        {
            fragment.setText(key);
        }else{
            Intent intent = new Intent(getActivity().getApplicationContext(), DetailActivity.class);
            intent.putExtra("key", key);
            startActivity(intent);
        }
    }
}

Last, but not least, we need to add the other activity to your AndroidManifest. Put this inside your <application /> tag.
          <activity android:name=".DetailActivity" />



mvn clean install

Launch the application (in portrait mode, I assume)
Select something. You should see a new activity popup with the value of the property you chose.
Hit back, and try it a few times.
When you are done, rotate the emulator using CTRL-F11.  I'm not sure about anyone else, but I have to hit it twice quickly or it won't rotate.
Now, when you select an item on the left, the answer is on the right.
When you are done playing with it, CTRL-F12 [twice in my case] to go back to portrait.

UPDATE: If you wish to use it with older devices...
First, I used the  maven-android-sdk-deployer to create the maven-compliant compat library for me.

        <dependency>
            <groupId>android.support</groupId>
            <artifactId>compatibility-v13</artifactId>
            <version>r6</version>
        </dependency>

This allows older versions of Android to use the Fragments. In my case, I tested against an Android 2.3.3 emulator.

I made no other changes to the pom.  IE: I compiled against a newer SDK and deployed against both.

Other changes to make:
  1. make FTDetailFragment extend android.support.v4.app.Fragment
  2. make FTListFragment extend android.support.v4.app.ListFragment
  3. make DetailActivity extend android.support.v4.app.FragmentActivity
  4. make FragmentTestActivity extend android.support.v4.app.FragmentActivity
  5. In FTListFragment, instead of using getFragmentManager you need to call getSupportFragmentManager.  This is part of the FragmentActivity, so you will need to do something like:
((FragmentActivity)getActivity()).getSupportFragmentManager()...

mvn clean install and it should all work... close down the 2.3.3 emulator, start up an ICS emulator and.. it will still work ;)

2 comments:

  1. I saw this post today:

    QUOTE:
    Dianne Hackborn - +Jake Wharton The v4 library works down to API 4; the v13 library works down to API 13. The classes inside are implementations of the library and are there to allow various things to use newer APIs if they are available. The classes in the v13 library require API 13 or better though.

    I'm a little confused because I used the -v13 above on the Android 2.3.3 emulator... but it seems like she is saying I should have had to use the -v4 one instead...?

    ReplyDelete
  2. I like your blog Intelligence and I am also agree with your opinion.This is one of the perfect and good post.Thanks for share with us..

    ReplyDelete