どうも。どっことです。
タイトルの通りですが、諸事情により GitHubPage に移行予定です。
この備忘録に記載の内容を転記しつつ、今後はこちらのページを更新していく予定です。
アウトプットしないとインプットできない私が Androidアプリ開発をメインとした実装方法の解説や備忘録を載せています。
どうも。どっことです。今回はgradle.properties
を使った設定値の管理方法について紹介します。
gradle.properties
を用意し定数値感覚で設定すれば、アプリのバージョンやtargetVersion
/minSDKVersion
などの値を一つのファイルで管理させることができます。
例えば以下のbuild.gradleにあるマジックナンバーを定数化しgradle.properties
に移して参照しましょう。
android {
namespace = "com.mkt120.sampleapplication"
compileSdk = 35
defaultConfig {
applicationId = "com.mkt120.sampleapplication"
minSdk = 26
targetSdk = 35
versionCode = 100
versionName = "1.0.0"
...
}
...
gradle.properties
に外出しする定数を記述します。
ANDROID_SDK_VERSION=35
ANDROID_MIN_SDK_VERSION=26
ANDROID_VERSION_NAME="1.0.0"
ANDROID_VERSION_CODE=100
上で配置した定数をbuild.gradle
で参照します。
android {
namespace = "com.mkt120.sampleapplication"
compileSdk = Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
defaultConfig {
applicationId = "com.mkt120.sampleapplication"
minSdk = Integer.parseInt(project.ANDROID_MIN_SDK_VERSION)
targetSdk = Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
versionCode = Integer.parseInt(project.ANDROID_VERSION_CODE)
versionName = project.ANDROID_VERSION_NAME
...
}
...
gradle.properties
に記述した定数はproject.XXXXX
の形式で参照することができます。
今回はgradle.properties
を使った設定値の管理方法について紹介しました。
どうも。どっことです。今回はRelativeLayoutについて解説します。
RelativeLayout
はViewを相対的な位置関係で配置するためのViewGroupです。親のViewGroupを基準とした位置や同じRelativeLayout内の他Viewを基準として位置を決めることができ、FrameLayout
やLinearLayout
より柔軟にViewを配置することができます。現在はConstraintLayout
が制約という形でRelativeLayoutよりも難しい配置設定を実現できるようになったことから、RelativeLayoutはレガシーなViewGroupという整理になっていますが、長く開発されてきたアプリの場合RelativeLayoutを使って画面レイアウトを実装しているものも多いと思いますので、理解しておくに越したことはないでしょう。今回はそんなRelativeLayoutの使い方を解説します。
RelativeLayoutを使ううえで、押さえておくべきポイントは以下です。
layout_alignParent***
layout_center***
layout_toStartOf
layout_toEndOf
layout_above
layout_below
layout_align***
順番に解説していきます。
まずはlayout_alignParent**
です。layout_alignParent**
はViewを上下左右のいずれかに寄せる設定です。以下の4種類があります。
layout_alignParentTop
:layout_alignParentBottom
:layout_alignParentStart
(Left
):layout_alignParentEnd
(Right
):
以下はサンプルです。layout_alignParentTop
とlayout_alignParentStart
は設定してもViewが移動しないので、省略します。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="alignParentEnd" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="alignParentBottom" />
</RelativeLayout>
こちらを実装した時のプレビューは以下です。
layout_center**
は上下中央、左右中央、中央にViewを配置するための設定です。以下の3種類があります。
layout_centerVertical
layout_centerHorizontal
layout_centerInParent
以下はサンプルです。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Center\nIn\nParent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Center\nHorizontal" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="Center\nVertical" />
</RelativeLayout>
こちらを実装した時のプレビューが以下です。
ここまでの配置設定は親Viewを基準にして設定するものでしたが、ここからは同じRelativeLayout内にある他のViewに対する設定となります。
まずはlayout_toStartOf
です。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view_target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:text="target" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_toStartOf="@id/text_view_target"
android:text="toStartOf" />
</RelativeLayout>
layout_toStartOf
を設定すると、指定したViewの左と自身の右の位置を揃えてくれます。
次にlayout_toEndOf
です。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view_target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:text="target" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/text_view_target"
android:padding="8dp"
android:text="toStartOf" />
</RelativeLayout>
layout_toEndOf
を設定すると、指定したViewの右と、自身の左の位置を揃えてくれます。
次にlayout_above
です。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view_target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:text="target" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/text_view_target"
android:padding="8dp"
android:text="above" />
</RelativeLayout>
layout_above
を設定すると、指定したViewの下と自身の上の位置を揃えてくれます。
そしてlayout_below
です。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view_target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:text="target" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/text_view_target"
android:padding="8dp"
android:text="below" />
</RelativeLayout>
layout_below
を設定すると、指定したViewの上と自身の下の位置を揃えてくれます。
最後に紹介するのは、layout_align**
です。この設定は指定のViewの端を揃えるものです。以下の4種類があります。
layout_alignTop
:layout_alignBottom
:layout_alignStart
(Left
):layout_alignEnd
(Right
):layout_alignTop
です。指定したViewの上端と揃う位置に配置されます。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view_target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="target" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/text_view_target"
android:padding="4dp"
android:text="alignTop" />
</RelativeLayout>
指定したtargetと上端が揃う位置にViewが配置されます。
次にlayout_alignBottom
です。指定したViewの下端と揃う位置に配置されます。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view_target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="target" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/text_view_target"
android:padding="4dp"
android:textSize="8dp"
android:text="alignBottom" />
</RelativeLayout>
指定したtargetと下端が揃う位置にViewが配置されます。(テキストサイズを小さくしています)
続いてlayout_alignStart
です。指定したViewの左端と揃う位置に配置されます。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view_target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="target" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="@id/text_view_target"
android:padding="4dp"
android:text="alignStart" />
</RelativeLayout>
最後にlayout_alignEnd
です。指定したViewの右端と揃う位置に配置されます。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view_target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="target" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@id/text_view_target"
android:padding="4dp"
android:text="alignEnd" />
</RelativeLayout>
今回はRelativeLayoutについて解説しました。すでにレガシーとなったViewGroupですが、まだまだ利用されているプロジェクトも多いと思うので、それぞれの設定について理解しておきたいですね。
どうも。どっことです。今回はLinearLayoutについて解説します。
LinearLayout
は順番にViewを並べてくれるというかなり便利なViewGroupであり、それでいて利用するために必要な設定が多くないところから今でも利用頻度が高いものとなっています。
今回はそんなLinearLayoutの使い方を解説します。
LinearLayoutを使う上で押さえておくべきポイントは以下です。
orientation
layout_weight
layout_gravity/gravity
順番に解説していきます。
orientation
は、Viewをどの方向に並べるか設定するものです。Viewを順番に並べる機能を持つLinearLayoutの最も重要な設定となります。
設定は縦方向(vertical
)か横方向(horizontal
)のいずれかで、縦方向を設定した場合は上から下に、横方向を設定した場合は左から右にViewが配置されます。
もし画面に収まらない場合は当然はみ出してしまいますが、LinearLayoutにはスクロールする機能がないので、ScrollView
やHorizontalScrollView
の中にLinearLayoutを配置することで画面をスクロール可能にしましょう。
layout_weight
はLinearLayout内に並べたViewの大きさを比率で決定するためのものです。
例えばいくつかのViewを同じ大きさで並べたい場合は、すべてのViewのlayout_weight
を1に設定することで全てのViewの大きさを1:1にすることができます。Viewの大きさを決めるlayout_width
またはlayout_height
を0dpに設定する必要があります。
例えば3つのViewを均等幅(1:1:1)で並べる場合は以下です。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="左"
android:background="@android:color/holo_red_dark"
android:gravity="center" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="中"
android:gravity="center" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="右"
android:background="@android:color/holo_blue_dark"
android:gravity="center" />
</LinearLayout>
このレイアウトのプレビューが以下です。
またlayout_weight
の便利な使い方として、並べたViewのうちの1つだけ画面いっぱいに表示したいケースが挙げられます。
例えば画面内にヘッダ・フッタを持つページの場合です。ヘッダとフッタは固定(または可変長)、コンテンツ領域は残った領域全てとしてViewを配置するケースがほとんどだと思いますが、このようなケースでlayout_weight
が大活躍します。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/darker_gray"
android:gravity="center"
android:text="ヘッダ領域" />
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:text="コンテンツ領域" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/darker_gray"
android:gravity="center"
android:text="フッタ領域" />
</LinearLayout>
この実装をプレビュー表示すると以下になります。
RelativeLayout
やConstraintLayout
でも同様の画面構成を実現できますが、これらを使う場合はヘッダ・フッタ領域にid
を付与する必要があり、それを基準にコンテンツ領域に対して制約をつける必要があります。
layout_gravity
はViewの位置を決める設定です。LinearLayout内のViewに対して設定します。
この設定はFrameLayout
にも同様にありますが、LinearLayout
ではorientation
の設定が反映されつつ、layout_gravity
の設定が反映されます。
例えば、以下のケースを見てみましょう。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_red_dark"
android:layout_gravity="end"
android:text="左" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="中" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_dark"
android:text="右" />
</LinearLayout>
このレイアウトのプレビューは以下になります。LinearLayoutのorientation
の制約が守られつつ、layout_gravity
に合わせた位置にViewが配置されています。
また、LinearLayoutに対してgravity
を設定することもできます。gravity
はView内の要素に対して位置を指定するもので、テキストの改行位置などを決めるためにTextView
でも指定できます。
↑の例にgravityを適用させるとどうなるか見てみましょう。LinearLayoutにgravity:center
を設定します。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<!-- 内部は同じなので省略 -->
</LinearLayout>
これのプレビューは以下になります。
全てのViewが中央寄せになりましたが、個別にlayout_gravity
を設定している左
は右寄せのまま、逆にもともと左寄せになっていた右
はLinearLayoutのgravityの設定により中央寄せに移動しました。
何も設定していなかった右
が中央寄せに移動するのは良いとして、左
が右寄せのままになっているのは、layout_gravity
が親のViewGroup(LinearLayout)のgravity
の設定を上書きしているから、と考えられますね。
今回はLinearLayoutについて解説しました。非常に高頻度に使われるとても使いやすいViewGroupなので、ぜひ使いこなせるようにしておきましょう。
どうも。どっことです。今回はFrameLayoutについて解説します。
FrameLayout
はAndroidViewで使われるViewGroupの一つです。利用頻度はあまり多くありませんが、一時期まではかなり重要な使い道がありました。今回はそんなFrameLayout
の使い方について解説します。
FrameLayout
を使うにあたって押さえておくべきポイントを解説します。
layout_gravity
layout_gravity
はViewをどこに配置するかを設定するものです。LinearLayout
にもこの設定項目はありますが、LinearLayout
がorientation
の設定値にも影響する一方、FrameLayout
ではこれ一つで配置が完結します。
layout_gravity
には以下を設定することができます。|(パイプ)
で繋げることで複数設定することも可能です。
top
bottom
start
end
center_vertical
center_horizontal
center
実装サンプルを載せます。top
とstart
はViewが移動しないので省略します。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="center"
android:textSize="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<TextView
android:text="center\nvertical"
android:textSize="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"/>
<TextView
android:text="center\nhorizontal"
android:textSize="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"/>
<TextView
android:text="end"
android:textSize="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"/>
<TextView
android:text="bottom"
android:textSize="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"/>
</FrameLayout>
このレイアウトのプレビューが以下になります。
前述した通り、FrameLayout
は使いどころが少ないViewGroupです。ただ古いアプリの場合、特にAndroidのバージョンが4.4まではLinearLayout
やRelativeLayout
にforeground
の設定がサポートされていませんでした。その時に唯一foreground
がサポートされていたViewGroupがFrameLayout
でした。Viewをタップした時にオーバーレイする形でViewの色を変えたい時はこのforeground
が唯一の解決方法ですので、そのような表示をするためにFrameLayout
が利用されていたというわけです。
今回はFrameLayout
の使い方について解説しました。「いまさら聞けないFrameLayout」と名打ちましたが、押さえておくべきポイントは他のViewGroupより少ないので、いざ使うことになったとしても困ることは多くないでしょう。
どうも。どっことです。今回はAndroidのウィジェット機能の実装方法について解説します。
iOSと大きく違うところとして、Androidはウィジェット機能が挙げられると思います。アプリを起動しなくても、必要な機能だけ切り出したコンポーネントをホーム画面に配置することで、ユーザが必要なサービスにダイレクトにアクセスできるようになり、ユーザにより良い体験を提供することができます。
今回は、そんなウィジェット機能の実装方法について解説します。
実装手順は以下の通りです。
AppWidgetProvider
のサブクラスを作成するAndroidManifest.xml
に追記する順番に解説していきます。
後続の処理を円滑に進めるため、まずはウィジェットとして表示するレイアウトファイルを追加します。これは通常のレイアウトファイルと同様res/layout
フォルダに配置します。ここではwidget_sample_view.xml
として以下を配置します。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="Button"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
アプリがウィジェット機能を具備していることをOS側に検知してもらうため、設定ファイルを作成します。作成したファイルは(普段使うことがあまりない)res/xml
フォルダに配置します。ここではwidget_provider.xml
として以下を配置します。
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_sample_view"
android:minHeight="100dp"
android:minWidth="200dp"
android:updatePeriodMillis="1000000" >
</appwidget-provider>
AppWidgetProvider
を継承したカスタムクラスを作成します。今回はSampleWidgetProvider
として以下を作成します。
class SampleWidgetProvider : AppWidgetProvider() {
override fun onUpdate(
context: Context?,
appWidgetManager: AppWidgetManager?,
appWidgetIds: IntArray?
) {
context ?: return
val remoteViews = RemoteViews(context.packageName, R.layout.widget_sample_view)
val widget = ComponentName(context, SampleWidgetProvider::class.java)
appWidgetManager?.updateAppWidget(widget, remoteViews)
}
}
AndroidManifest.xml
に前項までに追加した設定ファイルとクラスを追記します。receivert
タグでSampleWidgetProvider
を追加し、Widgetのアップデート通知を受け取るためのintent-filter
、前項で作成した設定ファイルをmeta-data
としてタグ内に設定します。
<application
...>
<receiver android:name=".SampleWidgetProvider"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_provider" />
</receiver>
</application>
ここまで実装すれば、実装したウィジェットをホーム画面に配置することができます。
ウィジェット内にクリックリスナーを設定する場合、通常のクリックリスナーと異なり実装できることはIntent
を発火させるだけになります。このIntentをActivity
やService
、BroadcastReceiver
で受け取り、必要な処理をそちらで実施します。画面を表示したいならActivity
、画面を表示せず必要な情報を更新したいなどであればService
、BroadcastReceiver
が通知先になります。
以下の例は、SampleActivityを起動する場合(何らかの画面を表示するケース)です。RemoteViews.setPendingIntent
を使って、クリックリスナーを設定したいViewのidと、そのViewがタップされたときに発火するIntent(PendingIntent)を渡します。
class SampleWidgetProvider : AppWidgetProvider() {
override fun onUpdate(
context: Context?,
appWidgetManager: AppWidgetManager?,
appWidgetIds: IntArray?
) {
context ?: return
val remoteViews = RemoteViews(context.packageName, R.layout.widget_sample_view)
// ボタンがタップされた時のintent(PendingIntent)を設定
remoteViews.setOnClickPendingIntent(R.id.button, createPendingIntent(context))
val widget = ComponentName(context, SampleWidgetProvider::class.java)
appWidgetManager?.updateAppWidget(widget, remoteViews)
}
private fun createPendingIntent(context: Context): PendingIntent {
// SampleActivityを通知先として指定
val intent = Intent(context, SampleActivity::class.java)
// 通知先がActivityなので、PendingIntent.getActivityでPendingIntentを生成。
// requestCodeやflagは任意の値
return PendingIntent.getActivity(context, 100, intent, PendingIntent.FLAG_IMMUTABLE)
}
}
今回はAndroidのウィジェット機能の実装方法について解説しました。開発したアプリを利用してもらうための導線としてもウィジェットは機能するので、利用する際は参考にしていただけると幸いです。
どうも。どっことです。今回はKotlinCoroutineのテーマです。
Kotlin Coroutine
は非同期処理などのコールバック地獄を解消してくれたり、可読性の高いコードを実装できるようになる便利な機能ですが、「実際のところ、どう使うのが良いの?」と考えました。今回はそんな使うと便利だけど実際どう使うかイマイチ分からない人向けに、ベストプラクティスのページの内容を簡単に紹介したいと思います。
ViewModel
を実装してviewModelScope.launch
/View側ではlifecycleScope.launch
を使うのがよさそうです。
Google開発者サイトでコルーチンに関するベストプラクティスが紹介されています。コルーチンにフォーカスしたものだと、以下が挙げられています。
Dispatcher
を外から注入できるようにするViewModel
でコルーチンを作成するGlobalScope
は使わない
withContext
などで引数に設定するDispatcher(どのスレッドで処理するかの指定)は、ハードコードせず外から注入できる形にした方が良いです。テストコードを実装するときにテストが容易にできるようになります。
// OK:処理するDispatcherを外から指定する
class SampleRepository(private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
suspend fun veryHeavyTask() = withContext(ioDispatcher) {
// very heavy task
}
}
class SampleRepository() {
// NG: Dispatcherをハードコード(直接指定)しない
suspend fun veryHeavyTask() = withContext(Dispatchers.IO) {
// very heavy task
}
}
ViewModel
を作成し、そこでコルーチンを作成→非同期処理を実装するのがよいとのことです。
そして画面側に非同期処理が必要なことを意識させることがないよう、suspend関数として公開しないよう実装するのが良いとのことです。
class SampleViewModel(): ViewModel() {
private val repository = SomeRepository()
// NG:UI側にsuspend関数(=非同期処理)として公開しない
suspend fun badSample() = repository.veryHeavyTask()
// OK:ViewModel内でコルーチンを生成し非同期処理を実行する
fun sample() {
viewModelScope.launch {
repository.veryHeavyTask()
}
}
}
テストコードの実装しにくさ、GlobalScope
をハードコードしていることによりDispatcher
もハードコードされやすくなる点などから、GlobalScope
を利用しないことを推奨しています。
class SampleRepository() {
fun veryHeavyTask() {
// NG:GlobalScopeをハードコードしない
GlobalScope.launch {
// very heavy task
}
}
}
GlobalScope
の利用が正当であるケースはかなり稀で、アプリが生存期間中にアクティブな状態を維持しなければいけない状態とのことです。また、現在のスコープより長い生存期間が必要な処理であれば、CoroutineScope
を外部から指定するような構成とすることを検討すべきとのことです。
コルーチンはキャンセル可能ですが、実際コルーチン内でキャンセルされたか確認したり、停止したりするまではコルーチンはキャンセルされないとのことです。ensureActive
を使用することでキャンセルの確認ができます。
class SampleViewModel(): ViewModel() {
private val repository = SampleRepository()
fun sample(value: Int) {
viewModelScope.launch {
val result = repository.validateValue(value)
// キャンセルできるタイミングでアクティブか確認
ensureActive()
if (result) {
repository.submit(value)
}
}
}
}
今回はKotlinCoroutineのベストプラクティスについて紹介しました。より詳しい内容は参考に記載したGoogleのベストプラクティスの記事からさまざまなページにアクセスできるので、ぜひ参考にしてください。
どうも。どっことです。今回はBottomSheetDialogFragment
について解説します。
BottomSheetDialogFragment
は通常のダイアログから派生したクラスで、下からスライドインで生えてくるような見え方が印象的です。最初に表示するダイアログの高さや、スクロールの制御もでき、通常のダイアログよりカスタマイズ性が高い点も特徴的です。
最小構成で実装する場合は以下の手順です。
BottomSheetDialogFragment
用のstyle
を追加する。
BottomSheetDialogFragment
のサブクラスを実装する。
順番に解説していきます。
<style name="BottomSheetDialogTheme" parent="@style/Theme.Design.Light.BottomSheetDialog"/>
BottomSheetDialogThemeというstyleを追加します。
BottomSheetDialogFragment
を継承したCustomBottomSheetDialogFragment
クラスを追加します。
class CustomBottomSheetDialogFragment :
BottomSheetDialogFragment(R.layout.fragment_bottom_sheet_dialog) {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return BottomSheetDialog(requireContext(), R.style.BottomSheetDialogTheme)
}
}
ダイアログ内で表示するためのコンテンツもレイアウトファイル(fragment_bottom_sheet_dialog.xml
)として追加しておきましょう。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:gravity="center"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="100dp"
android:text="hello_world!!"/>
</FrameLayout>
これだけ実装すれば、BottomSheetDialogFragmentを表示することができます。
class MainActivity : AppCompatActivity() {
...
fun showDialog() {
val dialog = CustomBottomSheetDialogFragment()
dialog.show(supportFragmentManager, null)
}
}
実際に表示すると、以下のようになります。
styleをカスタマイズします。
<style name="BottomSheetDialogTheme" parent="@style/Theme.Design.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
</style>
windowIsFloating:false
を設定することで、以下のようにナビゲーションバーの表示が不透明になり比較的自然な表示になります。
さらにダイアログとナビゲーションバーの表示を同じにしたい場合は、以下のようにBottomSheet
というスタイルを追加し、BottomSheetDialogTheme
のスタイルに追加してください。
<style name="BottomSheet" parent="@style/Widget.Design.BottomSheet.Modal">
<item name="android:background">@android:color/darker_gray</item>
</style>
<style name="BottomSheetDialogTheme" parent="@style/Theme.Design.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:navigationBarColor">@android:color/darker_gray</item>
<item name="bottomSheetStyle">@style/BottomSheet</item>
</style>
ダイアログの背景色を指定したい場合は BottomSheet
スタイルの
背景色(background
)を変更して下さい。このときBottomSheetDialogTheme
のnavigationBarColor
を同じ色になるように注意してください。
いくつか注意が必要です。
まず、BottomSheetDialogFragmentの呼び出し元のレイアウトがCoordinatorLayout
である必要があります。これは、高さ調整をするためのBottomSheetBehavior
クラスの制約となります。
ここではActivityがダイアログを表示するサンプルなので、Activityのレイアウトを調整します。
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ダイアログを表示"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
次に、ダイアログが表示されるタイミングでBottomSheetBehaviorを生成しますが、この時に渡すViewをcom.google.android.material.R.id.design_bottom_sheet
でfindViewByIdで参照する必要があります。これもBottomSheetBehaviorクラスの制約です。
class CustomBottomSheetDialogFragment :
BottomSheetDialogFragment(R.layout.fragment_bottom_sheet_dialog) {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return BottomSheetDialog(requireContext(), R.style.BottomSheetDialogTheme)
.also { dialog ->
dialog.setOnShowListener {
dialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
?.let {
BottomSheetBehavior.from(it).also {
// 後述
}
}
}
}
}
}
Viewを参照するIDがSDK側のものなので、正直あまりいい実装とは言えないという所感です。より適切な実装がみつかりましたら、この記事を更新しようと思います。
ここまでを調整した上で、生成するBottomSheetBehaviorに対してpeekHeight
やstate
を設定することで、最初に表示されるダイアログの高さを調整することができます。
class CustomBottomSheetDialogFragment :
BottomSheetDialogFragment(R.layout.fragment_bottom_sheet_dialog) {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return BottomSheetDialog(requireContext(), R.style.BottomSheetDialogTheme)
.also { dialog ->
dialog.setOnShowListener {
dialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
?.let {
BottomSheetBehavior.from(it).also {
it.peekHeight =
resources.getDimensionPixelSize(R.dimen.bottom_sheet_dialog_initial_height)
it.state = BottomSheetBehavior.STATE_COLLAPSED
}
}
}
}
}
}
peekHeight
には最初に表示する高さを、state
にはBottomSheetBehavior.STATE_COLLAPSED
を指定してください。
今回はBottomSheetDialogFragment
について解説しました。通常のDialogFragmentより実装が必要なポイントが多いですが、見せ方のカスタマイズできる点や元の画面表示の邪魔をしない点では、ユーザに優しい表示方法の一つと言えます。使い所を意識して使いこなせるようになりたいですね。
どうも。どっことです。今回はCustomTab(ChromeCustomTab)の使い方を紹介します。
CustomTab
はアプリ内で利用できるブラウザ機能です。CustomTabが登場する前はWebView
という機能がアプリでのブラウザとしての役目を果たしておりましたが、セキュリティや表示速度などの観点から現在はCustomTabを使うことが推奨されています。
実装手順は以下の通りです。
app/build.gradle
に依存関係を追加する。CustomTabIntent.Builder
を使ってCustomTabIntent
を生成する。
CustomTabIntent.launchUrl
を実行する。順番に解説していきます。
まずはapp/build.gradle
に依存関係を追加します。
dependencies {
// 既存にある dependencies に以下を追加する
implementation "androidx.browser:browser:1.8.0"
}
次にCustomTabIntent.Builder
を使ってCustomTabIntent
を生成します。
val builder = CustomTabsIntent.Builder()
val customTabIntent = builder.build()
最後にCustomTabIntentにあるlaunchUrl
関数を使って、アクセスしたいWebページを表示します。
customTabIntent.launchUrl(this@MainActivity, Uri.parse("https://www.google.co.jp/"))
CustomTabIntent.Builder
に対して、パラメータを設定することでカスタマイズすることができます。今回は参考欄に詳細の項目のページを記載する程度で割愛。
現在2025/4時点ではまだalpha版ですが、AuthTabIntent
が将来的に利用できるようです。
良くある認証フローでは、認証が完了したタイミングでアプリに戻るためのリダイレクトURLを発火してもらうことが一般的ですが、そのハンドリングの一部をAuthTabIntent
で制御してくれるようです。
activityResultLauncher
を利用すれば実現できます。参考はこちら。
ただ前述したように、CustomTabIntent
にlaunchUrl
のインターフェースが実装されている点から、これは推奨される利用方法ではない印象です。
val intent = customTabIntent.intent.also {
it.setData(Uri.parse("https://www.google.co.jp/"))
}
// registerForActivityResult で生成したactivityResultLauncherを使って呼び出し
launcher.launch(intent)
今回はCustomTabについて解説しました。WebViewよりも優れている点が多く、シンプルに使う分には簡単に実装できるためぜひ活用してみてください。
どうも。どっことです。今回はAdmobを利用するためのUMP(UserMessagingPlatform
)ライブラリについて解説します。
Androidアプリ内に広告を表示するためには、Admobライブラリを使って広告表示用のViewを組み込む必要がありますが、それと合わせてユーザに許諾を得る処理も必要です。
今回はユーザに許諾を得るためのUMP(UserMessagingPlatform
)ライブラリの使い方について解説します。
実装の手順は以下の通りです。
app/build.gradle
のdependencies
に依存関係を追加
AndroidManifest.xml
にmeta-data
を追加順番に解説していきます。
app/build.gradle
のdependencies
に依存関係を追加します。
dependencies {
...
// 既存のdependenciesスコープに以下を追加
implementation("com.google.android.ump:user-messaging-platform:2.0.0")
}
AndroidManifest.xml
にmeta-data
を追加します。value
に設定する値はAdmobのページから取得してください。
<!--Sample app ID: ca-app-pub-3940256099942544~3347511713-->
<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>
ここからは広告を表示するにあたってユーザから許諾を得るための処理を実装します。
UserMessagingPlatform.getConsentInformation
でconsentInformation
を取得します。
取得したconsentInformation
に対して同意情報を更新します。その後、処理終了が非同期に通知されます。
val params = ConsentRequestParameters.Builder()
.setTagForUnderAgeOfConsent(false)
.build()
val consentInformation = UserMessagingPlatform.getConsentInformation(activity)
consentInformation.requestConsentInfoUpdate(activity, params, {
// 処理終了。後述。
}, { error ->
// Handle the error.
})
処理終了の通知を受けてフォームの利用可否をチェックし、可能であればフォーム更新処理に進みます。
val params = ConsentRequestParameters.Builder()
.setTagForUnderAgeOfConsent(false)
.build()
val consentInformation = UserMessagingPlatform.getConsentInformation(activity)
consentInformation.requestConsentInfoUpdate(activity, params, {
// 処理終了。
if (consentInformation.isConsentFormAvailable) {
// 同意フォーム利用可能。
// 更新処理へ。後述。
loadForm(activity, consentInformation)
} else {
// 同意フォーム利用不可。
// 設定、フォーム更新中など。
// 再更新を促すのが無難。
}
}, { error ->
// Handle the error.
})
UserMessagingPlatform.loadConsentForm
で同意フォームを更新します。これはメインスレッドで実施するよう、サンプルにコメントされています。
更新処理が終了すると非同期に通知されるので、それを契機に許諾状態を確認しましょう。その結果、未許諾(REQUIRED
)であればフォームオブジェクトに表示を依頼します。これにより、ユーザに同意フォームが表示されるはずです。
ユーザ操作を契機にフォームが非表示になったことがアプリに通知されますので、これを契機に許諾状態を再度取得してください。これでユーザに許諾してもらえていれば、晴れてアプリ内で広告表示可能という状態になります。
private fun loadForm(consentInformation: ConsentInformation) {
// Loads a consent form. Must be called on the main thread.
UserMessagingPlatform.loadConsentForm(activity, { consentForm ->
if (consentInformation.consentStatus == ConsentInformation.ConsentStatus.REQUIRED) {
// 同意フォームを表示しユーザに許諾を得る。
consentForm.show(activity) {
if (consentInformation.consentStatus == ConsentInformation.ConsentStatus.OBTAINED) {
// 無事にユーザから許諾を得られた!
} else {
// 同意を得られなかった...
// 同意をもらえなかったことによる制御へ
}
}
}
}, { error ->
// Handle the error.
})
}
今回はAdmobを利用するための UMPライブラリについて解説しました。広告をアプリ内に組み込むためにはAdmobによる広告表示処理と合わせてユーザに同意を得る処理も必要不可欠ですので、参考にしていただけると幸いです。
どうも。どっことです。 タイトルの通りですが、諸事情により GitHubPage に移行予定です。 https://mkt120.github.io/ この備忘録に記載の内容を転記しつつ、今後はこちらのページを更新していく予定です。