- 投稿日:2019-07-28T23:23:34+09:00
UnityのAndroid実機ビルドでつまづいた話その2
前回までのあらすじ
UnityのAndroid実機ビルドでつまづいた話その1で一時的に動いたものの
また動かなくなった
前回うまくいったのはandroidの設定だと思っていたけど
どうやら違ったらしい結論から言うとPC変えたらいけた
動作環境
Windows 10(前回と違いノートパソコン)
adb ver.1.0.39
Unity 2018.3~2018.4LTS
※2019はGradleが原因でビルドできなかった
Galaxy note 9 Android 9.0Buildする前に
JDKとSDKのパスを確認
NDKは無くても問題ないBuild And Runを実行
Use Highest Installをクリック
※何故かUpdate Android SDKをクリックしても意味がない
このことについて詳細をご存知の方はコメントにて知らせていただけると助かりますBuildできなかった原因
十中八九AndroidStudioのバージョン上げたせい
それのせいでplatform-toolsのadbのバージョンが上がって
組み合わせの問題かadbのせいかはわからないけど
エラー吐いたんだと思われる
メインPCではADBを上手く構築できなかった
メインPCでビルドできたらまた記事書きます
- 投稿日:2019-07-28T19:26:19+09:00
【Kotlin】ActivityのLaunchModeについて
Androidアプリ開発でActivityの
launchMode
の設定が結構重要だと感じたので調べたことをまとめます。
特にBack Stackで挙動が大きく変わるので画面遷移の設計とかにもろに影響する内容だと思います。
launchMode
の設定には2通りあって
- Manifestファイルの
<activity>
タグの属性として設定する方法- Intentのfragで設定する方法
です。画面遷移毎に挙動を変える場合は2つ目の方法が必要ですが、常に同じ挙動なのであれば1つ目の方法で良いと思います。
Manifestファイルの
<activity>
タグの属性として設定する方法
<activity>
タグで設定できるlaunchMode
は4種類です。
下記のように設定します。
ruby:qiita.rb
puts 'code with syntax'
Manifest.xml<activity // 省略 android:launchMode="standard" // 省略 </activity>standard
- デフォルト値
- Activityのインスタンスがタスク上に生成される
- Back Stackの一番上に追加される
- 複数の異なるタスク上にインスタンスを生成することができるし、同じタスク上に複数のインスタンスを生成することもできる
singleTop
- Back Stackの一番上にActivityのインスタンスがある場合は新しくインスタンスを生成せずに、既にあるインスタンスを
onNewIntent()
で呼ぶ。- Activityのインスタンスが存在してもBack Stackの一番上でなければ新しいインスタンスを生成する
![]()
![]()
singleTask
- 新しいBack StackのrootとしてActivityのインスタンスを生成する
- ただし別のBack Stackの上にActivityのインスタンスが存在する場合は、既にあるインスタンスを
onNewIntent()
で呼び新しいBack Stackのrootにする- Activityのインスタンスは常に1つ
![]()
![]()
Note: Although the activity starts in a new task, the Back button still returns the user to the previous activity.
singleInstance
- singleTaskとほぼ同じ
注意点
Note: The behaviors that you specify for your activity with the launchMode attribute can be overridden by flags included with the intent that start your activity,
Intentのfragで上書きされます。
Intentのfragで設定する方法
Intentのfragで設定できる値は3種類です。
FLAG_ACTIVITY_NEW_TASK
launchMode
のsingleTask
と同じFLAG_ACTIVITY_SINGLE_TOP
launchMode
のsingleTop
と同じFLAG_ACTIVITY_CLEAR_TOP
launchMode
に該当するものはない- 既にActivityのインスタンスが存在する場合、Back Stack上でこのActivityの上にあるインスタンスが全てdestoryされた状態で
onNewIntent()
メソッドで既存のインスタンスが呼ばれるFLAG_ACTIVITY_CLEAR_TOP
とFLAG_ACTIVITY_NEW_TASK
は一緒に使われることが多い
- 新しいタスクでインスタンスを生成した時にBack Stackをclearできるから
今回の記事は以上です。
- 投稿日:2019-07-28T18:05:37+09:00
AndroidアプリにGoogle Mapを組み込む
最近 Maps SDK for Android を触る機会があったので、アレコレと試しています。
アプリに地図を組み込むのは面倒だと放置していましたが、凝ったことをしなければ組み込みは簡単ですし、マップの上に好きなオブジェクトを置いたり、それらにインタラクションをつけるのも手軽にできます。
そこで、ここでは以下についてまとめました。
- 地図を組み込んだアクティビティの作成と起動
- ジェスチャなど、地図についての設定
- マーカーや図形など、マップオブジェクトの配置
- マップオブジェクトをタップに反応させるなど、インタラクションの設定
なお、この記事は下記ページにある内容を元に書きました。Maps SDK を使う際には1度確認して頂ければと思います。
環境
このページに書かれているコード等は以下の環境下で動作・検証しています。
- macOS Mojave バージョン10.14.5
- Android Studio 3.4.2
- Pixel3a + Android 9
自分で実装してみたものは GitHub で公開しています。
APIキーの取得
Maps API を利用するためには、Google Cloud Platform Console にログインして API キーを取得し、それを Android Studio プロジェクトに設定する必要があります。キーを取得するための手順はこちらでご確認ください。
APIキーの取り扱いについて
この記事では取得したキーの用途に制限をかけず、アプリもデバッグモードでしか実行していません。リリース用のキーには、不正使用や割り当ての盗用を防止する措置を実施したうえで使用して下さい。
私が取得したときの手順
参考までに、現時点(2019/07)で私が取得したときの手順を記載しておきます。
ちなみに Google Cloud Platform 上に自分のアカウントがありますので、そのアカウントを使って作ったプロジェクトが存在する状態です。
1. Google Cloud Platform Console にアクセスします。
2. Platform 上の任意のプロジェクトを開きます。
3. コンソールの左ペインに"Googleマップ"がありますので、それをクリック。
4. "Maps SDK for Android" をクリック。
5. 『有効にする』ボタンをクリックすると、『APIを有効にしています』というメッセージが出るのでしばらく待ちます。
6. 有効になったら『認証情報』をクリック。
7. 『認証情報を作成』をクリックし、展開されるサブメニューから『APIキー』をクリック。
8. API キーが発行されるので、これをコピーしておきます。
アクティビティの作成と起動
地図が組み込まれたアクティビティを作成するには、Android Studio のメニューから作成するのが簡単です。
※直接 MapView をレイアウトに埋め込むこともできるはずですが、調べきれてません…。1. アクティビティの作成
プロジェクトと一緒に作成する方法と、既存のプロジェクトに追加する方法があります。
ちなみに必要なライブラリは Android Studio が自動的に追加してくれます。作成後の build.gradle を確認してみてください。1-1. プロジェクトと一緒に作成する
Android Studio の起動画面で "Start a new Android Studio Project" を選択し、『Create New Project』の画面で "Google Maps Activity" を選択します。
そのままプロジェクト名や Package Name などを入力してプロジェクトを作成すると、地図が組み込まれたアクティビティも一緒に作成されます。1-2.既存のプロジェクトに追加する
Android Studio のメニューから "File" を選択するか、プロジェクトビューで右クリックすると表示されるコンテキストメニューで "New" > "Google" > "Google Maps Activity" をクリックします。
そのあとアクティビティ名などを任意に設定して『Finish』をクリックすれば地図が組み込まれたアクティビティが追加されます。2. APIキーの設定
res/values に『google_maps_api.xml』というファイルができています。
『YOUR_KEY_HERE』の部分を Console で取得したAPIキーに変更して下さい。<string name="google_maps_key" translatable="false" templateMergeStrategy="preserve">YOUR_KEY_HERE</string>3. アクティビティの起動
作成したアクティビティを起動すると地図が表示されます。ちなみにシドニーを中心に表示されているのは、自動生成されたコードがそのようになっているためです。
地図自体の設定や操作
作成されたアクティビティには OnMapReadyCallback インターフェイスが実装されており、 onMapReady というメソッドが追加されています。このメソッドは、地図の準備ができたときに呼び出されるコールバックです。
地図関連の操作をするときは、基本的に onMapReady で取得できる GoogleMap オブジェクトの各メソッドを呼び出します。表示位置の変更
作成されたアクティビティを実行すると、カメラ位置(=地図の中心)がシドニーになっています。これを別の場所に変更します。
GoogleMap#moveCamera メソッドで、カメラ位置や表示倍率を変更できます。
moveCamera の引数は CameraUpdate クラスです。これは CameraUpdateFactory クラスで生成できます。CameraUpdateFactory のメソッド(一部)
メソッド 機能 newLatLng(LatLng latLng) 任意の緯度経度を中心に表示 newLatLngZoom(LatLng latLng, float zoom) 任意の緯度経度を中心に、任意の倍率で表示 zoomTo(float zoom) 現在の表示位置を任意の倍率で表示 newCameraPosition(CameraPosition cameraPosition) CameraPositionオブジェクトに設定した内容で表示 例えば、大阪駅を中心に16倍で表示したい場合は以下のようになります。
val osakaStation = LatLng(34.702423,135.495972) moveCamera(CameraUpdateFactory.newLatLngZoom(osakaStation, 16.0f))位置や倍率だけでなく仰角や回転角など、より詳細な表示位置を設定したい場合は newCameraPosition メソッドを使ってください。引数の CameraPosition は CameraPosition.Builderを使って生成できます。
地図の種類を変更
GoogleMap#setMapType(int type) で表示する地図の種類を変更することができます。type にセットする値は、GoogleMap クラスの定数5つから選べます。
setMapTypeの引数と表示結果
MAP_TYPE_NORMAL MAP_TYPE_SATELLITE MAP_TYPE_HYBRID MAP_TYPE_TERRAIN MAP_TYPE_NONE 通常 衛星写真 衛星+地図マーカー 地形図 なし 地図表示に制限を設ける
GoogleMap クラスのメソッドで、表示可能な領域や倍率を制限することができます。
表示制限に関するメソッド(一部)
メソッド 機能 setLatLngBoundsForCameraTarget(LatLngBounds bounds) 指定した領域からカメラ位置が外に移動しないよう制限する setMaxZoomPreference(float maxZoomPreference) 指定した倍率以上にズームインしないよう制限する setMinZoomPreference(float minZoomPreference) 指定した倍率以下にズームアウトしないよう制限する 以下はズームを14倍〜16倍に制限し、特定の領域外にカメラが移動しないようにするコードです。
googleMap.apply { setMaxZoomPreference(16.0f) setMinZoomPreference(14.0f) setLatLngBoundsForCameraTarget( LatLngBounds(LatLng(34.695332 ,135.486831), LatLng(34.707469, 135.501508))) }余談ですが LatLngBounds をコンストラクタから生成する場合は、地図上の2点を指定して領域を定義します。このとき緯度経度の指定は「southwest, northeast」の順ですので注意してください(私はしばらく「northwest, southeast」と勝手に間違って悩みました)。
地図の操作に制限を設ける
GoogleMap#getUiSettings() メソッドで得られる UiSettings オブジェクトを操作することで、ジェスチャの有効/無効などを切り替えることができます。
UiSettings クラスには操作だけでなく、UI部品関連のメソッドも用意されていますので、APIを確認してみてください。操作制限に関するメソッド(一部)
メソッド 機能 setScrollGesturesEnabled(boolean enabled) スクロール操作(1本指でドラッグ)の有効/無効を設定 setZoomGesturesEnabled(boolean enabled) ズーム操作(ピンチイン・アウト)の有効/無効を設定 setRotateGesturesEnabled(boolean enabled) 回転操作(2本指で回す)の有効/無効を設定 setTiltGesturesEnabled(boolean enabled) チルト操作(2本指で引き起こす)の有効/無効を設定 setAllGesturesEnabled(boolean enabled) 全てのジェスチャの有効/無効を設定 以下では、スクロール/ズーム/回転/チルトを全て無効にしています。 setAllGesturesEnabled を使えば一括で無効にできます。
// Kotlinなのでプロパティで操作 googleMap.uiSettings.run { isScrollGesturesEnabled = false isTiltGesturesEnabled = false isRotateGesturesEnabled = false isZoomGesturesEnabled = false }マップオブジェクト
マップオブジェクトは、地図上に描画できるマーカーや円、多角形などのことです。
種類
マップオブジェクトの種類は以下の通りです。
種類 クラス 描画用のメソッド クリックリスナ1 マーカー Marker addMarker(MarkerOptions options) OnMarkerClickListener 円 Circle addCircle(CircleOptions options) OnCircleClickListener 線 Polyline addPolyline(PolylineOptions options) OnPolylineClickListener 多角形 Polygon addPolygon(PolygonOptions options) OnPolygonClickListener 画像 GroundOverlay addGroundOverlay(GroundOverlayOptions options) OnGroundOverlayClickListener タイル画像 TileOverlay addTileOverlay(TileOverlayOptions options) - 個人的に気になったのは、Circle を描画するときの半径がメートル指定ということです。
例えばランドマークに何らかのマークをつけたいときに Circle にしてしまうと、ズームするとその円が大きくなってしまいます。
何かの目印にしたい場合は、Circle でなく Marker をつける方がよさそうです。マップオブジェクトを描画
これも GoogleMap オブジェクトを介して描画します。どのオブジェクトであれ、基本的に手順は以下の通りです。各オブジェクトを描画するためのオプションクラスについては、前項の表で描画用メソッドの引数を参照して下さい。
- 目的のマップオブジェクトに対応したオプションクラスのオブジェクトを生成
- 1.のオブジェクトに、描画する色やサイズなどのプロパティを設定
- GoogleMap の各セッタに、2.のオブジェクトをセット
例1:枠線が緑色&内部が黄色&半径20メートルの円を、大阪駅を中心として描画
googleMap.addCircle( CircleOptions() .center(LatLng(34.702423, 135.495972)) .radius(20.0) .fillColor(Color.YELLOW) .strokeColor(Color.GREEN) .zIndex(1.0f) )例2:"ground_overlay" という Drawable 画像を、大阪駅を中心とした1辺300メートルの領域に描画
googleMap.addGroundOverlay( GroundOverlayOptions() .position(LatLng(34.702423, 135.495972), 300.0f) .image(BitmapDescriptorFactory.fromResource(R.drawable.ground_overlay)) .zIndex(2.0f) )なお zIndex プロパティには、図形が重なるときにどちらを前面にするか指定します。
例1と例2を同じアクティビティの地図に描画すると Ground Overlay が前にくるので画像しか見えませんが、zIndex 値を逆にすると、円が画像に乗っかった形で描画されます。マップオブジェクトにインタラクションを設定する
インタラクションを設定したいマップオブジェクトに対応するリスナを作成し、GoogleMap オブジェクトに設定します。リスナについては前項の表を参考にしてください。
クリックの対象とするオブジェクトには、オプションでclickable(true)
をセットしないと反応しないことに注意してください。
また、Marker には Click 以外にもリスナがあります。APIドキュメントで確認してみてください。以下の例では、描画した Circle をタップしたときに『Circle is Tapped!!』とトーストで表示しています。
googleMap.run { addCircle( CircleOptions() .center(LatLng(34.702423, 135.495972)) .radius(20.0) .fillColor(Color.YELLOW) .strokeColor(Color.GREEN) .zIndex(2.0f) .clickable(true) ) setOnCircleClickListener { circle -> Toast.makeText(this@MapsActivity, "Circle is tapped!!", Toast.LENGTH_SHORT).show() } }タップされたのが何か判別する
複数のマップオブジェクトを描画したとき、何がタップされたか判別する方法についてです。
マップオブジェクトは、生成された際に固有のIDを割り当てられます。GoogleMap#addXXXXX の戻り値は描画されたオブジェクトなので、このIDを保持しておいて、リスナの戻り値と照らし合わせるという手段があります。以下の例では、2つの円のどちらかをタップしたときに、IDからタップされた方を判別してトーストを出力しています。
googleMap.run { val firstCircle = addCircle( CircleOptions().center(LatLng(34.702423, 135.495972)).clickable(true) ) val secondCircle = addCircle( CircleOptions().center(LatLng(34.695332, 135.501508)).clickable(true) ) setOnCircleClickListener { circle -> val toastText = when (circle.id) { firstCircle.id -> "First" secondCircle.id -> "Second" else -> "Unknown" } Toast.makeText(this@MapsActivity, "$toastText is tapped!!", Toast.LENGTH_SHORT).show() } }これ以外にも、マップオブジェクトは View と同じく setTag/getTag メソッドが用意されています。
GoogleMap#addXXXXX メソッドでマップオブジェクトを描画したあとに setTag で必要な情報を格納しておいて、クリックリスナで getTag を使うという手もよいかも知れません。まとめ
Maps SDK for Android を使ってアプリに地図を組み込む方法について書いてみました。
Maps SDK はここに書いた以外にも、様々な機能を備えています。以下の2点については特に便利だと感じました。
- マップオブジェクトの Marker をカスタマイズする方法
- Google Maps Android API Utility Library を使って、地図を更に拡張する方法
ただこれも書くと長くなりすぎるので、そのうち別の記事で書きたいと思います。
全て GoogleMap クラスのインターフェイスです。 ↩
- 投稿日:2019-07-28T16:49:39+09:00
Start an Activity from a Notification (日本語訳)
※以下の文章は、Start an Activity from a Notificationの内容を理解するために翻訳したものです。
通知からアクティビティを開始するときは、ユーザーが期待するナビゲーション機能を維持する必要があります。戻るをタップすると、ユーザーはアプリの通常のワークフローからホーム画面に戻ります。最近使った画面を開くと、アクティビティが別のタスクとして表示されます。このナビゲーション体験を維持するためには、新しいタスクでアクティビティを開始する必要があります。
通知のタップ動作を設定する基本的な方法は通知の作成で説明されていますが、このページでは通知のアクションに対してPendingIntentを設定して新しいタスクとバックスタックを作成する方法について説明します。しかし、これをどのように行うかは、開始しているアクティビティの種類によって異なります。
通常のアクティビティ
これは、アプリの通常のUXフローの一部として存在するアクティビティです。そのため、ユーザーが通知からアクティビティに到着したときに、新しいタスクに完全なバックスタックを含める必要があり、戻るを押してアプリ階層を上に移動できるようにします。特別なアクティビティ
ユーザーがこのアクティビティを通知から開始した場合にのみ表示されます。ある意味では、このアクティビティは通知自体に表示するのが難しい情報を提供することによって通知UIを拡張します。したがって、このアクティビティはバックスタックを必要としません。通常のアクティビティのPendingIntentを設定する
通知から「通常のアクティビティ」を開始するには、TaskStackBuilderを使用してPendingIntentを設定し、次のように新しいバックスタックを作成します。
アプリのアクティビティ階層を定義する
アプリのマニフェストファイルの各<activity>要素にandroid:parentActivityName属性を追加して、アクティビティの自然な階層を定義します。
<activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- MainActivityはResultActivityの親です --> <activity android:name=".ResultActivity" android:parentActivityName=".MainActivity" /> ... </activity>バックスタックでPendingIntentを構築する
バックスタックのアクティビティを含むアクティビティを開始するには、TaskStackBuilderのインスタンスを作成し、addNextIntentWithParentStack()を呼び出して、開始するアクティビティのIntentを渡す必要があります。
上記のように各アクティビティの親アクティビティを定義していれば、getPendingIntent()を呼び出してバックスタック全体を含むPendingIntentを受け取ることができます。
// 開始したいアクティビティのインテントを作成します。 Intent resultIntent = new Intent(this, ResultActivity.class); // TaskStackBuilderを作成し、バックスタックを膨張させるIntentを追加します。 TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); stackBuilder.addNextIntentWithParentStack(resultIntent); // バックスタック全体を含むPendingIntentを取得します。 PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);必要に応じて、TaskStackBuilder.editIntentAt()を呼び出して、スタック内のIntentオブジェクトに引数を追加できます。これは、ユーザーがそれまでナビゲートしたときに、バックスタック内のアクティビティが意味のあるデータを表示するようにするために必要な場合があります。
それから、いつものようにPendingIntentを通知に渡すことができます。
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID); builder.setContentIntent(resultPendingIntent); ... NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(NOTIFICATION_ID, builder.build());特別なアクティビティのPendingIntentを設定する
通知から開始される「特別なアクティビティ」はバックスタックを必要としないため、getActivity()を呼び出してPendingIntentを作成できますが、マニフェストで適切なタスクオプションを定義したことも確認する必要があります。
1.マニフェストで、<activity>要素に次の属性を追加します。
android:taskAffinity=""
コードで使用するFLAG_ACTIVITY_NEW_TASKフラグと組み合わせると、この属性を空白に設定することで、このアクティビティがアプリのデフォルトタスクに入らないようにすることができます。アプリのデフォルトのアフィニティを持つ既存のタスクは影響を受けません。android:excludeFromRecents="true"
ユーザーが誤ってそのタスクに戻ることができないように、最近使ったアプリから新しいタスクを除外します。
例えば:<activity android:name=".ResultActivity" android:launchMode="singleTask" android:taskAffinity="" android:excludeFromRecents="true"> </activity>2.通知を作成して発行します。
a. Activityを開始するIntentを作成します。
b. フラグFLAG_ACTIVITY_NEW_TASKおよびFLAG_ACTIVITY_CLEAR_TASKを指定してsetFlags()を呼び出して、新しい空のタスクで開始するようにActivityを設定します。
c. getActivity()を呼び出してPendingIntentを作成します。例えば:
Intent notifyIntent = new Intent(this, ResultActivity.class); // 新しい空のタスクで開始するようにアクティビティを設定します。 notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); // PendingIntentを作成します。 PendingIntent notifyPendingIntent = PendingIntent.getActivity( this, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT );3.それから、いつものようにPendingIntentを通知に渡すことができます。
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID); builder.setContentIntent(notifyPendingIntent); ... NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(NOTIFICATION_ID, builder.build());さまざまなタスクオプションとバックスタックの仕組みについての詳細は、タスクとバックスタックを読んでください。 通知を使用するサンプルコードについては、Android Notifications Sampleを参照してください。
- 投稿日:2019-07-28T16:15:02+09:00
今更だけど Android Instant Apps を試してみた (unity)
前提
- Google Play Instant と Unity でインスタント ゲームを作成する
- unity 2018.4.4f1
- android studio 3.4.2
- Google Play Instant Development SDK 1.7.0
Android Instant Appsってなんだろう?
- アプリのサブセットをインストールせずに「今すぐ試す」ものです。
- ストアに「今すぐ試す」ボタンが付きます。
- 有料アプリの無料体験版にできます。
- 「Install版がストアにない状態」=「Instant版だけ」では使えません。
- アプリ中にインストールボタンを付けられるので、インストールの導線になります。
- 「アプリを起動するリンク」を作ることもできます。
- Webアプリのような感じで使えます。
- ただし、端末側で許可設定が必要です。
※あくまでも私の理解です。
準備
- 基本的には、上記のGoogleの記事に従います。
- play-instant-unity-plugin をプロジェクトに導入します。
- android studio に Google Play Instant Development SDK を導入します。
できること
- ビルド自体は、公式記事通りに進めれば、容易にできます。
- サイズ制限がある以外に違いはありませんし、テストする分にはサイズ制限もありません。
- PlayerSettings以外は、何も変えずともビルドできます。
- InstantとInstallを切り替えてビルドできます。
PLAY_INSTANT
というシンボルを使って、条件コンパイルできます。- InstantAppを(実/仮想)デバイスに送って起動できます。
- コンソールへアップロード可能なInstant版APKが作れます。
- 容量制限がありますが、InstantからInstallへデータを引き継げます。(CookieApi)
- アプリサイズ制限をオーバーしていても、テストトラックで試すことはできます。
できないこと
- InstantとInstallでPlayerSettingsを切り替えてくれません。
- Instantでは、いくつかのPlayerSettingsが強制されます。
- プロジェクトやブランチを別にするなどの工夫が必要です。
- エディタ環境では、InstantからInstallへのデータ引き継ぎができません。
try {CookieApiで保存/取得} catch {PlayerPrefsで保存/取得}
として、エディタ上でシミュレートしました。- Android App Bundleをサポートしていません。
- もちろん、InstantとInstallの混成aabを作ることもできません。
- これのせいで、サイズ制限を超えられませんでした。
留意点
- サイズを減らすために、複数のPlayerSettingsに対して更新が提示されます。
- いくつかは強制のようで、更新しないとビルドできません。
- 更新しなくてもビルドできるものもありました。
- 推奨通りに更新されたものを元に戻したい場合は、PlayerSettingsで個別に直すことになります。
- ビルドタイプをInstallに切り替えても、元に戻りません。
- InstallとInstantで設定を別にしたい場合は、プロジェクトやブランチを別にすることになるでしょう。
- Install版は、Instant版より大きなビルド番号でなければなりません。
- ツール側で混成aabがサポートされれば、同じビルド番号でも良いものと思われます。
- InstantからInstallへのデータ引き継ぎ(CookieApi)には、容量制限があります。
- 保存可能なサイズは
CookieApi.GetInstantAppCookieMaxSizeBytes ()
で取得可能です。- テスト機のGoogle設定で、「バックアップ」⇒「アプリのデータ」の「自動復元」を切っておきましょう。
- インストール直後にデータが復活するので、データの引き継ぎが阻害されます。
結論
- aabの生成がサポートされるまでは厳しいです。
- 投稿日:2019-07-28T15:59:32+09:00
【Kotlin】LocalDateTimeで2つの日付を比較し、日数の差分を取得する
はじめに
日付/時間を扱うAPIには
- 日付のみを保持する
LocalDate
- 時刻のみを保持する
LocalTime
- 日付と時刻の両方を保持する
LocalDateTime
があり、やりたいことに合わせて適切なAPIを使うわけですが、
日数の差分を取得するためには、LocalDateTime
で日付を比較する方法がスマートだと感じたので、ここにメモしておきます。※なお、「日付」の比較ということで当初は
LocalDate
のcompareTo
メソッドを使っていたのですが、下記記事に書いた通り、意図しない結果になることがありました。【Kotlin】LocalDateで2つの日付を比較し、日数の差分を取得する - Qiita
【Android】LocalDate#compareToで日付を比較する際の注意点 - Qiitaコード
非常にシンプルで、
ChronoUnit.DAYS.between
を使うことで日数の差分が取得できます。
(LocalDateTime
とChronoUnit
はThreeTenABPライブラリのものを使っています。)val now = LocalDateTime.now() //2019-07-28T15:31:59.754 val day1 = LocalDateTime.parse("2019-08-28T10:15:30.123") val diff = ChronoUnit.DAYS.between(now, day1) // diff: 30
ChronoUnit
とは
ChronoUnit
は日付、時間などを操作するための「単位」を集めた列挙型です。
1日の概念を表すDAYS
の他にも、1時間の概念を表すHOURS
、1マイクロ秒の概念を表すMICROS
、1世紀の概念を表すCENTURIES
などなど、多くの単位が定義されています。詳細:ChronoUnit (Java Platform SE 8)
参考
- 投稿日:2019-07-28T14:37:14+09:00
Android端末にインストールされた認証局でサーバ証明書を検証する
目的
タイトル通りだが、方法を見つけるのに苦労したので記載しておく。
これを実装すると、自前のRoot認証局が署名したサーバ証明書でも検証してくれる。
自前のRoot認証局のインストール方法は、「設定」>「セキュリティ」>「SDカードからインストール」をタップし、証明書(PEM形式でよい)を選択する。インストールに成功すると、「設定」>「セキュリティ」>「信頼できる認証情報」の「ユーザー」タブに選択した証明書が表示される。実装
fun connect() { // 端末にインストールされている「信頼できる認証情報」ストレージを取得する val caStore = KeyStore.getInstance("AndroidCAStore").apply { load(null) } // デバッグ用: 「信頼できる認証情報」をログ出力する caStore.aliases().toList().forEach { alias -> Log.d("----", "alias=${alias}, certificate=${caStore.getCertificate(alias)}") } // SSLハンドシェイク時に受信したサーバ証明書の署名者を、検証してくれるTrustManagerを生成するオブジェクトを作成する val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { init(caStore) } // 上で作成したTrustManagerを使う SSLContext を作成する val context: SSLContext = SSLContext.getInstance("TLS").apply { init(null, trustManagerFactory.trustManagers, null) } // HTTPS 通信を行う val url = URL("https://example.com/index.html") val urlConnection = (url.openConnection() as HttpsURLConnection).apply { sslSocketFactory = context.socketFactory } // デバッグ用: 通信結果をログ出力する val inputStream = urlConnection.inputStream Log.d("----", InputStreamReader(inputStream).readText()) }注意点
端末にRoot認証局をインストールし、サーバ証明書を中間認証局(Root認証局で署名)で署名しているにもかかわらず、検証に失敗する場合がある。その時は、下記のように自前のX509TrustManagerを実装し、サーバから受信する証明書チェーンを確認する。
fun connect() { val context: SSLContext = SSLContext.getInstance("TLS").apply { // init(null, trustManagerFactory.trustManagers, null) init( null, arrayOf(object : X509TrustManager { override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) { } override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) { // デバッグ用: サーバから受信した証明書チェーンをログ出力する Log.d("----", "chain=${Arrays.toString(chain)}") } override fun getAcceptedIssuers(): Array<X509Certificate> { return emptyArray() } }), null ) } // HTTPS 通信を行う val url = URL("https://example.com/index.html") val urlConnection = (url.openConnection() as HttpsURLConnection).apply { sslSocketFactory = context.socketFactory } // デバッグ用: 通信結果をログ出力する val inputStream = urlConnection.inputStream Log.d("----", InputStreamReader(inputStream).readText()) }
- 投稿日:2019-07-28T14:31:39+09:00
AndroidのWebViewでAutofillを無効化する
はじめに
自前のWebViewを実装するときにHTMLのフォーム入力でAutofill表示しないようにしたかったときのメモ。
検証環境
* Pixel3
* Android 9解決策
WebViewの
importantForAutofill
にIMPORTANT_FOR_AUTOFILL_NO
を指定する(Android O / API 26以上の場合のみ)。webView.apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO } }
- 投稿日:2019-07-28T09:24:08+09:00
【Android】LocalDate#compareToで日付を比較する際の注意点
以前、下の記事で、日付を比較する方法として
LocalDate#compareTo
を挙げました。
【Kotlin】日付を比較し、その差分を取得する - Qiita
しかし、日数の差分を取得する目的で利用しようとすると、場合によっては期待結果と異なることわかったので備忘録として書いておきます。
LocalDate#compareTo
の中身を確認する実際に比較が走っている箇所はこちらです↓
LocalDateint compareTo0(LocalDate otherDate) { int cmp = this.year - otherDate.year; if (cmp == 0) { cmp = this.month - otherDate.month; if (cmp == 0) { cmp = this.day - otherDate.day; } } return cmp; }
- まず
year
を比較する- その差がなければ
month
を比較する- さらに差がなければ
day
を比較するという流れになっています。
つまり、日数の差分を比較・取得できるのは年数・月数が同じ場合に限定され、それ以外の場合は意図した結果とならないことがわかります。実際に比較してみる
同じ年、同じ月、違う日の場合
val day1 = LocalDate.parse("2019-07-01") val day2 = LocalDate.parse("2019-07-05") val diffDay = day2.compareTo(day1) // diffDay: 4→「日数の差分」が取得できます。
同じ年、違う月、違う日の場合
val day1 = LocalDate.parse("2019-07-01") val day2 = LocalDate.parse("2019-08-05") val diffMonth = day2.compareTo(day1) // diffMonth: 1→月の比較が行われるため、「日数の差分」は取得できません。
違う年、違う月、違う日の場合
val day1 = LocalDate.parse("2017-07-01") val day2 = LocalDate.parse("2019-08-05") val diffYear = day2.compareTo(day1) // diffYear: 2→年の比較が行われるため、「日数の差分」は取得できません。
- 投稿日:2019-07-28T09:20:07+09:00
【Programming News】Qiitaまとめ記事 Weekly July 4rd week, 2019 Vol.2
筆者が昨日2019/7/21(日)~7/27(土)に気になったQiitaの記事のまとめのまとめを作成しました。日々のまとめ記事のWeekly版です。
皆様が興味のある言語や思いもよらぬハック方法をこの中から見つけられたら幸いです。
Java
- Tips
- Spring
- Spring Boot
- Performance
- データ分析
Python
- Beginner
- Tips
- おじさん、Pythonでスクリプトを作成したい
- Pythonに組み込み関数「メガンテ」を追加する
- anaconda環境を壊さないようにpipを使う
- CSV形式をJSON形式に変換するPythonプログラム
- QRコード作成 pythonライブラリ python-qrcode を読む
- .NET (on Windows)からpythonを呼び出す
- Pythonの各種ライブラリ一覧
- python-cgiで超シンプルな画面遷移を書く
- Pythonでyaml形式の設定ファイルを読み込んでloggingを使ってみた
- 言語処理100本ノックをNLP屋がやってみる: 第5章 40~45
- Pythonで媒介変数表示された平面曲線の曲率を求める
- python3で切り下げ,切り上げ,四捨五入,だけのためにライブラリをimportしたくない
- 48日目。pandasで10万×1万のCSVをマージしたら簡単・早くて驚きました!
- python で近似球を作ってみる
- tensorflowで画像処理
- Pythonで表示したウィンドウにテキスト、インプットボックス、ボタンを挿入する
- PythonでAWS S3のフォルダ配下の最新ファイルを取得 & 履歴管理する方法
- pipelineとgridsearchcvを使って前処理から予測値出力の流れをシンプルに実装
- 画像認識で自作データのサイズ加工
- [n番煎じ] 言語処理100本ノック 2015 第3章 with Python
- Python 3のmultiprocessingでプロセス間で大量のデータを受け渡しつつnumpyで処理する
- 言語処理100本ノック第2章を解いてみた
- python ビッグデータを取り扱ったときのメモ
- from csv to json with Python
- Evernoteのサードパーティ用アクセストークンをOAuth認証で取得する
- pythonを使ったデータ探索と回帰【kaggle, EDA,randomforest】
- Qiitaコーパスを作る会 2️⃣前処理をする
- LINEbotのログインURLを出してみる
- OpenCV
- ルールベース
- Django
- Tools
- Apps
Ruby
- Tips
- RSpec
- Tools
Rails
- Beginner
- RailsチュートリアルのためにDockerの環境構築 for Mac…「Yay, you’re on Rails!」の画面が表示されるまで
- 【初学者向け】Railsアプリケーションにカスタムフォントを追加する方法
- <Windows>Ruby on Rails チュートリアルでのつまづきポイントまとめ
- Railsを支えるRESTについて
- WindowsでRuby on Railsの環境構築してみる
- Rails + Selenium + DockerでSystemSpecの環境構築
- Windows10環境にRubyとRailsをインストールしてみた
- Ruby on Rails開発環境を便利にしよう
- 【初心者向け】bundler、Gemfile、Gemfile.lockの関係性について図でまとめてみた
- Tips
- Ruby on Rails でレビュー機能を実装する
- Rails環境にUikitのデザインを適用する方法
- 初心者でもできる!Cloud RunでRailsアプリをデプロイ【Rails/GCP】
- Rails6 のちょい足しな新機能を試す57(Arel is_distinct_from編)
- Rails6 のちょい足しな新機能を試す58(ActiveRecord::Errors#of_kind?編)
- 既存のRuby on RailsプロジェクトにJenkins と Docker で CIを導入する
- Railsで複数DB接続する
- 【Rails 部分テンプレート(render partial)を用いたviewを別コントローラから呼び出す(render template)】
- Railsの日本語化、ヘルパーメソッドで呼べるようにする。
- 本番環境に合わせたアセットパイプライン
- Railsでダミーのユーザーデータをたくさん用意する方法
- RSpec
- Apps
C
Android
- Tips
- DataBinding
- Camera API
Swift
- Tips
- iOSアプリでSwifterを使ってTwitterログインする
- チュートリアルから一歩踏み出したSwiftUIのCustom Viewの作り方ーその2(PreferenceKey編)
- [swift] DateFormatterのインスタンス生成は遅い
- SwiftUIでViewのレイアウトを調整するStackについて調べて見ました
- Status Bar優良記事まとめ[Swift]
- iOSにデータベースを組み込んでみる。最新GRDBの使い方を解説
- 【Swift】AutoLayoutのPriorityを使ってアニメーション風のView制御をする
- Asset Catalogを使って色の管理 Swift
- 【iOS】iOS開発初心者の個人的に参考になったswift記事まとめ
- ブラウザで名前が定義されている140色をSwiftで定義する
Kotlin
- Tips
Flutter
Fulx
- Beginner
JavaScript
- Tips
- ESLint
- Turn.js
- Quark
React
- Beginner
- Tips
- Apps
Node.js
- Tips
- Tools
Vue.js
- Beginner
- Tips
- SentryとVueの組み合わせでイベントログを収集
- VueやNuxtでcodemirrorを使うときにはvue-codemirrorが便利
- Vuex-ORMとvuex-persistedstateの連携で半永続的データベースに!
- Vue.js + Vue.Draggable + CypressでDrag & Dropするテストを書く
- Vue.jsで作ったアプリをHerokuにデプロイ
- Vue.jsでカレンダーコンポーネント作ってみた
- Vue.js+Firebaseプロジェクト作成(Hosting、Authentication、Firestore、Storage、Functions)
- Vue + Quasarで環境を設定する
- Apps
Vuex
Nuxt.js
- Beginner
- Tips
- Apps
Nest.js
Angular
jQuery
- Beginner
TypeScript
- Beginner
- Tips
- Tools
- Apps
ReactNative
Laravel
- Beginner
- Tips
- 【Laravel】InnoDBで二番目以降のキーにAuto Incrementをマイグレーションで設定する
- 手軽なLaravelテストコード (3種類のモック手法)
- Laravelのお困りごと解決リスト
- Laravel 5.8 認証通知メールの多言語化
- Laravelで AWS の LB を通した環境でエンドユーザのIPを取得したい
- IntelliJ+Laravel+Vuejs+Docker+Mysqlの環境構築
- Laravelでいいね機能
- LaravelのControllerで生成・加工したデータをVue.jsのテンプレートファイルで利用する方法
- Laravelでエクセルを生成してS3に送信する方法
C
PHP
- Beginner
- Tips
- Tools
CakePHP
Rust
- Beginner
Go言語
- Beginner
- Tips
R言語
Scala
Unity
- Tips
- Unity ECS
A-Frame
- Beginner
PowerApp
Line
HTML
CSS
- Bulma
Sass
SQL
MySQL
- Beginner
- Tips
PostgreSQL
Oracle
MongoDB
SQL Server
ビッグデータ
Visual Studio Code
- Beginner
- Tips
IntelliJ IDEA
AI
IoC
Git
- Tips
AWS
- Beginner
- Tips
- EC2
- AWS Lambda
- AWS Athena
- AWS CDK
- Aurora
- Network
- ABAP
- AWS Systems Manager
- AWS Application Discovery Service
- Amazon Managed Blockchain
- AWS SageMaker
- AmazonLinux2
- AWS Chatbot
- AWS CodeStar
- AWS IoT
Azure
- Tips
- アーキテクチャ
- Azure AD B2C
- Azure DevOps
- Azure Heat Map
Oracle Cloud
IBM Clod
- Tips
- AutoAI
Active Directory
インフラ
ブロックチェーン
Ethereum
- OpenZeppelin
- ERC-721
セキュリティ
- SSL
機械学習
- Beginner
- Tips
Network
RPA
CI
Docker
- Beginner
- Tips
- Chromebook(ARM64)にLinux + Docker + PHPStormな開発環境を構築する
- JavaFXをDockerで実行するには
- AmazonLinux2のdockerイメージにpythonをインストールしてAWS ECRにアップする方法
- SwaggerをDockerで動かしてみる(環境構築)
- Dockerを使ってGAEにNuxt.jsをデプロイしたい
- Dockerコンテナ初回起動時にLaravelの環境をセットアップする方法を求めて
- Dockerで使っているコンテナを作り直す方法
- GHDLとVunitでVHDLのテスト環境を構築する[Docker版]
- docker expose プラグイン便利そうなので試す
Heroku
VirtualBox
kubernetes
OpenID
OAuth2.0
Elasticsearch
- Beginner
Linux
- Tips
Cent OS
Windows
Google API
Google Apps Script
- Tips
Google Cloud Platform
Google Colaboratory
Google Drive
Firebase
- Beginner
Server Side
CSS
BootStrap
WordPress
Develop
- Beginner
- Tips
- Tools
- Apps
PowerShell
Vim
awk
LaTex
Redmine
UML
- PlantUML
Raspberry
- Beginner
- Tips
- Raspberry PiでPythonファイルを自動起動させる
- QEMU4.0.0 + Raspbian Buster による RaspberryPi のエミュレーション環境構築
- Raspberry Pi Zero WにインストールしたRaspbian BusterにUSB経由でSSH接続する
- Raspberry Piで日本語入力をする
- QEMU4.0.0 + Raspbian Buster による RaspberryPi のエミュレーション環境構築(超シンプル手順バージョン)
- PC使ったRaspberry Pi開発手順
- Raspberry PiをHeadlessでセットアップする
- ラズパイからADと連携したRADIUS認証が必要な無線LANに接続する (WPA2-EAP)
- [Raspberry Piメモ] SSHでログインするまで
- Apps
RPA
- Beginner
IoT
Alexa
- Beginner
Line
SharePoint
VBA
ShellScript
Nim
Emacs
WPF
UI
Ansible
Arduino
Julia
- Tips
- Apps
Coral
- Tips
ionic
QRCode
OCR
資格
- Azure
- AWS認定ソリューションアーキテクト
- Associate Cloud Engineer
転職
更新情報
Kotlin
- Kotlin入門
Android
Java
IDE
- 投稿日:2019-07-28T03:25:22+09:00
AndroidのマルチモジュールでLint結果をDangerでコメントする
Androidアプリ開発をマルチモジュールですすめていた場合、Lintを実行すると各モジュールのbuildフォルダ配下にlintチェック結果が生成されてしまうため、Danger等でプルリクにコメントしていた場合にファイルを1つしか選択できず、特定のモジュールのみのコメントになってしまいます。そのため、各モジュールの結果を1つのファイルにマージしてあげてからDangerにそのファイルを指定してあげる ことで解決させます。
詳しい説明やBitriseでの設定などを ブログ のほうに記載したので、こちらでは方法のみを記載します。
lintの設定
各モジュールで以下のlint設定を行い、lint結果のファイルがプロジェクトのルートの
build-reports
というフォルダに集約されるようにします。build.gradleandroid { lintOptions { xmlReport true xmlOutput rootProject.file("./build-reports/lint-results-${project.getDisplayName()}.xml") } }lintチェック結果のXMLファイルをマージする
Lint実行後に、生成されたXMLファイルを全てマージした新しいXMLファイルを生成します。そのために、以下のRubyスクリプトをプロジェクトのルートで実行します。実行にはnokogiriが必要なのでインストールしておきます。
gem install nokogiri
merge_lint_results.rbrequire 'nokogiri' Dir.chdir('build-reports') new_doc = nil Dir::glob('lint-results-*.xml') do |item| file = File.new(item) if new_doc.nil? new_doc = Nokogiri.XML(file) else doc = Nokogiri.XML(file) issues = doc.search('issue') new_doc.at('issues').add_child(issues) end end File.open('lint-results.xml', 'w') do |file| unless new_doc.nil? file.puts(new_doc.to_xml(indent: 4)) end endruby ./merge_lint_results.rbすると
build-reports
にlint-results.xml
というファイルが生成されます。Dangerでファイルを指定する
あとはDangerfileで生成された
lint-results.xml
を指定してあげます。android_lint.skip_gradle_task = true android_lint.filtering = false android_lint.report_file = "build-reports/lint-results.xml" android_lint.lint(inline_mode: true)
- 投稿日:2019-07-28T02:43:41+09:00
【Programming News】Qiitaまとめ記事 July 27, 2019 Vol.13
筆者が2019/7/27(土)に気になったQiitaの記事をまとめました。昨日のまとめ記事はこちら。
2019/7/15(月)~2019/7/20(土)のWeeklyのまとめのまとめ記事もこちらで公開しております。
Java
- Spring Boot
Python
- Tips
- Tools
Ruby
Rails
- Beginner
- Tips
- RSpec
JavaScript
Node.js
- Tips
Vue.js
- Tips
Android
Swift
PHP
- Tips
A-Frame
- Beginner
Line
MySQL
Azure
AWS
- EC2
- AWS Chatbot
Firebase
- Beginner
TypeScript
Google Apps Script
Go言語
Rust
- Beginner
Julia
ShellScript
Unity
Docker
Develop
- Tips
- Tools
- Apps
Raspberry
- Tips
Heroku
OAuth2.0
Visual Studio Code
IntelliJ IDEA
更新情報
Kotlin
- Kotlin入門
Android
Java
IDE
- 投稿日:2019-07-28T01:44:31+09:00
Navigationを実際に使ってみた時のまとめ
Navigationとは?
画面遷移に関する事をいい感じに実装できる優れもの
以下の3つのコンポーネントを理解する事が重要![]()
- Navigation graph
- 画面遷移を1つのXMLファイルで集中的に管理
- NavHost
- ナビゲーショングラフから目的地を表示する空のコンテナ
- NavController
- ナビゲーション管理
Navigationを使う事の利点
- Fragmentトランザクションの管理
- Up, Backイベント処理
- アニメーションとトランジションのための標準化されたリソースを提供
- DeepLinkの処理
- 最小限の追加作業で、ナビゲーションパネルや下部ナビゲーションなどのナビゲーションUIパターンを含めることができる
- Safe Args
- ViewModelのサポート
Navigation Editor
AndroidStudio 3.3以上の環境でナビゲーションをGUIで操作できる
環境構築
新しく
NavigationSample
としてプロジェクトを作成し
dependenciesはNavigationとSafeArgsをインストールします。rootの
build.gradle
repositories { google() } dependencies { // SafeArgs def nav_version = "2.1.0-alpha06" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" }moduleの
build.gradle
apply plugin: "androidx.navigation.safeargs.kotlin" dependencies { // Navigation def navi_version = "2.1.0-alpha06" implementation "androidx.navigation:navigation-fragment-ktx:$navi_version" implementation "androidx.navigation:navigation-ui-ktx:$navi_version" }
実装
Navigation Graph の作成
AndroidStudioでリソース作成時にリソースタイプを
Navigation
に設定し、
ファイル名を入力して作成
ファイル名はGet started通りにnav_graph.xml
で作成2つのフラグメントの作成
- MainFragment
class MainFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_main, container, false) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) buttonToSecond.setOnClickListener { Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_secondFragment) } } }
- SecondFragment
class SecondFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_second, container, false) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) buttonToMain.setOnClickListener { Navigation.findNavController(it).navigate(R.id.action_secondFragment_to_mainFragment) } } }※ Navigation.findNavController... は以下の様にも書ける
buttonToSub.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_xxx, null))Navigation Editor での設定
「New Destination」をクリックすると既に存在しているFragmentやActivityが
追加できる。またこの画面から新規にFragment等を作成もできる。そして上記の2つのフラグメントを繋ぐように設定した navigation graphがこちら
<?xml version="1.0" encoding="utf-8"?> <navigation 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:id="@+id/nav_graph" app:startDestination="@id/mainFragment"> <fragment android:id="@+id/mainFragment" android:name="slowhand.com.navigationsample.MainFragment" android:label="fragment_main" tools:layout="@layout/fragment_main"> <action android:id="@+id/action_mainFragment_to_secondFragment" app:destination="@id/secondFragment"/> </fragment> <fragment android:id="@+id/secondFragment" android:name="slowhand.com.navigationsample.SecondFragment" android:label="fragment_second" tools:layout="@layout/fragment_second"> <action android:id="@+id/action_secondFragment_to_mainFragment" app:destination="@id/mainFragment"/> </fragment> </navigation>このようになりました。
NavHostFragmentの指定
MainActivityのレイアウトファイルで、NavHostFragmentを追加しています。
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph"/> </androidx.constraintlayout.widget.ConstraintLayout>
app:defaultNavHost="true"
端末の戻るボタンを制御する。
またデフォルトにできるのは通常1つのNavHostのみになります。別Activityへの遷移
NavigationEditorのNew DestinationでActivityも追加できるので、
Activityを追加し、Fragmentからactionを追加してやる![]()
Safe args
Navigation Graphにargumentを追加
<fragment android:id="@+id/secondFragment" android:name="slowhand.com.navigationsample.SecondFragment" android:label="fragment_second" tools:layout="@layout/fragment_second"> <action android:id="@+id/action_secondFragment_to_mainFragment" app:destination="@id/mainFragment"/> <!-- こちら --> <argument android:name="text" android:defaultValue="default" app:argType="string" /> </fragment>argumentを渡す側
buttonToSecond.setOnClickListener { // ↓の様にも書ける // val bundle = bundleOf("text" to "hello") // Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_secondFragment, bundle) Navigation.findNavController(it).navigate( MainFragmentDirections.actionMainFragmentToSecondFragment(text = "hello")) }argumentを受け取る側
textView.text = SecondFragmentArgs.fromBundle(arguments ?: return).text
XXXXFragmentDirections
やXXXXXFragmentArgs
が自動生成される![]()
DialogFragment
ダイアログのNavigationを行う方法です。
先に表示先のダイアログを作成します。class SimpleDialogFragment : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return AlertDialog.Builder(requireActivity()) .setTitle("") .setMessage("") .setPositiveButton("OK") { _, which -> ... } .create() } }Navigation Editorの「New Destination」から作成したDialogFragmentを追加します。
次にDialogに渡すパラメータを追加して行きます。一回ビルドしなおすとパラメータをSafeArgsで受け取れるようになります。
val args = arguments?.let { SimpleDialogFragmentArgs.fromBundle(it) }実際に使う場合は遷移元のFragmentをMainFragmentとすると
Navigation.findNavController(it).navigate( MainFragmentDirections.actionMainFragmentToSimpleDialogFragment(arg = 0))こんな感じで書けます
バッドノウハウ
java.lang.IllegalArgumentException: navigation destination xxxxx.dev:id/action_xxxFragment_to_xxxDialogFragment is unknown to this NavControllerこんなエラーが出た場合はDestinationの繋ぎ方を見直して間違ってないか要確認。
java.lang.IllegalStateException: View xxxx does not have a NavController setDialogからDialog、またはFragmenntへ遷移しようとしていた為。
どうもDialogから他のFragmentへは遷移できなさそうでした。
stackoverflow
googleissue
参考URL
- 投稿日:2019-07-28T01:38:02+09:00
【Programming News】Qiitaまとめ記事 July 26, 2019 Vol.12
遅くなりましたが筆者が2019/7/26(金)に気になったQiitaの記事をまとめました。昨日のまとめ記事はこちら。
2019/7/15(月)~2019/7/20(土)のWeeklyのまとめのまとめ記事もこちらで公開しております。
Python
- Beginner
- Tips
- Apps
JavaScript
Ruby
- RSpec
Android
Swift
Kotlin
Rails
- Beginner
- Tips
React
Nuxt.js
Laravel
Sass
PHP
- Tips
MySQL
Oracle
AWS
- AWS Lambda
- AWS CodeStar
- AWS IoT
- Beginner
Docker
Visual Studio
- Beginner
IBM Cloud
Unity
- Tips
TypeScript
- Tips
- Tools
Raspberry
- Tips
- Apps
Julia
Git
- Tips
Vim
VirtualBox
更新情報
Kotlin
- Kotlin入門
Android
Java
IDE