フラグメントナビゲーションパターンに関するAndroid開発者ガイド

公開: 2022-03-11

何年にもわたって、Androidでのさまざまなナビゲーションパターンの実装を見てきました。 一部のアプリはアクティビティのみを使用していましたが、他のアプリはフラグメントやカスタムビューと混合されていました。

私のお気に入りのナビゲーションパターンの実装の1つは、「One-Activity-Multiple-Fragments」哲学、または単にフラグメントナビゲーションパターンに基づいています。このパターンでは、アプリケーションのすべての画面がフルスクリーンフラグメントであり、これらのフラグメントのすべてまたはほとんどがに含まれています。 1つのアクティビティ。

このアプローチは、ナビゲーションの実装方法を簡素化するだけでなく、パフォーマンスが大幅に向上し、その結果、ユーザーエクスペリエンスが向上します。

この記事では、Androidでの一般的なナビゲーションパターンの実装をいくつか見てから、フラグメントベースのナビゲーションパターンを紹介し、他のナビゲーションパターンと比較対照します。 このパターンを実装するデモアプリケーションがGitHubにアップロードされました。

活動の世界

アクティビティのみを使用する一般的なAndroidアプリケーションは、ツリーのような構造(より正確には有向グラフ)に編成され、ランチャーによってルートアクティビティが開始されます。 アプリケーション内を移動すると、OSによって維持されるアクティビティバックスタックがあります。

簡単な例を次の図に示します。

フラグメント画像1

アクティビティA1は、アプリケーションのエントリポイントであり(たとえば、スプラッシュ画面またはメインメニューを表します)、そこからユーザーはA2またはA3に移動できます。 アクティビティ間で通信する必要がある場合は、 startActivityForResult()を使用するか、グローバルにアクセス可能なビジネスロジックオブジェクトをアクティビティ間で共有することができます。

新しいアクティビティを追加する必要がある場合は、次の手順を実行する必要があります。

  • 新しいアクティビティを定義する
  • AndroidManifest.xmlに登録します
  • 別のアクティビティのstartActivity()で開きます

もちろん、このナビゲーション図はかなり単純なアプローチです。 バックスタックを操作する必要がある場合、または同じアクティビティを複数回再利用する必要がある場合、たとえば、いくつかのチュートリアル画面を介してユーザーをナビゲートしたいが、各画面が実際には同じアクティビティを使用する場合、非常に複雑になる可能性があります。ベース。

幸い、タスクと呼ばれるツールと、適切なバックスタックナビゲーションのためのガイドラインがあります。

次に、APIレベル11でフラグメントが登場しました…

フラグメントの世界

AndroidはAndroid3.0(APIレベル11)にフラグメントを導入しました。これは主に、タブレットなどの大画面でより動的で柔軟なUIデザインをサポートするためです。 タブレットの画面は受話器の画面よりもはるかに大きいため、UIコンポーネントを組み合わせたり交換したりする余地があります。 フラグメントを使用すると、ビュー階層への複雑な変更を管理しなくても、このような設計が可能になります。 アクティビティのレイアウトをフラグメントに分割することで、実行時にアクティビティの外観を変更し、アクティビティによって管理されるバックスタックにそれらの変更を保持できるようになります。 –フラグメントに関するGoogleのAPIガイドから引用。

この新しいおもちゃにより、開発者はマルチペインUIを構築し、他のアクティビティでコンポーネントを再利用できました。 一部の開発者はこれを気に入っていますが、他の開発者はそうではありません。 フラグメントを使用するかどうかはよくある議論ですが、フラグメントを使用すると複雑さが増し、開発者がフラグメントを適切に使用するには、フラグメントを理解する必要があることに誰もが同意すると思います。

Androidのフルスクリーンフラグメントの悪夢

フラグメントが画面の一部を表しているだけでなく、実際には画面全体がアクティビティに含まれているフラグメントである例がますます増えています。 すべてのアクティビティにフルスクリーンフラグメントが1つだけあり、それ以上のものがないデザインを見たことがあります。これらのアクティビティが存在する唯一の理由は、これらのフラグメントをホストすることでした。 明らかな設計上の欠陥の次に、このアプローチには別の問題があります。 下の図を見てください。

フラグメント画像2

A1はどのようにF1と通信できますか? さて、A1はF1を作成して以来、F1を完全に制御しています。 A1は、たとえばF1の作成時にバンドルを渡すか、そのパブリックメソッドを呼び出すことができます。 F1はどのようにA1と通信できますか? これはもっと複雑ですが、A1がF1にサブスクライブし、F1がA1に通知するコールバック/オブザーバーパターンで解決できます。

しかし、A1とA2はどのように相互に通信できますか? これは、たとえばstartActivityForResult()を介してすでに説明されています。

そして今、本当の問題がやってくる:F1とF2はどのように相互に通信できるのか? この場合でも、グローバルに利用可能なビジネスロジックコンポーネントを使用できるため、データの受け渡しに使用できます。 しかし、これが必ずしもエレガントなデザインにつながるとは限りません。 F2がより直接的な方法でF1にデータを渡す必要がある場合はどうなりますか? コールバックパターンを使用すると、F2はA2に通知でき、A2は結果で終了し、この結果はF1に通知するA1によってキャプチャされます。

このアプローチには多くの定型コードが必要であり、すぐにバグ、苦痛、怒りの原因になります。

すべてのアクティビティを取り除き、残りのフラグメントを保持するアクティビティの1つだけを保持できるとしたらどうでしょうか。

フラグメントナビゲーションパターン

何年にもわたって、ほとんどのアプリケーションで「One-Activity-Multiple-Fragments」パターンを使用し始めましたが、今でも使用しています。 このアプローチについては、たとえばここやここで多くの議論があります。 しかし、私が見逃したのは、自分で見てテストできる具体的な例です。

次の図を見てみましょう。

Framgnet画像3

これで、コンテナアクティビティは1つだけになり、複数のフラグメントがあり、これもツリーのような構造になっています。 それらの間のナビゲーションはFragmentManagerによって処理され、バックスタックがあります。

ここではstartActivityForResult()がありませんが、コールバック/オブザーバーパターンを実装できることに注意してください。 このアプローチの長所と短所を見てみましょう。

長所:

1.よりクリーンで保守しやすいAndroidManifest.xml

アクティビティが1つしかないため、新しい画面を追加するたびにマニフェストを更新する必要がなくなりました。 アクティビティとは異なり、フラグメントを宣言する必要はありません。

これは些細なことのように思えるかもしれませんが、50以上のアクティビティを持つ大規模なアプリケーションの場合、これによりAndroidManifest.xmlファイルの読みやすさが大幅に向上します。

いくつかの画面があるサンプルアプリケーションのマニフェストファイルを見てください。 マニフェストファイルは依然として非常に単純なままです。

 <?xml version="1.0" encoding="utf-8"?> package="com.exarlabs.android.fragmentnavigationdemo.ui" > <application android:name= ".FragmentNavigationDemoApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name="com.exarlabs.android.fragmentnavigationdemo.ui.MainActivity" android:label="@string/app_name" android:screenOrientation="portrait" android:theme="@style/AppTheme.NoActionBar" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

2.一元化されたナビゲーション管理

私のコード例では、 NavigationManagerを使用していることがわかります。私の場合、NavigationManagerはすべてのフラグメントに挿入されます。 このマネージャーは、ロギングやバックスタック管理などの集中管理の場所として使用できるため、ナビゲーション動作は他のビジネスロジックから切り離され、さまざまな画面の実装に分散することはありません。

ユーザーが人のリストからいくつかの項目を選択できる画面を開始したい状況を想像してみましょう。 また、年齢、職業、性別などのフィルタリング引数を渡したいと思います。

アクティビティの場合、次のように記述します。

 Intent intent = new Intent(); intent.putExtra("age", 40); intent.putExtra("occupation", "developer"); intent.putExtra("gender", "female"); startActivityForResult(intent, 100);

次に、以下のどこかでonActivityResultを定義し、結果を処理する必要があります。

 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); }

このアプローチに関する私の個人的な問題は、これらの引数が「エクストラ」であり、必須ではないことです。したがって、エクストラが欠落している場合、受信アクティビティがさまざまなケースをすべて処理することを確認する必要があります。 後でリファクタリングが行われ、たとえば「年齢」の追加が不要になった場合、このアクティビティを開始するコード内のすべての場所を検索し、すべての追加が正しいことを確認する必要があります。

さらに、結果(人のリスト)が_Listの形式で到着するのは良いことではないでしょうか_逆シリアル化する必要があるシリアル化された形式ではありませんか?

フラグメントベースのナビゲーションの場合、すべてがより簡単です。 あなたがしなければならないのは、必要な引数とコールバックの実装を使って、 startPersonSelectorFragment()と呼ばれるNavigationManagerのメソッドを書くことです。

 mNavigationManager.startPersonSelectorFragment(40, "developer", "female", new PersonSelectorFragment.OnPersonSelectedListener() { @Override public boolean onPersonsSelected(List<Person> selection) { [do something] return false; } });

またはRetroLambdaを使用

mNavigationManager.startPersonSelectorFragment(40, "developer", "female", selection -> [do something]);

3.画面間のより良いコミュニケーション手段

アクティビティ間で共有できるのは、プリミティブまたはシリアル化されたデータを保持できるバンドルのみです。 これでフラグメントを使用して、たとえばF1が任意のオブジェクトを渡すF2をリッスンできるコールバックパターンを実装できます。 _Listを返す前の例のコールバック実装を見てください_。

4.フラグメントの構築は、アクティビティの構築よりも安価です

これは、たとえば5つのメニュー項目があるドロワーを使用する場合に明らかになり、各ページにドロワーが再度表示されます。

純粋なアクティビティナビゲーションの場合、各ページはドロワーを膨らませて初期化する必要がありますが、これはもちろん高価です。

下の図では、ドロワーから直接アクセスできるフルスクリーンフラグメントであるいくつかのルートフラグメント(FR *)を確認できます。また、ドロワーには、これらのフラグメントが表示されている場合にのみアクセスできます。 図の破線の右側にあるものはすべて、任意のナビゲーションスキームの例としてあります。

Framgnet画像4

コンテナアクティビティはドロワーを保持するため、ドロワーインスタンスは1つしかないため、ドロワーが表示されるすべてのナビゲーションステップで、ドロワーを再度膨らませて初期化する必要はありません。 これらすべてがどのように機能するかまだ確信が持てませんか? ドロワーの使用法を示すサンプルアプリケーションを見てください。

短所

私の最大の恐怖は、プロジェクトでフラグメントベースのナビゲーションパターンを使用した場合、将来のどこかで、フラグメント、サードパーティライブラリ、およびさまざまなOSバージョンの追加された複雑さの周りで解決するのが難しい予期しない問題に遭遇することでした。 これまでに行ったすべてのことをリファクタリングする必要がある場合はどうなりますか?

実際、ネストされたフラグメント、ShinobiControls、ViewPagers、FragmentStatePagerAdaptersなどのフラグメントも使用するサードパーティライブラリの問題を解決する必要がありました。

これらの問題を解決するためにフラグメントについて十分な経験を積むことは、かなり長いプロセスであったことを認めなければなりません。 しかし、いずれの場合も、問題は哲学が悪いということではなく、断片を十分に理解していなかったということでした。 たぶん、私よりも断片をよく理解していれば、これらの問題に遭遇することさえないでしょう。

私が今言及できる唯一の欠点は、フラグメントベースのナビゲーションを備えた複雑なアプリケーションのすべての複雑なシナリオを紹介する成熟したライブラリがないため、解決するのが簡単ではない問題にまだ遭遇する可能性があるということです。

結論

この記事では、Androidアプリケーションにナビゲーションを実装する別の方法を見てきました。 アクティビティを使用する従来のナビゲーション哲学と比較し、従来のアプローチよりも使用することが有利である理由をいくつか見てきました。

まだ行っていない場合は、GitHub実装にアップロードされたデモアプリケーションを確認してください。 自由にフォークするか、その使用法をよりよく示すより良い例でそれに貢献してください。

関連: Android開発者が犯す最も一般的な間違いトップ10