Activity肥大化を防ぐデザインパターン
では、10個のアプリを解析して見つけたActivity肥大化問題に対する良い解決策をデザインパターンとして紹介します。トップエスイーの修了制作では4つのデザインパターンを提示しましたが、今回はそのうちの1つについて、紹介・解説をしたいと思います。
提示するのは「Fragmentを使って、Layoutのパーツごとに実装クラスを分ける」デザインパターンです。肥大化したクラスを分割するのはもちろん一般的な対処です。ここでポイントになることは、AndroidのActivity周りで使われている「よくある機能分割の方針」を、調査したいくつかのアプリケーションから抽出できたということです。その方針とは、Activityクラスの中身を画面上のパーツごとにFragmentクラスのサブクラスに分解する、ということです。分解するため、それぞれのFragmentクラスのサブクラスは元のActivityのサブクラスより管理しやすい小さな単位になり、実装する機能も特定の機能に絞られます。
この解決策は10個のアプリのうち4個のアプリ、具体的には以下のアプリで使われていました。
- K-9 Mail
- Offline Calendar
- Open-camera
- Calculator
1つのアプリケーションで見られるだけの手法でももちろん、意味はあるでしょう。しかしながら、これは多数のユーザーの支持を集めているアプリケーションのうち4つのアプリケーションで共通に使われている手法です。この手法はより広い問題を解くことができる可能性が大いにあるといえます。さまざまな問題に適用できる可能性がある手法を「パターン」として提唱することで、これから先に立ち向かう必要がある問題にも適用しやすくなります。そしてパターン適用を繰り返し行うことで、パターンそのものの分析がさらに進むことも期待できます。
デザインパターンを利用せずに、シンプルなAndroidアプリを実装する
具体的なコード例を挙げながら解説していきます。まず、デザインパターンを利用せずにシンプルなAndroidアプリを実装してみます。その後、デザインパターンを適用した実装を行います。
AndroidStudio(バージョンは2.3.1)でActivityを作成する際に「BasicActivity」というテンプレートを利用すると、図1のような画面を表示するActivityが生成されます。FloatingActionButtonが1つと、メニューが実装されています。
自動生成されたActivityクラスの実装はリスト1の通りです。なお、自動生成されたコメント行は削除しました。onCreateとonCreateOptionsMenu、onOptionsItemSelectedの3つのメソッドが最初から定義されています。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// FloatingActionButtonに関する実装
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
// メニューに関する実装
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
まだ機能を実装していないのにも関わらず、コード量はそこそこ多くなっています。特にFloatingActionButtonとメニューに関する実装があるので、その分コード量増加につながっています。FloatingActionButtonが押された時の処理と、メニューが選択された時の処理が書かれると、さらにコード量は増えます。それに加え、この画面でのメインの機能が実装されれば、Activityクラスのソースコード量は膨大になることが容易に想像できます。
メインの機能を追加してみましょう。画面の中央にボタンを配置して、ボタンが押されたらメッセージを画面下に表示する、といったシンプルな機能を実装します。何の工夫も行わずに素直に実装しようとすると、まずレイアウト定義のファイルにボタンを追加すると思います。ボタン追加後のレイアウトファイル定義はリスト2のようになります。
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="kazu.code_zin_sample_app.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<!-- ボタンを追加 -->
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button"
android:layout_gravity="center"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
「ボタンを追加」とコメントした箇所以外は自動生成されたxmlファイルです(厳密にはincludeが使われていたのですが、シンプルにするためincludeを使わない構成に変更しました)。
続いて、リスト3の通りにActivityクラスのサブクラスであるMainActivityに機能追加の実装を行います。MainActivityクラスのonCreate()メソッドにコードを追加します。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
// メインの機能の実装
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Button clicked.", Snackbar.LENGTH_LONG).show();
}
});
}
onCreate()が少し大きなメソッドになってしまいました。1つのクラスに、さまざまな処理(FloatingActionが押された時の処理、画面中央のボタンが押された時の処理、メニューに関する処理)が実装されているのも望ましくないですね。
