An Abstract Activity for Hosting a Fragment

In a moment, you will create the CrimeListActivity class that will host a CrimeListFragment. First, you are going to set up a view for CrimeListActivity.

A generic fragment-hosting layout

For CrimeListActivity, you can simply reuse the layout defined in activity_crime.xml (which is copied in Listing 8.4). This layout provides a FrameLayout as a container view for a fragment, which is then named in the activity’s code.

Listing 8.4  activity_crime.xml is already generic

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />

Because activity_crime.xml does not name a particular fragment, you can use it for any activity hosting a single fragment. Rename it activity_fragment.xml to reflect its larger scope.

In the project tool window, right-click res/layout/activity_crime.xml. (Be sure to right-click activity_crime.xml and not fragment_crime.xml.)

From the context menu, select RefactorRename.... Rename this layout activity_fragment.xml and click Refactor.

When you rename a resource, the references to it should be updated automatically. If you see an error in CrimeActivity.java, then you need to manually update the reference in CrimeActivity, as shown in Listing 8.5.

Listing 8.5  Updating layout file for CrimeActivity (CrimeActivity.java)

public class CrimeActivity extends AppCompatActivity {
    /** Called when the activity is first created. */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_crime);
        setContentView(R.layout.activity_fragment);

        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragment_container);

        if (fragment == null) {
            fragment = new CrimeFragment();
            fm.beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit();
        }
    }
}

An abstract Activity class

To create the CrimeListActivity class, you could reuse CrimeActivity’s code. Look back at the code you wrote for CrimeActivity (which is copied in Listing 8.6). It is simple and almost generic. In fact, the only nongeneric code is the instantiation of the CrimeFragment before it is added to the FragmentManager.

Listing 8.6  CrimeActivity is almost generic (CrimeActivity.java)

public class CrimeActivity extends AppCompatActivity {
    /** Called when the activity is first created. */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment);

        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragment_container);

        if (fragment == null) {
            fragment = new CrimeFragment();
            fm.beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit();
        }
    }
}

Nearly every activity you will create in this book will require the same code. To avoid typing it again and again, you are going to stash it in an abstract class.

Right-click on the com.bignerdranch.android.criminalintent package, select NewJava Class, and name the new class SingleFragmentActivity. Make this class a subclass of AppCompatActivity and make it an abstract class. Your generated file should look like this:

Listing 8.7  Creating an abstract Activity (SingleFragmentActivity.java)

public abstract class SingleFragmentActivity extends AppCompatActivity {

}

Now, add the following code to SingleFragmentActivity.java. Except for the highlighted portions, it is identical to your old CrimeActivity code.

Listing 8.8  Adding a generic superclass (SingleFragmentActivity.java)

public abstract class SingleFragmentActivity extends AppCompatActivity {

    protected abstract Fragment createFragment();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment);

        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragment_container);

        if (fragment == null) {
            fragment = createFragment();
            fm.beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit();
        }
    }
}

In this code, you set the activity’s view to be inflated from activity_fragment.xml. Then you look for the fragment in the FragmentManager in that container, creating and adding it if it does not exist.

The only difference between the code in Listing 8.8 and the code in CrimeActivity is an abstract method named createFragment() that you use to instantiate the fragment. Subclasses of SingleFragmentActivity will implement this method to return an instance of the fragment that the activity is hosting.

Using an abstract class

Try it out with CrimeActivity. Change CrimeActivity’s superclass to SingleFragmentActivity, remove the implementation of onCreate(Bundle), and implement the createFragment() method as shown in Listing 8.9.

Listing 8.9  Cleaning up CrimeActivity (CrimeActivity.java)

public class CrimeActivity extends AppCompatActivity SingleFragmentActivity {
    /** Called when the activity is first created. */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment);

        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragment_container);

        if (fragment == null) {
            fragment = new CrimeFragment();
            fm.beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit();
        }
    }

    @Override
    protected Fragment createFragment() {
        return new CrimeFragment();
    }
}

Creating the new controllers

Now, you will create the two new controller classes: CrimeListActivity and CrimeListFragment.

Right-click on the com.bignerdranch.android.criminalintent package, select NewJava Class, and name the class CrimeListActivity.

Modify the new CrimeListActivity class to also subclass SingleFragmentActivity and implement the createFragment() method.

Listing 8.10  Implementing CrimeListActivity (CrimeListActivity.java)

public class CrimeListActivity extends SingleFragmentActivity {

    @Override
    protected Fragment createFragment() {
        return new CrimeListFragment();
    }
}

If you have other methods in your CrimeListActivity, such as onCreate, remove them. Let SingleFragmentActivity do its job and keep CrimeListActivity simple.

The CrimeListFragment class has not yet been created. Let’s remedy that.

Right-click on the com.bignerdranch.android.criminalintent package again, select NewJava Class, and name the class CrimeListFragment.

Listing 8.11  Implementing CrimeListFragment (CrimeListFragment.java)

public class CrimeListFragment extends Fragment {

      // Nothing yet

}

For now, CrimeListFragment will be an empty shell of a fragment. You will work with this fragment later in the chapter.

Now your activity code is nice and tidy. And SingleFragmentActivity will save you a lot of typing and time as you proceed through the book.

Declaring CrimeListActivity

Now that you have created CrimeListActivity, you must declare it in the manifest. In addition, you want the list of crimes to be the first screen that the user sees when CriminalIntent is launched, so CrimeListActivity should be the launcher activity.

In the manifest, declare CrimeListActivity and move the launcher intent filter from CrimeActivity’s declaration to CrimeListActivity’s declaration.

Listing 8.12  Declaring CrimeListActivity as the launcher activity (AndroidManifest.xml)

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity android:name=".CrimeListActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity android:name=".CrimeActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

</application>

CrimeListActivity is now the launcher activity. Run CriminalIntent and you will see CrimeListActivity’s FrameLayout hosting an empty CrimeListFragment, as shown in Figure 8.3.

Figure 8.3  Blank CrimeListActivity screen

Screenshot shows CriminalIntent in Android phone. The CriminalIntent screen is shown blank.
..................Content has been hidden....................

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