iOSエンジニアのつぶやき

毎朝8:30に iOS 関連の技術について1つぶやいています。まれに釣りについてつぶやく可能性があります。

Carthage Error: fatal: unable to open "": No such file or directory 解消法

ライブラリを Check out と使用とコマンドを実行すると、下記のような Error が、、

$ carthage bootstrap

A shell task (/usr/bin/env git clone --quiet "") failed with exit code 128:
fatal: unable to open "": No such file or directory 
fatal: index-pack failed

https://github.com/Carthage/Carthage/issues/407#issuecomment-89079614 にもある通り、どうやら Carthage のキャッシュが悪さをしているようです。

解決法

下記コマンドで Carthage リポジトリのキャッシュを削除することで、うまく実行できるようになりました🤧

$ rm -rf ~/Library/Caches/org.carthage.CarthageKit

参考

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

iOS アプリのライフサイクルを理解しよう

今回は iOS アプリがどのようなライフサイクルで動いているのかを簡単にまとめて行きたいと思います。なんとなくボヤッとした感じでライフサイクルを認識していた人はぜひ目を通してみてください🙃

アプリの状態

状態 内容
Not running アプリは開始されていません
Inactive アプリケーションはフォアグラウンドで実行されていますが、イベントは受信しません。電話やメッセージを受け取った時や Active State から別の状態に切り替わる時に少しの間この状態になります。UI の操作は行うことができません。
Active アプリケーションはフォアグラウンドで実行されていて、イベントの受信もしています。Inactive を経由することで状態の切り替えが可能です。
Background アプリケーションはバックグラウンドにあり、コードは実行されます。通常、アプリを開いて、ホームボタンがタップされた時などに5秒ほどこの状態になり、Suspended 状態に移動します。また、5秒以上かかるタスクを行いたい場合時間を Backgroud 状態の時間を延長させることも可能です(ただ、10分を越すような長いタスクの場合は強制的に Suspended になる)
Suspended アプリケーションはバックグランドにあり、コードは実行されません。システムは自動的にアプリをこの状態に移行します(この移行に関する通知はありません)。システムのメモリが不足している場合、この状態のアプリケーションはメモリから削除され、アプリが Not running 状態になる可能性もあります。

※ Project の CapabilityBackground Modes を設定することで、例外的にバックグランドで長時間タスクを実行することも可能です。(ex. YouTube, Maps)

ライフサイクル

通常起動時

Not running -> Inactive -> Active

アプリ起動時にホームボタンをタップ

Active -> Inactive -> Background -> Suspended

サスペンド状態から起動された時(メモリ内にアプリがある場合)

Suspended -> Background -> Inactive -> Active

システムによってメモリから削除された場合(Suspended時)

Suspended -> Not running

Delegate Methods

delegate 内容
application(_:willFinishLaunchingWithOptions) 起動プロセスが開始されたことを通知します。
application(_:didFinishLaunchingWithOptions:) 起動プロセスがほぼ完了し、アプリを実行する準備がほぼ整ったことを通知します。
applicationDidBecomeActive(_application:) アプリが Active 状態になったことを通知します。
applicationWillResignActive(_application:) アプリが Active 状態から Inactive に移行しようとしていることを通知します。
applicationDidEnterBackground(_application:) アプリが Background 状態になったことを通知します(Inactive -> Background 通知)。このメソッドが呼ばれてから約5秒後に Suspended 状態になります。追加の時間が必要な場合は beginBackgroundTask(expirationHandler:) を呼び出してシステムに追加の時間を要求します。タスクの実行が長すぎる場合は強制的に Suspended 状態に移行します。
applicationWillTerminate(_ application: UIApplication) アプリが終了され、メモリから完全に削除されることを通知します。タスクの実行時間が長すぎる場合は、プロセスは強制的に終了させられます。

通常起動時

  1. application(_:willFinishLaunchingWithOptions)-(Inactive)

  2. application(_:didFinishLaunchingWithOptions:)-(Inactive)

  3. applicationDidBecomeActive(_application:)-(Active)

アプリ起動時にホームボタンをタップ

  1. applicationWillResignActive(_application:)-(Active)

  2. applicationDidEnterBackground(_application:)-(Background)

...アプリ終了して、メモリから完全に削除される直前

  1. applicationWillTerminate(_application:)-(Background)

サスペンド状態から起動された時(メモリ内にアプリがある場合)

  1. applicationWillEnterForeground(_application:)-(Background)

  2. applicationDidBecomeActive(_application:)-(Active)

参考

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

Xcode Error: Unable to install "" 解消法

Xcode12 に対応するためにちょこちょことマイナーアップデートをしていた時に発生したエラーについて共有します。この Error は調べたところ色々なケースで発生しているので一概には言えませんが、Carthage のアップデート周りで発生した場合はこの解決方法で治るかと思われます。

解決法

開発用ビルドでも Framework をコピーできるように、Xcode の Carthage Runscript を下記のようにします。(元々下記のようになっていた場合は、別の解決方法を見つけてください)

/usr/local/bin/carthage copy-frameworks

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

知識ゼロからの Kotlin Android アプリリリースへの軌跡 / Day12【Start an external Activity編】

学ぶこと

  • Bundle クラスを使用して、ある Fragment から別の Fragmen に引数を渡す方法
  • タイプセーフのために Safe Args Gradle プラグインを使用する方法
  • Share menu item をアプリに追加する方法
  • implicit intent とは何か、それを作成する方法

すること

  • NavDirection クラスを生成する Safe Args プラグインを使用するように AndroidTrivia コードを変更します。

  • 生成された NavDirection クラスの1つを使用して、Game Fragment と game-state Fragment 間でタイプセーフな引数を渡します。

  • "Share" menu item をアプリに追加します。

  • ユーザがゲームの結果に関するメッセージを共有するために使用できるセレクターを起動する implicit intent を作成します。

アプリ概要

前の2つのコードラボで取り組んだ AndroidTrivia アプリは、ユーザーが Android 開発に関する質問に答えるゲームです。ユーザが3つの質問に正しく答えると、ゲームに勝ちます。

このコードラボでは、AndroidTrivia アプリを変更して、ユーザがゲームの結果を他のアプリに送信し、その結果を友達と共有できるようにします。

Safe Args プラグインをセットアップして使用する

ユーザが AndroidTrivia アプリ内からゲームの結果を共有する前に、コードは1つのフラグメントから別のフラグメントにパラメータを渡す必要があります。これらのトランザクションのバグを防ぎ、タイプセーフにするために、Safe Args と呼ばれる Gradle プラグインを使用します。プラグインNavDirection クラスを生成し、これらのクラスをコードに追加します。

このコードラボの後半のタスクでは、生成された NavDirection クラスを使用して、Fragment 間で引数を渡します。

Safe Args プラグインが必要な理由

多くの場合、アプリはフラグメント間でデータを渡す必要があります。あるフラグメントから別のフラグメントにデータを渡す1つの方法は、[Bundle])(https://developer.android.com/reference/android/os/Bundle.html) クラスのインスタンスを使用することです。Android Bundle は key-value ストアです。

key-value ストアは、ディクショナリまたは連想配列とも呼ばれ、一意のキー(文字列)を使用して、そのキーに関連付けられた値をフェッチするデータ構造です。Ex:

Key Value
"name" "Anika"
"favorite_weather" "sunny"
"favorite_color" "blue"

アプリは、Bundle を使用して FrgmentA から FragmentB にデータを渡すことができます。例えば、FragmentA は Bundle を作成し、情報を key-value として保存してから、Bundle を FragmentB に渡します。次に、FragmentB はキーを使用して、Bundle から key-value のペアをフェッチします。この手法は機能しますが、コードがコンパイルされ、アプリの実行時にエラーが発生する可能性があります。

発生する可能性のあるエラーの種類は次の通りです:

  • Type mismatch errors。例えば、FragmentA が文字列を送信し、FragmentB がバンドルから整数を要求した場合、要求はデフォルト値のゼロを返します。ゼロは有効な値なので、この種のタイプの不一致の問題は、アプリのコンパイル時にエラーをスローしません。ただし、ユーザがアプリを実行すると、エラーが原因でアプリが誤動作したりクラッシュしたりする可能性があります。

  • Missing key errors。FragmentB がバンドルに設定されていない引数を要求した場合、操作は null を返します。これはアプリのコンパイル時エラーをスローしませんが、ユーザがアプリを実行した時に深刻な問題を引き起こす可能性があります。

Android Studio でアプリをコンパイルする時にこれらのエラーをキャッチして、アプリを本番環境にデプロイする前にこれらのエラーをキャッチする必要があります。言い換えると、ユーザがそれらに遭遇しないように、アプリ開発中にエラーをキャッチしたいです。

これらの問題を解決するために、Android の Navigation Architecture Component には Safe Args と呼ばれる機能が含まれています。Safe Args は、コードとクラスを生成する Gradle プラグインで、コンパイル時にアプリが実行されるまで表示されないエラーを検出するのに役立ちます。

このコードラボでは、Congratulations 画面の上部に共有アイコンを追加します。Share icon を使用すると、ユーザは結果をメールまたはテキストで共有できます。

プロジェクトに Safe Args を追加する

  1. Android Studio で、プロジェクトレベルの build.gradle ファイルを開きます。

  2. 次に示すように、navigation-safe-args-gradle-plugin 依存関係を追加します。

// Adding the safe-args dependency to the project Gradle file
dependencies {
   ...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"

}
  1. アプリレベルの build.gradle ファイルを開きます。

  2. ファイルの先頭で、他の全てのプラグインの後に、androidx.navigation.safeargs プラグインを含む apply plugin ステートメントを追加します。

// Adding the apply plugin statement for safeargs
apply plugin: 'androidx.navigation.safeargs'
  1. プロジェクトを Re-build します。追加のビルドツールのインストールをするように求められたら、それらをインストールします。

プリプロジェクトに、生成された NavDirection クラスが含まれるようになりました。

Safe Args プラグインは、Fragment ごとに NavDirection クラスを生成します。クラスは、全てのアクションからの navigation を表します。

たとえば、GameFragment には GameFragmentDirections クラスが生成されています。GameFragmentDirections クラスを使用して、Game Fragment とアプリ内の他のフラグメントとの間でタイプセーフな引数を渡すことができます。

生成されたファイルを確認するには、generatedJava フォルダーを探します。

注意: NavDirection クラスは編集しないでください。これらのクラスは、プロジェクトがコンパイルされるたびに再生成され、編集内容は失われます。

NavDirection クラスを Game Fragment に追加する

このステップでは、Game Fragment に GameFragmentDirections クラスを追加します。後でこのコードを使用して、GameFragment と game-state Fragment の間で引数を渡します。

  1. java フォルダーにある GameFragment.kt ファイルを開きます。

  2. onCreateView() メソッド内で、geme-won conditional ステートメントを見つけます。NavController.navigate() メソッドに渡されるパラメータを変更します。game-won state の アクション ID を、GameFragmentDirections クラスの actionGameFragmentToGameWinFragmen() メソッドを使用する ID に置き換えます。

条件ステートメントは次のコードのようになります。次のタスクでは、actionGameFragmentToGameWonFragment() メソッドにパラメータを追加します。

// Using directions to navigate to the GameWonFragment
view.findNavController()
        .navigate(GameFragmentDirections.actionGameFragmentToGameWonFragment())
  1. 同様に、game-over statement も下記のように変更します。
// Using directions to navigate to the GameOverFragment
view.findNavController()
        .navigate(GameFragmentDirections.actionGameFragmentToGameOverFragment())

引数を追加して渡す

このタスクでは、型付き引数を gameWonFragment に追加し、引数を GameFragmentDirections メソッドに渡します。次に、他の Fragment クラスを同等の NavDirection クラスに置き換えます。

game-won Fragment に引数を追加します

  1. navigation.xml を開きます。navigation graph を開いて、fragment の引数を設定します。

  2. プレビューで gameWonFragment を選択します。

  3. Attributes ペインで、Arguments セクションを開きます。

  4. +icon をクリックして引数を追加します。numQuestions と名前を付け、タイプを Integer に設定して Add をクリックします。この引数は、ユーザが回答した質問の数を表します。

  1. 2つ目の引数を追加します。numCorrect と名前を付け、型を Integer に設定します。この引数は、ユーザが正しく回答した質問の数を表します。

アプリをビルドしようとすると、2つのコンパイルエラーが発生する可能性があります。

No value passed for parameter 'numQuestions'
No value passed for parameter 'numCorrect'

次のステップでこのエラーを修正します。

引数を渡す

このこのステップでは、numQuestions および questionIndex 引数を、GameFragmentDirections クラスから actionGameFragmentToGameWonFragment() メソッドに渡します。

  1. GameFragment.kt Kotlin ファイルを開き、ゲームに勝利した時のステートメントを見つけます。
else {
 // We've won!  Navigate to the gameWonFragment.
 view.findNavController()
      .navigate(GameFragmentDirections
            .actionGameFragmentToGameWonFragment())
}
  1. numQuestions および questionIndex パラメータを actionGameFragmentToGameWonFragment() メソッドに渡します。
// Adding the parameters to the Action
view.findNavController()
      .navigate(GameFragmentDirections
            .actionGameFragmentToGameWonFragment(numQuestions, questionIndex))

質問の総数を numQuestions として渡し、現在試行されている質問を questionIndex として渡します。アプリは、ユーザが全ての質問に正しく回答した場合にのみデータを共有できるように設計します。正しく回答した数は、常に回答された質問の数と同じです。(必要に応じてゲームロジックを変更します。)

  1. GameFragment.kt で、バンドルから引数を取り出し、Toast を使用して引数を表示します。return ステートメントの前の onCreateView() メソッドに次のコードを挿入します。
val args = GameWonFragmentArgs.fromBundle(requireArguments())
Toast.makeText(context, "NumCorrect: ${args.numCorrect}, NumQuestions: ${args.numQuestions}", Toast.LENGTH_LONG).show()
  1. アプリを実行して確認します。

Fragment クラスを NavDirection クラスに置き換える

"safe argument" を使用すると、navigation code で使用される Fragment class を NavDirection クラスに置き換えることができます。これを行うと、アプリ内のフラグメントでタイプセーフな引数を使用できます。

TitleGragmentGameOverFragment および GameWonFragmentnavigate() メソッドに渡される action ID を変更します。action ID を NavDirection クラスの同等のメソッドに置き換えます。

  1. TitleFragment.kt ファイルを開きます。onCreateView() で、Play ボタンのクリックハンドラー navigate() メソッドを見つけます。TitleFragmentDirections.actionTitleFragmentToGameFragment() をメソッドの引数として渡します。
binding.playButton.setOnClickListener { view: View ->
    view.findNavController()
            .navigate(TitleFragmentDirections.actionTitleFragmentToGameFragment())
}
  1. GameOverFragment.kt ファイルで Try Again ボタンのクリックハンドラーで、navigate() メソッドの引数として、GameOverFragmentDirections.actionGameOverFragmentToGameFragment() を渡します。
binding.tryAgainButton.setOnClickListener { view: View ->
    view.findNavController()
        .navigate(GameOverFragmentDirections.actionGameOverFragmentToGameFragment())
}
  1. GameWonFragment.kt ファイルの Next Match も下記のように変更します。
binding.nextMatchButton.setOnClickListener { view: View ->
    view.findNavController()
          .navigate(GameWonFragmentDirections.actionGameWonFragmentToGameFragment())
}
  1. アプリを実行

Implicit intent と "share" menu item

Implicit Intens

Navigation Components を使用して、Activity 内の Fragment 間を移動してきました。Android では、インテントを使用して、他のアプリが提供する Activity に移動するこもできます。AndroidTrivia アプリでこの機能を使用して、ユーザがゲームプレイの結果を共有できるようにします。

Intent は、Android Component 間の通信に使用される単純なメッセージオブジェクトです。Implicit intets を使用すると、タスクを処理するアプリまたは Activity を知らなくても Activity を開始できます。例えば、アプリで写真をとる場合は、どのアプリまたは Activty がタスクを実行するかは気にしません。複数の Android アプリが同じ implicit intents を処理できる場合、Android はユーザにセレクターを表示し、ユーザがアプリを選択してリクエストを処理できるようにします。

implicit intent は、実行する処理のタイプを説明する ACTION が必要です。ACTION_VIEW,ACTION_EDIT,ACTION_DIAL などの一般的なアクションは、Intent クラスで定義されています。

Implicit intents の詳細についてはこちら

Congratulations 画面にオプションメニューを追加する

  1. GameWonFragment.kt ファイルを開きます。

  2. onCreateView() メソッド内で、return 前に、setHasOptionsMenu() メソッドを呼び出して true にします。

  setHasOptionsMenu(true)

Implicit intent を構築して呼び出す

ユーザのゲーム結果のメッセージを送信するインテントを作成して呼び出せるようにコードを変更します。いくつかのアプリが ACTION_SEND インテントを処理できるため、ユーザには、情報の送信方法を選択できるセレクターが表示されます。

  1. GameWonFragment クラス内で、onCreateView() メソッドの後に、以下に示すように getShareIntent() というプライベートメソッドを作成します。args の値を設定するコード行は、クラスの onCreateView() で使用されるコード行と同じです。
// Creating our Share Intent
private fun getShareIntent() : Intent {
   val args = GameWonFragmentArgs.fromBundle(requireArguments())
   val shareIntent = Intent(Intent.ACTION_SEND)
   shareIntent.setType("text/plain")
            .putExtra(Intent.EXTRA_TEXT, getString(R.string.share_success_text, args.numCorrect, args.numQuestions))
   return shareIntent
}
  1. getShareIntent() メソッドの下に、shareSuccess() メソッドを作成します。このメソッドは、getShareIntent() からインテントを取得し、starActivity() を呼び出して sahre を開きます。
// Starting an Activity with our new Intent
private fun shareSuccess() {
   startActivity(getShareIntent())
}
  1. onCreateOptionsMenu() をオーバーライドして、winner_menu を拡張します。
// Showing the Share Menu Item Dynamically
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
       super.onCreateOptionsMenu(menu, inflater)
       inflater.inflate(R.menu.winner_menu, menu)
       if(getShareIntent().resolveActivity(requireActivity().packageManager)==null){
            menu.findItem(R.id.share).isVisible = false
       }
}
  1. menu item を処理するには、onOptionsItemSelected() をオーバーライドします。menu item がクリックされた時に、shareSuccess を呼び出します。
// Sharing from the Menu
override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when(item.itemId){
            R.id.share -> shareSuccess()
        }
        return super.onOptionsItemSelected(item)
}
  1. アプリを実行します。

まとめ

Safe Args: - あるフラグメントから別のフラグメントにデータを渡す時に、キーの欠落や方の不一致によって発生するエラーをキャッチするには、Safe Args と呼ばれる Gradle プラグインを使用します。

  • アプリ内のフラグメントごとに、Safe Args プラグインは対応する NavDirection クラスを生成します。NavDirection クラスを Fragment コードに追加し、そのクラスを使用して、Fragmentと他のフラグメント間で引数を渡します。

  • NavDirection クラスは、全てのアプリのアクションからnavigationを表します。

implicit intents: - implicit intent は、アプリが他のアプリに代わって実行するアクションを宣言します。

sharing functionality: - 成功を友達と共有する場合、Intent アクションは Intent.ACTION_SEND になります。 - オプションメニューをフラグメントに追加するには、Fragment コードで setHasOptionsMenu メソッドを true にします。

  • フラグメントコードで、onCreateOptionsMenu() メソッドをオーバーライドしてメニューを inflate します。

  • onOptionsItemSelected() をオーバーライドして startActivity() を使用し、それを処理できる他のアプリにインテントを送信します。

ユーザーがメニュー項目をタップすると、インテントが発生し、SEND アクションのセレクターが表示されます。

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

知識ゼロからの Kotlin Android アプリリリースへの軌跡 / Day11【Define navigation paths編】

学ぶこと

  • navigation graphs の使用方法
  • アプリで navigation path を定義する方法
  • Up button とは何か、および button を追加する方法
  • オプションメニューの作成方法
  • navigation drawer を作成する方法

すること

  • navigation library と Navigation Editor を使用して、fragment の navigation graph を作成します。
  • アプリに navigation paths を作成する。
  • オプションメニューを使用して navigation を追加します。
  • ユーザーがアプリ内のどこからでもタイトル画面に戻ることができるように、Up button を実装します。
  • navigation drawer メニューを追加します。

アプリ概要

このコードラボでは、AndroidTrivia アプリを次の方法で更新します。

  • アプリの navigation graph を作成します。
  • タイトル画面とゲーム画面の navigation を追加します。
  • 画面をアクションに接続し、Play をタップしてゲーム画面に移動する方法をユーザに提供します。
  • 一部の画面の上部に左矢印として表示される Up button を追加します。

プロジェクトに Navigation components を追加する

Navigation の依存関係を追加する

navigation library を使用するには、Gradle ファイルに navigation の依存関係を追加する必要があります。

  1. Android Studio で AndroidTrivia アプリを開きます。

  2. Gradle Scripts フォルダーを開きます。プロジェクトレベルの build.gradle ファイルをダブルクリックしてファイルを開きます。

  1. プロジェクトレベルの build.gradle ファイルの上部に、他の ext 変数と共に、navigationVersion の変数を追加します。最新の Navigation version number を確認するには、Android 開発者向け Declaring dependencies をご覧ください。

  2. Gradle Scripts フォルダーで、モジュールレベルの build.gradle ファイルを開きます。次に示すように、navigation-fragment-ktxnavigation-ui-ktx の依存関係を追加します。

  3. Peoject を rebuild します。

プロジェクトに navigation graph を追加する

  1. res フォルダーで、New > Android Resource File を選択します。

  2. New Resource File ダイアログで、Resource type として Navigation を選択します。

  3. File name フィールドで、navigation と名付けます。

  4. Chosen qualifiers ボックスが空であることを確認して、OK をクリックします。新しいファイル navigation.xmlres > navigation フォルダーに表示されます。

  1. res > navigation > navigation.xml ファイルを開き、Design タブをクリックして Navigation Editor を開きます。レイアウトエディターに No NavHostFragments found というメッセージが表示されることに注意してください。この問題は次のタスクで修正します。

NavHostFragment を作成する

navigation host Fragment は、Navigation graph の fragment host として機能します。navigation host の fragment は通常、NavHostFragment という名前です。

ユーザーが navigation graph で定義された目的地間を移動すると、navigation host の Fragment は必要に応じて Fragment を入れ替えます。Fragment は、適切な Fragment バックスタックも作成および管理します。

このタスクでは、コードを変更して TitleFragmentNavHostFragment に置き換えます。

  1. res > layout > activity_main.xml を開き、Code タブを開きます。

  2. activity_main.xml ファイルで、既存のタイトル Fragment の名前を androidx.navigation.fragment.NavHostFragment に変更します。

  3. id を myNavHostFragment に変更します。

  4. Navigation host Fragment は、使用する navigation graph リソースを認識する必要があります。app:navGraph attribute を追加して、@navigation/navigation に設定します。

  5. app: defaultNavHost attribute を追加して、"true" に設定します。これで、この navigation がデフォルトの host になり、システムの Back button をインターセプトします。

activity_main.xml レイアウトファイル内で、fragment は次のようになります。

<!-- The NavHostFragment within the activity_main layout -->
            <fragment
                android:id="@+id/myNavHostFragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:navGraph="@navigation/navigation"
                app:defaultNavHost="true" />

Navigation Graph に Fragment を追加する

このタスクでは、タイトルの Fragment とゲームの Fragment をアプリの Navigatio Graph に追加します。Fragment を相互に接続します。次に、クリックハンドラーを Play ボタンに追加して、ユーザがタイトル画面からゲーム画面に移動できるようにします。

Navigation Graph に2つの Fragment を追加し、それらをアクションに接続します

  1. navigation リソースフォルダーから navigation.xml を開きます。Navigation Editornew Destication ボタンをクリックします。Fragment と Activities のリストが表示されます。

  1. fragment_title を選択します。TitleFragment Fragment はアプリユーザがアプリを最初に開いた時に起動する場所であるため、最初に fragment_title を追加します。

  1. New Destination ボタンを使用して、GameFragment を追加します。

preview に "Preview Unavailable" というメッセージが表示された場合は、Code タブをクリックして navigation XML を開きます。次に示すように、gameFragmentfragment 要素に tools:layout="@layout/fragment_game" が含まれていることを確認してください。

<!-- The game fragment within the navigation XML, complete with tools:lay
out. -->
<fragment
   android:id="@+id/gameFragment"
   android:name="com.example.android.navigation.GameFragment"
   android:label="GameFragment"
   tools:layout="@layout/fragment_game" />
  1. Layout Editor で、Game Fragment を右にドラッグして、Title Fragment と重ならないようにします。

  1. preview で Title Fragment にポインターを合わせます。Fragment View の右側に円形の接続ポイントが表示されます。接続ポイントをクリックして、Game Fragment preview にドラッグします。2つの Fragment を接続する Action が作成されます。

  2. Actions's attribute を表示するには、2つの Fragment を結ぶ線をクリックします。Attributes ペインで、action's ID が action_titleFragment_to_gameFragment に設定されていることを確認します。

Play Button にクリックハンドラーを追加する

Title Fragment は、action によって Game Fragment に接続されます。次に、タイトル画面の Play ボタンでユーザをゲーム画面に移動させます。

  1. Android StudioTitleFragment.kt ファイルを開きます。onCreateView() メソッド内で、return ステートメントの前に次のコードを追加します。
binding.playButton.setOnClickListener{}
  1. setOnClickListener 内に、binding class を通じて Play button にアクセスするコードを追加し、Game Fragment に移動します。
//The complete onClickListener with Navigation
binding.playButton.setOnClickListener { view : View -> 
       view.findNavController().navigate(R.id.action_titleFragment_to_gameFragment)
}
  1. アプリをビルドし、必要なインポートが全て含まれていることを確認します。たとえば、次の行を TitleFragment.kt ファイルに追加する必要がある場合があります。
import androidx.navigation.findNavController
  1. アプリを実行し、タイトル画面の Play button をタップします。ゲーム画面が開きます。

Conditional Navigation を追加する

このステップでは、conditional Navigation(条件付き Navigation) を追加します。これは、特定のコンテキストでのみユーザが使用できる Navigation です。conditional navigation の一般的な使用例は、ユーザがログインしているかどうかに応じて、アプリのフローが異なる場合です。

アプリは別のケースです: アプリは、ユーザが全ての質問に正しく答えたかどうかに基づいて、別のフラグメントに移動します。

スターターコードには、conditional navigation で使用する2つの fragment が含まれています:

  • GameWonFragment は、ユーザに "Congratulations!" メッセージを示す画面を表示します。
  • GameOverFragment は、ユーザに "Try Again" メッセージを表示する画面を表示します。

Navigation graph に GameWonFragmentGameOverFragment を追加する

  1. navigation フォルダーにある navigation.xml ファイルを開きます。

  2. game-over Fragment を navigation graph に追加するには、Navigation Editor で New Destination ボタンをクリックします、fragment_game_over を選択します。

  1. レイアウトエディターの Preview 領域で、game-over Fragment を game Fragment の右側にドラッグして、2つがオーバーラップしないようにします。必ず game-over Fragment の ID attribute を gameOverFragmen に変更してください。

  2. game-won Fragment を navigation graph に追加するには、New Destination ボタンをクリックして、fragment_game_won を選択します。

  1. game-won Fragment を game-over Fragment の下にドラッグして、2つが重ならないようにします。game-won Fragment の ID attribute の名前を gameWonFragment にしてください.

Layout Editor は次のスクリーンショットのようになります:

ゲームの Fragment をゲーム結果の Fragment に接続する

このステップでは、game Fragment を、game-won Fragment と game-over Fragment の両方に接続します。

  1. Layout Editor のプレビュー領域で、円型の connection point が表示されるまで、ポインターを game Fragment の上におきます。

  2. connection point をクリックして、game-over Fragment にドラッグします。青い接続線が表示され、game Fragment を game-over Fragment に接続するアクションを表します。

  3. 同様に、game Fragment を game-won Fragment に接続するアクションを作成します。Layout Editor は次のスクリーンショットのようになります。

  1. Preview で、game Fragment と game-won Fragment を結ぶ線の上にポインタを置きます。アクションの ID が自動的に割り当てられていることに注意してください。

Fragment 間を移動するコードを追加する

GameFragment は、ゲームの質問と回答を含む Fragment クラスです。このクラスには、ユーザがゲームに勝利するか敗北するかを決定するロジックも含まれています。プレーヤーの勝敗に応じて、GameFragment クラスに conditional navigation を追加する必要があります。

  1. GameFragment.kt ファイルを開きます。onCreateView() メソッドは、プレーヤーが勝ったか負けたかを決定する if / else 条件を定義します。
binding.submitButton.setOnClickListener @Suppress("UNUSED_ANONYMOUS_PARAMETER")
        { 
              ...
                // answer matches, we have the correct answer.
                if (answers[answerIndex] == currentQuestion.answers[0]) {
                    questionIndex++
                    // Advance to the next question
                    if (questionIndex < numQuestions) {
                        currentQuestion = questions[questionIndex]
                        setQuestion()
                        binding.invalidateAll()
                    } else {
                        // We've won!  Navigate to the gameWonFragment.
                    }
                } else {
                    // Game over! A wrong answer sends us to the gameOverFragment.
                }
            }
        }
  1. ゲームに勝利した時の else 条件内に、gameWonFragment に移動する次のコードを追加します。アクション名(action_gameFragment_to_gameWonFragment)が、navigation.xml ファイルを設定されているものと正確に一致していることを確認してください。
// We've won!  Navigate to the gameWonFragment.
view.findNavController()
   .navigate(R.id.action_gameFragment_to_gameWonFragment)
  1. ゲームに敗北した時の else 条件内に、gameOverFragment に移動する次のコードを追加します。
// Game over! A wrong answer sends us to the gameOverFragment.
view.findNavController().
   navigate(R.id.action_gameFragment_to_gameOverFragment)
  1. アプリを実行し、質問に答えてゲームをプレイします。3つの質問全て正しく答えると、アプリは GameWonFragment に移動します。

答えが間違っている場合、アプリはすぐに GameOverFragment に移動します。

Android システムの Back button は、上のスクリーンショットでは ① と表示されています。ユーザが game-won Fragment または game-over Fragment の BackButton を押すと、アプリは、質問画面に移動します。理想的には、BackButton はアプリのタイトル画面に戻る必要があります。次のタスクで Back Button の遷移先を変更します。

戻るボタンの遷移先を変更する

Android システムは、ユーザが Android 搭載デバイスでナビゲートした場所を追跡します。ユーザがデバイスの新しい宛先に移動するたびに、Android はその宛先を back stack に追加します。

ユーザが Back button を押すと、アプリは back stack の斎場にある宛先に移動します。デフォルトでは、back stack の最上位は、ユーザが最後に表示した画面です。以下に示すように、Back button は通常、画面下部の左端のボタンです。(Back button の正確な外観は、デバイスによって異なります。)

これまでは、Navigation Controller にバックスタックを処理させていました。ユーザがアプリの目的地に移動すると、 Android はこの目的地をバックスタックに追加します。

AndroidTrivia アプリでは、ユーザが GameOverFragment または GameWonFragment 画面から Back button を押すと、GameFragment に戻ります。しかし、ゲームが終わったので、ユーザを GameFragment に送りたくありません。ユーザはゲームを再開できますが、より良い体験は、タイトル画面に戻ることです。

Navigation アクションは、back stack を変更できます。このタスクでは、GameFragment から移動するアクションを変更して、アクションが back stack から GameFragment を削除するようにします。ユーザがゲームに勝った時、負けた時に Back Button を押すと、アプリは GameFragment をスキップして TitleFragment に戻ります。

navigation action のポップ動作を設定する

このステップでは、ユーザが GameWonGameOver 画面にいる時に Back Button を押すとタイトル画面に戻るように、back stack を管理します。Fragment を接続するアクションの "pop" 動作を設定して、back stack を管理します:

  • アクションの popUpTo 属性は、navigate する前に、指定された宛先に back stack をポップアップします。(宛先は back stack から削除されます。)

  • popUpToInclusive attribute が false または設定されていない場合、popUpTp は指定された宛先まで宛先を削除しますが、指定された宛先を back stack に残します。

  • popUpToInclusivetrue に設定されている場合、popUpTo 属性は、指定された宛先までの全ての宛先を back stack から削除します。

  • popUpToInclusivetrue で、popUpTo がアプリの開始場所に設定されている場合、アクションは全てのアプリの宛先をバックスタックから削除します。Back Button を押すと、ユーザはアプリから離れます。

このステップでは、前のタスクで作成した2つのアクションの popUpTo 属性を設定します。これを行うには、Layout Editor の Attributes ペインの Pop To フィールドを使用します。

  1. res > navigation フォルダにある navigation.xml を開きます。navigation graph がレイアウトエディターに表示されない場合は、Design タブに切り替えます。

  2. gameFragment から gameOverFragment に移動するアクションを選択します。(プレビュー領域では、アクションは2つの Fragment を結ぶ青い線で表されます。)

  3. Attributes ペインで、Pop TogameFragment に設定します。inclusive チェックボックスを選択します。

これにより、popUpTo および popUpTpInclusive attribute が XML に設定されます。attributes は、Navigation Component に、back stack から GameFragment までの Fragment を削除するように指示します。(これは、Pop To フィールドを titleFragment に設定し、Inclusive チェックボックスをオフにするのと同じ効果があります。)

  1. gameFragment から gameWonFragment に移動するアクションを選択します。再度 Attributes ペインで Pop TogameFragment に設定し、Inclusive チャックボックスを選択します。

  1. アプリを実行してゲームをプレイし、Back button を押します。勝っても負けても Back button をクリックすると TitleFragment に戻ります。

Navigation Action を追加し、onClick ハンドラーを追加します

現在、アプリには次のユーザーフローがあります:

  • ユーザがゲームをプレイして勝ちまたは負け、アプリが GameWonGameOver 画面に移動します。
  • この時点でユーザがシステムの Back button を押すと、アプリは TitleFragment に移動します。

このステップでは、ユーザーフローにさらに2つのステップ実装します:

  • ユーザが Next Match または Try Again ボタンをタップすると、アプリは gameFragment 画面に移動します。

  • この時点でユーザがシステムの Back button を押すとアプリは TitleFragment 画面に移動します。

このユーザーフローを作成するには、PopUpTo attributes を使用して、back stack を管理します:

  1. navigation.xml ファイル内に、gameOverFragmentgameFragment に接続する navigation action を追加します。アクションの ID のフラグメント名が、XML のフラグメント名と一致していることを確認してください。例えば、アクション の ID は action_gameOverFragment_to_gameFragment のようになります。

  1. Attributes ペインで、アクションの Pop To attribute を titleFragment に設定します。

  2. back stack から削除される宛先に titleFragment を含めたくないので、Inclusive チェックボックスをクリアします。代わりに、TitleFragment までの全てを back stack から削除します。

  1. navigation.xml ファイル内に、gameWonFragmentgameFragment に接続する Navigation Action を追加します。

  1. 作成したアクションの Pop To attribute を titleFragment に設定し、Inclusive チェックボックスをオフにします。

次に、Try Again ボタンと Next Match ボタンに機能を追加します。ユーザがいずれかのボタンをタップした時に、アプリが GameFragment 画面に移動して、ユーザがゲームを再開できるようにする必要があります。

  1. GameOverFragment.kt Kotlin ファイルを開きます。onCreateView() メソッドの最後の return ステートメントの前に、次のコードを追加します。コードは、Try Again ボタンにクリックリスナーを追加します。ユーザがボタンをタップすると、アプリはゲームのフラグメントに移動します。
// Add OnClick Handler for Try Again button
        binding.tryAgainButton.setOnClickListener{view: View->
        view.findNavController()
                .navigate(R.id.action_gameOverFragment_to_gameFragment)}
  1. GameWinFragment.kt Kotlin ファイルを開きます。onCreateView() メソッドの最後の return ステートメントの前に、次のコードを追加します。
// Add OnClick Handler for Next Match button
        binding.nextMatchButton.setOnClickListener{view: View->
            view.findNavController()
                    .navigate(R.id.action_gameWonFragment_to_gameFragment)}
  1. アプリを実行してゲームをプレイし、Next MatchTry Again ボタンをテストします。どちらのボタンでもゲーム画面に戻るので、もう一度ゲームを試すことができます。

  2. ゲームに勝利または敗北した後、Next Match または Try Again をタップします。ここで、システムの Back button をタップします。アプリは、元の画面に戻るのではなく、タイトル画面に移動する必要があります。

アプリバーに上ボタンを追加する

App bar

app bar と呼ばれることもあるアプリバーは、アプリのブランディングアイデンティティのための専用スペースです。例えば、アプリバーの色を設定できます。アプリバーを使用すると、オプションメニューなどの使い慣れたナビゲーション機能にアクセスできます。アプリバーからオプションメニューにアクセスするには、縦に3つ並んだドットのアイコンをタップします。

アプリバーには、各画面で変更できるタイトル文字列が表示されます。AndroidTrivia アプリのタイトル画面では、アプリバーに "Android Trivia" と表示されます。質問画面では、タイトル文字列はユーザがどの質問をしているかも示します。("1/3", "2/3", "3/3")

Up button

現在アプリでは、ユーザはシステムの Back button を使用して前の画面に移動します。ただし、Android アプリには、アプリバーの左上に表示される画面上ボタンもあります。

AndroidTrivia アプリでは、タイトル画面を除く全ての画面に Up button を表示する必要があります。タイトル画面はアプリの画面階層の最上位にあるため、ユーザがタイトル画面に到達すると Up button は表示されなくなります。

Up button と Back button:

  • 下のスクリーンショットで 1 として示されている Up button がアプリバーに表示されます。
  • Up button は、画面間の階層関係に基づいて、アプリ内を移動します。Up button は、ユーザをアプリの外に移動させることはありません
  • 下のスクリーンショットで 2 として示されている Back button は、開いているアプリに関係なく、System Navigation Bar に表示されるか、デバイス自体の機械的なボタンとして表示されます。
  • Back button は、ユーザが最近操作した画面を逆方向に移動します(back stack)

詳細については、Designing Back and Up navigation を参照してください。

Up button のサポートを追加する

Navigation Component には、NavigationUI と呼ばれる UIライブラリが含まれています。Navigation Controller はアプリバーと統合して、Up button の動作を実装するので、自分で行う必要はありません。

次のステップでは、navigation controller を使用して、Up button をアプリに追加します:

  1. MainActivity.kt ファイルを開きます。onCreate() メソッド内に、Navigation Controller オブジェクトを検索するコードを追加します。
val navController = this.findNavController(R.id.myNavHostFragment)
  1. また、onCreate() メソッド内に、Navigation Controller をアプリバーにリンクするコードを追加します。
NavigationUI.setupActionBarWithNavController(this,navController)
  1. onCreate() メソッドの後で、onSupportNavigateUp() メソッドをオーバーライドして、navigation controller で navigateUp() を呼び出します。
override fun onSupportNavigateUp(): Boolean {
        val navController = this.findNavController(R.id.myNavHostFragment)
        return navController.navigateUp()
    }
  1. アプリを実行します。Up button は、タイトル画面を除く全ての画面のアプリバーに表示されます。アプリのどこにいても、Up button をタップすると、タイトル画面に移動します。

オプションメニューを追加する

Android には、オプションメニューなど、様々な種類のメニューがあります。最近の Androidバイスでは、ユーザはアプリバーに表示される3つの縦のドットをタップしてオプションメニューにアクセスします。

このタスクでは、オプションメニューにバージョン情報メニュー項目を追加します。ユーザが About メニュー項目をタップすると、アプリは AboutFragment に移動し、アプリの使用方法に関する情報が表示されます。

AboutFragment を Navigation Graph に追加します

  1. navigation.xml ファイルを開き、Design タブをクリックして、Navigation Graph を表示します。

  2. New Destination ボタンをクリックして、fragment_about を選択します。

  1. Layout Editor で、"about" Fragment を左にドラッグして、他の Fragment と重ならないようにします。Fragment の IDabountFragment であることを確認してください。

オプションメニューリソースを追加する

  1. res フォルダーを右クリックし、New > Android Resource File を選択します。

  2. New Resource File ダイアログで、ファイルに option_menu という名前をつけます。

  3. Resource Type として Menu を選択し、OK をクリックします。

  1. res > menu フォルダーから option_menu.xml ファイルを開き、Design タブをクリックして Layout Editor を表示します。

  2. Palette ペインから Menu Item をドラッグし、デザインエディターの任意の場所にドロップします。menu item がプレビューと Component Tree に表示されます。

  1. プレビューまたは Component Tree で、メニュー項目をクリックして、Attribute ペインに attributes を表示します。

  2. menu item's の ID を aboutFragment に設定します。タイトルを @string/about に設定します。

Tip:

追加したメニュー項目の ID が、Navigation Graph に追加した AboutFragment の ID と完全に同じであることを確認してください。これにより、onClick ハンドラーのコードが遥かに簡単になります。

onClick ハンドラーを追加する

このステップでは、ユーザが About メニュー項目をタップした時の動作を実装するコードを追加します。

  1. TitleFragment.kt Kotlin ファイルを開きます。onCreateView() メソッド内で、return 前に、setHasOptionsMenu() メソッドを呼び出し、true を渡します。
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                         savedInstanceState: Bundle?): View? {
   ...
   setHasOptionsMenu(true)
   return binding.root
}
  1. onCreateView() メソッドの後で、onCreateOptionsMenu() メソッドをオーバーライドします。メソッドで、オプションメニューを追加し、メニューリソースファイルを拡張します。
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.options_menu, menu)
}
  1. onOptionsItemSelected() メソッドをオーバーライドして、メニュー項目がタップされた時に適切なアクションを実行します。この場合のアクションは、選択したメニュー項目と同じ id を持つ Fragment に移動することです
override fun onOptionsItemSelected(item: MenuItem): Boolean {
     return NavigationUI.
            onNavDestinationSelected(item,requireView().findNavController())
            || super.onOptionsItemSelected(item)
}
  1. アプリがビルドされない場合は、コード内の未解決の参照を修正するためにパッケージをインポートする必要があるかどうかを確認してください。例えば、いくつかの参照を解決するために importandroid.view.* を追加できます。(そして import android.view.ViewGroup などのより具体的なインポートを置き換えます。)

  2. アプリを実行し、オプションメニューにある About メニューをテストします。メニュー項目をタップすると、アプリは "About" 画面に移動します。

Navigation Drawer を追加する

このタスクでは、AndroidTrivia アプリに Navigation drawer を追加します。Navigation Drawer は、画面の端からスライドして出るパネルです。ドロワーには通常、ヘッダーとメニューが含まれています。

phone-size デバイスでは、使用しない時は Navigation drawer は非表示になります。2種類のユーザアクションにより、navigation drawer を表示できます。

  • drawer は、ユーザが画面の開始端から終了端に向かってスワイプすると表示されます。AndroidTrivia アプリでは、ユーザが左から右にスワイプすると Navigation drawer が表示されます。

  • drawer は、ユーザがアプリの開始先にいて、アプリバーのドロワーアイコンをタップすると表示されます。(drawer は、nav drawer button または humburger icon と呼ばれることもあります。)

以下のスクリーンショットは、開いている Navigation Drawer を示しています。

navigation drawer は、Material Components for Android、またはマテリアルライブラリの一部です、マテリアルライブラリを使用して、Googleマテリアルデザインガイドラインの一部であるパターンを実装します。

AndroidTrivia アプリでは、Navigation drawer に2つのメニュー項目が含まれます。最初の項目は既存の"about" Fragment を指し、二番目の項目は新しい"rule" Fragment を指します。

マテリアルライブラリをプロジェクトに追加する

  1. アプリレベルの Gradle build ファイルで、マテリアルライブラリの依存関係を追加します:
dependencies {
    ...
    implementation "com.google.android.material:material:$supportlibVersion"
    ...
}
  1. プロジェクトを同期します。

Destination Fragment に ID があることを確認してください

navigation drawer には2つのメニュー項目があり、それぞれが Navigation Drawer から到達できるフラグメンを表します。両方の Destination は、Navigation Graph で ID を持っている必要があります。

AboutFragment の Navigation Graph には既に ID がありますが、RulesFragment にはないので、ここで追加します。

  1. fragment_rules.xml レイアウトファイルを開いて、ファイルの外観を確認します。Desing タブをクリックして、デザインエディタでプレビューを確認します。

  2. Navigation Editor で、navigation.xml ファイルを開きます。New Destination ボタンをクリックして、rules Fragment を追加します。その IDrulesFragment に設定します。

Drawer メニューと Drawer レイアウトを作成する

navigation drawer を作成するには、navigation menu を作成します。レイアウトファイルの DrawerLayout 内に View を配置する必要もあります。

  1. drawer メニューを作成します。res フォルダーを右クリックし、New Resource File を選択します。ファイルに navdrawer_menu という名前を付け、リソースタイプをMenu に設定して、OK をクリックします。

  1. res > menu フォルダーから、navdrawer_menu.xml を開き、Design タブをクリックします。2つの menu item を Palette ペインから Component Tree にドラッグして追加します。

  2. 最初のメニュー項目の idrulesFragment に設定します。(メニュー項目の ID は Fragment の ID と同じである必要があります。)タイトルを @string/rules に、アイコンを @drawable/rules に設定します。

  1. 2つ目のメニュー項目では、idaboutFragment に、タイトル文字列を @string/about に、アイコンを @drawable/about_android_trivia に設定します。

Note:

メニュー項目に destination Fragment と同じ ID を使用する場合、onClick リスナーを実装するためにコードを記述する必要はありません。

  1. activity_main.xml レイアウトファイルを開きます。全ての Drawer 機能を無料で利用するには、View を DrawerLayout 内に配置します。<LinearLyout> 全体を <DrawerLayout> でラップします。(つまり、DrawerLayout を root view として追加します)
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
   <androidx.drawerlayout.widget.DrawerLayout
       android:id="@+id/drawerLayout"
       android:layout_width="match_parent"
       android:layout_height="match_parent">

   <LinearLayout
       . . . 
       </LinearLayout>
   </androidx.drawerlayout.widget.DrawerLayout>
</layout>
  1. 次に、Drawer を追加します。これは、先ほど定義した navdrawer_menu を使用する NavigationView です。次のコードを DrawerLayout<LinearLayout> 要素の後に追加します。
<com.google.android.material.navigation.NavigationView
   android:id="@+id/navView"
   android:layout_width="wrap_content"
   android:layout_height="match_parent"
   android:layout_gravity="start"
   app:headerLayout="@layout/nav_header"
   app:menu="@menu/navdrawer_menu" />

Navigation Drawer を表示する

Navigation Drawer と Navigation Drawer Layout のメニュー項目を作成しました。次に、Navigation Drawer を Navigation Controller に接続して、ユーザが Navigation Drawer でアイテムを選択すると、アプリが適切な Fragment に移動するようにします。

  1. MainActivity.kt ファイルを開きます。onCreate() で、ユーザが Navigation Drawer を表示できるようにするコードを追加します。これには、setupWithNavController() を呼び出します。onCreate() の下部に次のコードを追加します。
NavigationUI.setupWithNavController(binding.navView, navController)
  1. アプリを実行します。左端からスワイプして Navigation Drawer を表示し、Drawer の各メニュー項目が正しい場所に移動していることを確認します。

navigation drawer は機能しますが、もう1つ修正する必要があります。通常、アプリでは、ホーム画面のアプリバーにある drawer ボタンをタップして表示することもできます。アプリのホーム画面にはまだ drawer が表示されていません。

Drawer ボタンから navigation drawer を表示する

最後のステップは、ユーザがアプリバーの左上にある引き出しボタンから navigation drawer にアクセスできるようにすることです。

  1. MainActivity.kt Kotlin ファイルで、lateinitdrawerLayout メンバー変数を追加して、drawer layout を表します。
private lateinit var drawerLayout: DrawerLayout

Note:

Kotlin は null-safety 言語です。null の安全性を提供する方法の1つは、lateinit を使用することです。これにより、null 参照を返す危険なしに変数の初期化を遅らせることができます。

  1. onCreate() メソッド内で、binding 変数が初期化された後で、drawerLayout を初期化します。
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this,
                R.layout.activity_main)

drawerLayout = binding.drawerLayout
  1. 三番目のパラメータとして、draweLayout を setupActionBarWithNavController() メソッドに追加します。
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
  1. onSupportNavigateUp() メソッドを編集して、PnavController.navigateUp を返す代わりに NavigatioUI.navigateUp を返します、navigation controllerと drawerLayout をnavigateUp()渡します。メソッドは次のようになります。
override fun onSupportNavigateUp(): Boolean {
   val navController = this.findNavController(R.id.myNavHostFragment)
   return NavigationUI.navigateUp(navController, drawerLayout)
}
  1. アプリを実行して挙動を確認します。

まとめ

Navigation components

Android navigation library を使用するには、いくつかの設定を行う必要があります。

  • モジュールレベルの build.gradle ファイルに、navigation-fragment-ktx および navigation-uiktx の依存関係を追加します。

  • プロジェクトレベルの build.gradle ファイルに、navigationVersionext 変数を追加します。

Navigation destinations は、ユーザが移動する Fragment、Activity、またはその他のアプリコンポーネントです。Navigation graph は、ある navigation 先から次の navigation 先への可能なパスを定義します。

  • navigation graph を作成するには、Navigation タイプの新しい Android Resource file を作成します。このファイルは、アプリの navigation フローを定義します。ファイルは res/navigation フォルダーにあり、通常は navigation.xml と呼ばれます。

  • Navigation Editor で Navigation graph を表示するには、navigation.xml ファイルを開き Design タブをクリックします。

  • Navigation Editor を使用して、Fragment などの宛先を navigation graph に追加します。

  • ある destination から別の destination へのパスを定義するには、navigation graph を使用して、destination を接続するアクションを作成します。navigation.xml ファイルでは、これらの各接続は ID を持つ action として表されます。

通常は navigation host は NavHostFragment と名付けられ、navigation graph のホストとして機能します:

  • ユーザが navigation graph で定義された宛先間を移動すると、NavHostFragment は fragment をスワップインおよびスワップアウトし、Fragment をバックスタックを管理します。

  • activity_main.xml レイアウトファイルでは、NavHostFragmentandroid:name="androidx.navigation.fragment.NavHostFragment" という名前の fragment 要素で表されます。

ユーザが view などをタップした時に表示される fragment を定義するには、view の onClick リスナーを設定します:

  • onClick リスナーで、view に対して findNavController().navigate() を呼び出します。

  • 宛先に繋がる actionID を指定します。

Conditional navigation は、ある場合には1つの画面に移動し、別の場合には別の画面に移動します。conditional navigation を作成するには:

  1. Navigation Editor を使用して、Starting Fragment から可能な各 destination fragments への接続を作成します。

  2. 各接続に一意の ID を割り当てます。

  3. View のクリックリスナーメソッドで、条件を検出するコードを追加します。次に、View で findNavController().navigate() を呼び出し、適切な action ID を渡します。

Back button

システムの Back button は通常、デバイスの下部にあります。デフォルトでは、Back button は、ユーザが最後に表示した画面に戻ります。状況によって、Back button でユーザが移動する場所を制御することもできます。

  • Navigation Editor では、Attributes ペインを使用して Pop To 設定を変更できます。この設定により、宛先が back stack から削除されます。これにより、Back button がユーザをどこに移動させるかを制御します。

  • Pop To 設定は、navigation.xml ファイルの popUpTp attributes として表示されます。

  • Inclusive チェックボックスを選択すると、popUpToInclusive attribute が true に設定されます。この宛先までの全ての宛先は、back stack から削除されます。

  • action's の popUpTo attribute がアプリの開始先に設定され、popUpToInclusivetrue に設定されている場合、Back button はユーザをアプリから離脱させます。

Up button

Android アプリの画面には、app bar に左上に表示される画面上のボタンがあります(アプリバーは action bar と呼ばれることもあります)。Up button は、画面間の階層関係に基づいて、アプリの画面内を "upwards" に移動します。

navigation controller の NavigationUI ライブラリはアプリバーと統合されているため、ユーザは app bar の Up button をタップして、アプリのどこからでもアプリのホーム画面に戻ることができます。

Navigation Controller を app bar にリンクするには:

  1. onCreate() で、NavigationUI クラスの stupActionBarWithNavController() を呼び出し、navigation controller に渡します。
val navController = this.findNavController(R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this,navController)
  1. onSupportNavigateUp() メソッドをオーバーライドして、navigation controller で navigateUp() を呼び出します。
override fun onSupportNavigateUp(): Boolean {
        val navController = this.findNavController(R.id.myNavHostFragment)
        return navController.navigateUp()
    }
}

Options menu

options menu は、ユーザがアプリバーから縦に3つ並んだドットのアイコンをタップしてアクセスするメニューです。Fragment を表示するメニュー項目を含む options menu を作成するには、Fragment に ID があることを確認してください。次に option menu を定義し、メニュー項目の onOptionsItemSelected() ハンドラをコーディングします。

  1. Fragment に ID があることを確認します:
  2. destination Fragment を navigation graph に追加し、Fragment の ID をメモします。(ID は必要に応じて変更できます。)
  3. オプションメニューを定義する:
  4. 通常は options_menu.xml という名前の、menu type の Android resource file を作成します。ファイルは Res > Menu フォルダに保存されます。
  5. デザインエディターで options_menu.xml ファイルを開き、Menu Item widgetPalette ペインからメニューにドラッグします。
  6. 便宜上、メニュー項目の ID を、ユーザがこのメニュー項目をクリックした時に表示される Fragment の ID と同じにします。このステップは必須ではありませんが、メニュー項目の onClick 動作のコーディングが簡単になります。
  7. menu item の onClick ハンドラーをコーディングします。
  8. option menu を表示する fragment または activity のonCreateView() 内で、setHasOptionsMenu(true) を呼び出し、options menu を有効にします。
  9. onCreateOptionsMenu() を実装して、options menu を inflate します。
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.options_menu, menu)
}
  • onOptionsItemSelected() メソッドをオーバーライドして、menu item がクリックされた時に適切なアクションを実行します。
  • 次のコードは、メニュー項目と同じ ID を持つ Fragment を表示します。(このコードは、menu item と fragment に同じ ID がある場合にのみ起動します。)
override fun onOptionsItemSelected(item: MenuItem): Boolean {
     return NavigationUI.
            onNavDestinationSelected(item,requireView().findNavController())
            || super.onOptionsItemSelected(item)
}

Navigation drawer

navigation drawer は、画面の端からスライドして出るパネルです。ユーザが navigation drawer を開く方法は2つあります。

  • 任意の画面で、starting edge(通常は左)からスワイプします。
  • アプリの上部にある app bar の drawer ボタンをタップします。

アプリに navigation drawer を追加するには:

  1. build.gradle(app) に依存関係を追加します。

  2. destination fragment に ID があることを確認してください。

  3. drawer メニューを作成します。

  4. drawer を Fragment のレイアウトに追加します。

  5. drawer を navigation controller に追加します。

  6. app bar の drawer ボタンを設定します。

これらの手順については、以下で詳しく説明します。

  1. buidle.gradle に依存関係を追加する
  2. navigation drawer は、Android ライブラリの Material Components の一部です。マテリアルライブラリを build.gradle(app) ファイルに追加します。
dependencies {
    ...
    implementation "com.google.android.material:material:$supportlibVersion"
    ...
}
  1. destination に ID を指定します。

  2. navigation drawer から Fragment に到達できる場合は、Navigation graph で Fragment を開いて ID があることを確認します。

  3. Drawer の Menu を作成する。

  4. navigation drawer menu 用の Menu タイプの Android Resource file を作成します。これにより、Res > Menu フォルダーに新しい navdrawer_menu.xml ファイルが作成されます。
  5. デザインエディターで、Menu Item widgetMenu に追加します。

  6. Fragment の layout に drawer を追加します。

  7. navigation host fragment を含むレイアウト(通常は main layout) で、root view として <androidx.drawerlayout.widget.DrawerLayout> を使用します。
  8. <com.google.android.material.navigation.NavigationView> をレイアウトに追加します。

  9. Drawer を navigation controller に接続します:

  10. navigation controllerを作成する activit を開きます。onCreate() で、NavigationUI.setupWithNavController() を使用して、navigation drawer を navigation controller に接続します。
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
       this, R.layout.activity_main)
NavigationUI.setupWithNavController(binding.navView, navController)
  1. app bar の drawer button を設定します。

  2. Navigation controller を作成する activity の onCreate() で、drawer layout を三番目のパラメーターとして NavigationUI.setupActionBarWithNavController い渡します:

val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
    this, R.layout.activity_main)

NavigationUI.setupActionBarWithNavController(
    this, navController, binding.drawerLayout)
  • Up button を drawer button と連動させるにはonSuportNavigateUp() を編集して NavigatioUI.navigateUp() を返します。navigation controllre と drawer layout を navigateUp に渡します。
override fun onSupportNavigateUp(): Boolean {
   val navController = this.findNavController(R.id.myNavHostFragment)
   return NavigationUI.navigateUp(navController, drawerLayout)
}

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

知識ゼロからの Kotlin Android アプリリリースへの軌跡 / Day10【Create a fragment編】

学ぶこと

  • Fragment を静的にアプリに追加する方法

すること

  • Activity 内に Fragment を作成します

アプリの概要

このレッスンを構成する3つのコードラボでは、AndroidTrivia というアプリで作業します。完成したアプリは、ユーザが Android コーディングに関する3つの雑学に答えるゲームです。ユーザが3つ全ての質問に正しく答えると、ゲームに勝ち、結果を共有できます。

AndroidTrivia アプリは、Navigation パターンとコントロールを示しています。アプリにはいくつかのコンポーネントがあります。

  • 上のスクリーンショットの左側に示されているタイトル画面で、ユーザはゲームを開始します。
  • 上の中央に示す質問のあるゲーム画面で、ユーザはゲームをプレイして回答を送信します。
  • 右上に示されている Navigation drawer は、アプリの側面からスライドして出て、ヘッダー付きのメニューが含まれてます。Drawer アイコンは、Navigation drawer を開きます。Navigation drawer には、ページへのリンクとゲームのルールへのリンクが含まれています。

アプリの上部には、アプリバーと呼ばれるアクションバーとも呼ばれる色付きの View が表示されます。

Starter app project を探索する

このコードラボでは、Trivia App を完了する時に必要なテンプレートコードと Fragment クラスを提供するスターターアプリから作業します。

  1. AndroidTrivia-Starter をダウンロードします。

  2. Android Studio でプロジェクトを開き、アプリを実行します。アプリが開くと、アプリ名と空白の画面が表示されるだけです。

  1. app > java フォルダーを開き、MainActivity クラスと Fragment クラスを確認します。

  1. res > layout フォルダーを開き、activity_main.xml をダブルクリックします。activity_main.xml ファイルがレイアウトエディターに表示されます。

  2. Desgin タブに切り替えます。activity_main.xml ファイルの Component Tree には、ルートレイアウトが垂直の LinearLayout として表示されます。

vertical linear layout では、レイアウト内の全ての子 View が垂直に配置されます。

Fragment を追加する

Fragment は、activity の動作またはユーザーインターフェース(UI)の一部を表します。単一の Activity で複数のフラグメントを組み合わせてマルチペイン UI を構築したり、Fragment を複数の Activity で再利用したりできます。

Fragment を、他の Activity でも使用できる "sub-activity" のような Activity のモジュールセクションと考えてください。

  • Fragment には独自のライフサイクルがあり、独自の入力イベントを受け取ります。
  • Activity の実行中に Fragment を追加または削除できます。
  • Fragment は Kotlin クラスで定義されます。
  • Fragment のUIは、XML レイアウトファイルで定義されます。

AndroidTrivia アプリには、Main Activity といくつかの Fragment があります。ほとんどの Fragment とそのレイアウトファイルは定義されています。このタスクでは、Fragment を作成し、その Fragment をアプリの Main Activity に追加します。

Fragment クラスを追加する

空の TitleFragment クラスを作成します。新しい Fragment の Kotlin クラスを作成することから始めます。

  1. Android Studio で、プロジェクトペイン内の任意の場所をクリックして、フォーカスをプロジェクトファイルに戻します。com.example.android.navigation フォルダーをクリックします。

  2. File > New > Fragment > Fragment(Blank) を選択します。

  3. Fragment 名として、TitleFragment と入力します。

  4. Fragment Layout 名として、placeholder_layout を入力します。(このレイアウトは、TitleFragment用に設計されたレイアウトがすでにあるため、アプリでは使用しません)

  5. ソース言語として、Kotlin を選択します。

  6. Finish をクリックします。

  7. TitleFragment.kt ファイルをまだ開いていない場合は開きます。これには、Fragment lifecycle 中に呼び出されるメソッドの1つである。onCreateView()) メソッドが含まれています。

  8. onCreateView() 内のコードを削除します。onCreateView() 関数には、次のコードのみが残されています。

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                         savedInstanceState: Bundle?): View? {
}
  1. TitleFragment クラスで、onCreate() メソッド、fragment 初期化パラメーター、および companion オブジェクトを削除します。TitleFragment クラスが次のようになっていることを確認します。
class TitleFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        
    }
}

binding object を作成する

Fragment を現在コンパイルされません。Fragment をコンパイルするには、binding object を作成し、Fragment's view を inflate する必要があります。(これは、Activity に setContentView() を使用するのと同じです。)

  1. TitleFragment.ktonCreateView()メソッドで、binding 変数(val binding)を作成します。

  2. Fragment's view を inflate するためには、Fragment の Binding オブジェクト(FragmentTitleBinding) で、DataBindingUtil.inflate() メソッドを呼び出します。

メソッドに4つのパラメータを渡します:

  • binging layout を inflate するために使用される LayoutInflater である、inflater
  • inflate するレイアウトの XML レイアウトリソース。すでに定義されているレイアウトの1つである R.layout.fragment_title を使用します。
  • ViewGroupcontainer。(このパラメーターはオプションです。)
  • attachToParent 値の場合は false

  • DataBindingUtil.inflate が返す binding を binding 変数に割り当てます。

  • inflate view を含むメソッドから binding.root を返します。onCreateView() メソッドは次のコードのようになります。

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                         savedInstanceState: Bundle?): View? {
   val binding = DataBindingUtil.inflate<FragmentTitleBinding>(inflater,
           R.layout.fragment_title,container,false)
   return binding.root
   }
  1. res > layout を開き、placeholder_layout.xml を削除します。

新しい Fragment を main layout file に追加する

このステップでは、TitleFragment をアプリの activity_main.xml レイアウトファイルに追加します。

  1. res > layout > activity_main.xml を開き、Code タブを選択して、レイアウト XML コードを表示します。

  2. 既存の LinearLayout 要素内に、fragment 要素を追加します。

  3. フラグメントの ID を titleFragment に設定します。

  4. fragment's name を Fragment クラスのフルパスに設定します。この場合、com.example.android.navigation.TitleFragment です。

  5. layout の幅と高さを match_parent に設定します。

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <fragment
                android:id="@+id/titleFragment"
                android:name="com.example.android.navigation.TitleFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                />
        </LinearLayout>

</layout>
  1. アプリを実行します。Fragment がメイン画面に追加されました。

まとめ

このコードラボでは、AndroidTrivia アプリに Fragment を追加しました。これは、このレッスンの次の2つのコードラボで引き続き作業していきます。

  • Fragment は、Activity のモジュールセクションです。
  • Fragment には独自のライフサイクルがあり、独自の入力イベントを受け取ります。
  • <fragment> タグを使用して、XML レイアウトファイルでフラグメントのレイアウトを定義します。
  • onCreateView() で Fragment のレイアウトを Inflate します。
  • activity の実行中に Fragment を追加または削除できます。

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

Firebase + BigQuery + Redash で分析基盤を作ろう

Firebase + BigQuery + Redash で分析基盤を作ろう

今回は、Firebsae Analytics で取得しているデータを Big Query + Redash を使って可視化できるようにする方法を簡単にまとめていきます。

手順

  • Firebase と BigQuery 連携
  • BigQuery と Redash 連携

Firebase と BigQuery 連携

まずは、使用している Firebase プロジェクトと Big Query を連携します。どちらも Google が提供しているサービスなので親和性が高く。Firebase コンソールをちょこっといじるだけで完了します。

  1. Firebase プロジェクトを開きます。

  2. 歯車アイコンをタップして プロジェクトを設定 を選択します。

  3. 設定画面で統合タブをクリックします。

  4. BigQuery カードでリンクをクリックします。

  5. 表示されたダイアログの手順に沿って設定をします。(今回は分析系のデータだけ必要だったので、Google Analytics アプリのみをエストポートの対象にしました。)

※ リンクを設定が完了すると、Big Query に設定したプロジェクトに対応するデータがエクスポートされ、データセットが作成されます。また、1日分のイベントデータが初めてエスクポートされた時に作成されるため、即時に使えるというわけではありません。

https://support.google.com/firebase/answer/6318765?hl=ja#

BigQuery と Redash 連携

次に、BigQuery と Redash を連携していきます。

  1. Redash から BigQuery を使えるようにするために、BigQuery のサービスアカウントキーを作成します(手順についてはこちらを参照してください)。

  2. Redash の Settings > Data Sources > New Data Source から BigQuery Data Source を作成します。Project ID には、Firebsae のプロジェクトID を、JSON Key File には BigQuery のサービスアカウントキーをそれぞれ入力します。

3.Create > Query で新しいクエリを作成してみます。Data Source には前の手順で作成した BigQuery を選択します。

クエリの実行が完了しました🎉

f:id:yum_fishing:20200918160236p:plain

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com