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.
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 Refactor → Rename.... 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(); } } }
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 New → Java 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.
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 extendsAppCompatActivitySingleFragmentActivity {/** Called when the activity is first created. */@Overrideprotected 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(); } }
Now, you will create the two new controller classes: CrimeListActivity and CrimeListFragment.
Right-click on the com.bignerdranch.android.criminalintent package, select New → Java 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 New → Java 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.
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.