【Android】MediatorLiveData で他の LiveData を監視する
今回は、MediatorLiveData で他の LiveData を監視する方法を解説します。
なお、ここに掲載しているソースコードは以下の環境で動作確認しています。
- Android Studio Bumblebee | 2021.1.1 Patch 2
- JDK 11.0.11
- Android Gradle Plugin 7.1.2
- Kotlin 1.6.10
- Gradle 7.4.1
- androidx.lifecycle 2.4.1
MediatorLiveData とは?
MediatorLiveData は LiveData のサブクラスであり、他の LiveData の値を監視して自身の値を変更することができます。
以下が、監視対象を追加するメソッドです。
@MainThread public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged)
引数 source に監視対象の LiveData を設定します。そして、引数 onChanged に引数 source が変更されたときに呼び出されるコールバックを設定します。
なお、1 つの MediatorLiveData で、2 つ以上の LiveData を監視することが可能です。
// OK
mediator.apply {
addSource(source1) { mediator = it }
addSource(source2) { mediator = it }
}
しかし、MediatorLiveData は 1 つの LiveData に対して 2 つ以上のコールバックを設定することができません。
以下のように、すでに監視を開始している LiveData に対して、異なるコールバックを設定した場合、MediatorLiveData#addSource は IllegalArgumentException を投げます。
// NG mediator.apply { addSource(source1) { mediator = it } addSource(source1) { mediator = "source1: $it" } }
監視を停止したい場合には、以下の MediatorLiveData#removeSource を使います。
@MainThread public void <S> removeSource(@NonNull LiveData<S> toRemote)
MediatorLiveData を使う
実際に MediatorLiveData を試してみます。
今回は、Activity と ViewModel、そして、String 型の値を持つ LiveData を 2 つとそれらを結合する MediatorLiveData を 1 つ用意します。MediatorLiveData は、2 つの LiveData を監視し、それらが変更されるたびに 2 つの LiveData を結合して自身に格納します。
上記の 3 つの LiveData を ViewModel に保持し、Activity から結合元となる 2 つの LiveData の変更と MediatorLiveData の監視を行います。
ライブラリのインポート
MediatorLiveData を使えるようにするために、アプリの build.gradle ファイルに次の依存関係を追加します。
dependencies { implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1" }
lifecycle-livedata-ktx
をインポートすることにより、MediatorLiveData が使えるようになります。また、以降のサンプルでは LiveData を ViewModel に保持するため、lifecycle-viewmodel-ktx
もインポートします。
ViewModel で値の保持と結合を行う
まず、ViewModel を以下のように実装します。
class MainViewModel : ViewModel() { // editText1 の文字列 val str1 = MutableLiveData("") // editText2 の文字列 val str2 = MutableLiveData("") // editText1 と editText2 の結合 val linkedStr: MediatorLiveData<String> = MediatorLiveData() init { linkedStr.apply { // str1 の変更を監視する。 addSource(str1) { linkedStr.value = "$it ${str2.value!!}" } // str2 の変更を監視する。 addSource(str2) { linkedStr.value = "${str1.value!!} $it" } } } }
監視の開始を init で行います。2 つの LiveData ごとに addSource を呼び出します。監視対象が変更されたら、もう一方の LiveData と結合して、自身に格納します。
Activity で LiveData の変更と MediatorLiveData の監視を行う
そして、Activity を以下のように実装します。
class MainActivity : AppCompatActivity(R.layout.main_activity) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModel = ViewModelProvider(this)[MainViewModel::class.java] // editText1 の文字列変更を str1 に反映する。 findViewById<EditText>(R.id.editText1) .doOnTextChanged { text, _, _, _ -> viewModel.str1.value = (text ?: "").toString() } // editText2 の文字列変更を str2 に反映する。 findViewById<EditText>(R.id.editText2) .doOnTextChanged { text, _, _, _ -> viewModel.str2.value = (text ?: "").toString() } viewModel.linkedStr.observe(this) { findViewById<TextView>(R.id.text).text = it } } }
Activity のレイアウトには、2 つの LiveData ごとに EditText を用意し、それらのテキストの変更を監視し、テキストを ViewModel の LiveData に格納します。
そして、2 つの LiveData を結合した linkedStr
を監視し、その値を TextView に表示します。
こうすることにより、2 つの EditText に入力したテキストを結合したものが TextView に表示されるようになります。
参考
【Android】アプリのインストールを検知する
今回は、Androidでアプリのインストールを検知する方法を解説します。
なお、ここに掲載しているソースコードは以下の環境で動作確認しています。
- Android Studio Chipmunk | 2021.2.1
- JDK 11.0.12
- Android Gradle Plugin 7.2.0
- Kotlin 1.6.21
- Gradle 7.4.2
アプリのインストールを検知する方法
Android OSは、アプリがインストールされると android.intent.action.PACKAGE_ADDED
というインテントを発信します。よって、このインテントをBroadcastReceiverにて受信することにより、アプリのインストールを検知することができます。
マニフェストファイルの変更
まず、マニフェストファイル AndroidManifest.xml
に以下の設定を追加します。
<receiver android:name=".PackageChangedReceiver" android:enabled="true" android:exported="false"> <intent-filter> <action android:name="android.intent.action.PACKAGE_ADDED" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="package" /> </intent-filter> </receiver>
BroadcastReceiverの実装
そして、以下のようにBroadcastReceiverを実装します。
public final class PackageChangedReceiver extends BroadcastReceiver { @NonNull private static final String TAG = "PackageChangedReceiver"; @Override public void onReceive(final Context context, final Intent intent) { // intent#getDataStringからインストールされたアプリのパッケージ名を取得できる。 final String packageName = intent.getDataString(); Log.d("PackageChanged", packageName); } }
以上の実装により、アプリのインストールを検知できるようになります。
また、intent#getDataStringを使ってインストールされたアプリのパッケージ名を取得することができるため、期待するアプリがインストールされたのか判断することも可能です。
【Android】WebView の JavaScript からネイティブコードを呼び出す
今回は、WebView の JavaScript からネイティブコードを呼び出す方法を解説します。
なお、ここに掲載しているソースコードは以下の環境で動作確認しています。
- Android Studio Bumblebee | 2021.1.1 Patch 3
- JDK 11.0.11
- Android Gradle Plugin 7.1.3
- Kotlin 1.6.21
- Gradle 7.4.2
JavaScript からネイティブコードを呼び出すまでの流れ
WebView の JavaScript からネイティブコードを呼び出すには、以下を行う必要があります。
- JavaScript とネイティブコードをつなぐインターフェースを作成する
- JavaScript とネイティブコードのインターフェースを WebView に追加する
- WebView の JavaScript を有効にする
今回は、WebView に表示する Web ページにボタンを用意し、そのボタンを押すと、Android ネイティブの Toast を表示するようにします。また、ネイティブコードは JavaScript に対して戻り値を返すようにし、JavaScript はその戻り値を Web ページに表示するようにします。
JavaScript とネイティブコードをつなぐインターフェースを作成する
まず、JavaScript とネイティブコードをつなぐインターフェースを作成します。
class WebAppInterface(private val context: Context) { @JavascriptInterface fun showMessage(message: String): String { Toast.makeText(context, message, Toast.LENGTH_LONG).show() return message } }
インターフェースには、JavaScript から呼び出すネイティブコードをメソッドとして作成します。このメソッドには、@JavascriptInterface を付与し、public にする必要があります。
今回作成するメソッドでは、JavaScript から文字列を受け取るようにし、その文字列を Toast に表示します。また、受け取った文字列をそのまま戻り値として呼び出し元に返します。
WebView の設定
次に、WebView にさきほど作成したインターフェースを追加し、JavaScript を有効にします。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WebView(this).apply { setContentView(this) loadUrl("file:///android_asset/index.htm") val webAppInterface = WebAppInterface(this@MainActivity) addJavascriptInterface(webAppInterface, "Android") settings.apply { allowFileAccess = true javaScriptEnabled = true } } } }
まず、以下のように WebView にさきほど作成したインターフェースを追加します。
WebView#addJavascriptInterface の第 1 引数にインターフェースのインスタントを渡します。そして、第 2 引数には、JavaScript から呼び出す際の名前を指定します。例えば、この名前に "Android"
を指定した場合、JavaScript から呼び出す際は Android.showMessage()
となります。
WebView(this).apply { ︙ val webAppInterface = WebAppInterface(this@MainActivity) addJavascriptInterface(webAppInterface, "Android") ︙
そして、WebView の JavaScript を有効にします。デフォルトでは JavaScript は無効になっているので、有効にする必要があります。
WebView(this).apply { ︙ settings.apply { ︙ javaScriptEnabled = true } }
以上で、ネイティブの実装は完了です。これで、JavaScript からネイティブコードを呼び出すことができるようになります。
JavaScript からネイティブコードを呼び出す
JavaScript からネイティブコードを呼び出してみます。Web ページの HTML を以下のようにします。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> </head> <body> <script type="text/javascript"> function showMessage(message) { const result = Android.showMessage(message); document.getElementById("text").innerHTML = result; } </script> <input type="button" value="Button" onClick="showMessage('Hello Android!');" /> <p id="text"></p> </body> </html>
ボタンが押されたら、JavaScript とネイティブコードのインターフェースの showMessage メソッドを呼びます。この呼び出しにより、Android ネイティブの Toast が表示されます。また、showMessage メソッドの戻り値を p タグに表示します。
参考
【Android】LiveDataで値を監視する
LiveData
は監視可能なデータホルダークラスです。通常の監視とは異なり、LiveData
は Android のライフサイクルに応じた監視が可能です。つまり、アクティビティ、フラグメント、サービスなどの他のアプリコンポーネントのライフサイクルが考慮されます。
build.gradle
の dependencies
の設定は以下の通り。
// ViewModel implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version") // LiveData implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
ここでは、以下の2種類の LiveData
を解説します。
- LiveData
- MutableLiveData
LiveData
データが更新されたとき、監視者にそれを通知します。データを直接更新することはできません。
LiveData#observe
public void observe(LifecycleOwner owner, Observer<? super T> observer)
アクティビティやフラグメントなどの監視者が監視を開始します。引数 owner
には監視者のライフサイクルオーナーを指定します。監視者がアクティビティのときは this
を指定します。監視者がフラグメントのときは viewLifecycleOwner
を指定します。これは、フラグメントのライフサイクルはアクティビティのそれとは異なり、不要な更新通知などを避けるためです。
以下は、フラグメントが LiveData
を監視する場合の observe
の使用方法です。
viewModel.count.observe(viewLifecycleOwner, { binding.countText.text = it.toString() })
LiveData#getValue
public T getValue()
LiveData
に格納されている値を取得します。
MutableLiveData
データが更新されたとき、観察者にそれを通知します。データを直接更新することができます。LiveData
を継承しています。
一般的に、クラスのカプセル化を意識して、MutableLiveDataプロパティを非公開にし、LiveDataプロパティのみを公開します。
class MainViewModel : ViewModel() { private val _count = MutableLiveData(0) val count: LiveData<Int> get() = _count
MutableLiveData#setValue
public void setValue(T value)
MutableLiveData
に値を設定します。メインスレッドから値を設定する場合に使用します。
MutableLiveData#postValue
public void postValue(T value)
MutableLiveData
に値を設定します。ワーカースレッドから値を設定する場合に使用します。
参考
Pythonのf文字列による文字列補完
Pythonで文字列補完をするとき、今までは文字列メソッドformat()を使用していたのですが、Python3.6から追加されたf文字列(フォーマット済み文字列リテラル)を用いることで、より簡潔に記述出来ることがわかったので、ここにその方法を記載します。
サンプルソース
以下にformat()による文字列補完とf文字列による文字列補完を記載します。
w = 'World' msg = 'Hello,{}!'.format(w) print(msg) # Hello,World!
w = 'World' msg = f'Hello,{w}!' print(msg) # Hello,World!
上記の通り、冗長なformat()呼び出しの記述がなくなるため、f文字列による文字列補完のほうがソースコードが簡潔になります。
書式指定
文字列メソッドformat()と同様に、f文字列でも置換フィールドでコロン:のあとに書式指定文字列を指定することで様々な書式を指定出来ます。
s = 'abc' right = f'right : {s:_>8}' center = f'center: {s:_^8}' left = f'left : {s:_<8}' print(right) print(center) print(left) # right : _____abc # center: __abc___ # left : abc_____
i = 1234 s = f'{i:08}' print(s) # 00001234
参考
Pythonのvenvでパッケージ管理する
今回は、venvを用いたPythonのパッケージ管理方法を解説します。
パッケージ管理とは?
Pythonでは、pip
コマンドによりパッケージをインストールすることが出来ます。このときにそのままパッケージをインストールすると、ユーザーディレクトリなどにインストールされ、各プロジェクト共通になります。
しかし、プロジェクト毎にパッケージを隔離したい場合があります。例えば、特定のプロジェクトのパッケージのバージョンを古いバージョンのままにしたいなどです。そのような場合に、パッケージ管理を利用します。
前準備
venv自体はPython3.3から標準搭載であるため、Pythonのバージョンが3.3以上であれば、venvのインストール作業は必要ないです。
まず、プロジェクトディレクトリを以下の構成にします。
[Project Directory] ├src │ ├__init__.py │ └hoge.py └venv
上記のvenvディレクトリは仮想環境ディレクトリなどと呼ばれ、このディレクトリ以下にインストールしたパッケージが保存されていきます。最初、venvディレクトリの中は空にします。ディレクトリ名はvenvでなくてもよいですが、一般的にvenvと命名することが多いので、そのままとします。
なお、srcディレクトリ以下にソースコードを配置します。このディレクトリも制限はないですが、srcディレクトリ以下にソースコードを配置するのが一般的なので、こちらもそれに倣います。
利用方法
仮想環境の作成
カレントディレクトリをプロジェクトディレクトリ直下にした状態で、コマンドプロンプトなどで以下のコマンドを打ちます。
>python -m venv [venv_dir_name]
[venv_dir_name]
には仮想環境ディレクトリのディレクトリ名を入力します。なので、今回はvenvとなります。
仮想環境の作成に成功すると、以下のように仮想環境ディレクトリの配下に新たにディレクトリやファイルが生成されます。
[Project Directory] ├src │ ├__init__.py │ └hoge.py └venv ├Include ├Lib ├Scripts └pyvenv.cfg
Activate
以下のようなコマンドを入力して仮想環境を有効にします。
>.\[venv_dir_name]\Scripts\activate ([venv_dir_name]) >
コマンド入力後、コマンドプロンプト上で仮想環境ディレクトリ名が表示されるようになったと思います。
次に、インストール済みパッケージを確認するために、pip freeze
コマンドを入力します。
([venv_dir_name]) >pip freeze ([venv_dir_name]) >
入力の結果、パッケージのインストール状態がまっさらになっていることがわかります。
パッケージのインストール
pip install
コマンドを入力して、パッケージをインストールします。インストール方法は、venvを使用していないときと同じです。
([venv_dir_name]) >pip install [package name]
次に、再度pip freeze
コマンドを入力します。
([venv_dir_name]) >pip freeze [package name1]==x.x.x [package name2]==x.x.x ・ ・ ・
入力の結果、仮想環境にパッケージがインストールされたことがわかります。
Deactivate
仮想環境を無効にするには、deactivate
コマンドを入力します。
([venv_dir_name]) >deactivate >
入力の結果、コマンドプロンプトから仮想環境ディレクトリ名の表示が消えて、仮想環境を有効にする前の状態に戻ったことがわかります。
仮想環境の破棄と作り直し
仮想環境を1から作り直したい場合など、作成済みの仮想環境を破棄したい場合は、仮想環境ディレクトリ配下のディレクトリとファイルをすべて削除してください。
削除後、再度venv
コマンドを入力することにより、仮想環境を作り直すことが出来ます。
【Android】SavedStateHandle解説
今回は、AndroidのSavedStateHandleを解説します。
SavedStateHandle
クラスは、set()
メソッドおよび get()
メソッドを介して、SavedState
との間でデータの書き込みや取得を行えるようにする Key-Value マップです。また、getLiveData()
を使用して LiveData
オブザーバブルにラップされている値を SavedStateHandle
から取得できます。キーの値が更新されると、LiveData
が新しい値を受け取ります。
build.gradle
の dependencies
では、以下を必要とします。
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version")
使用例
例えば、以下のように使用します。
class MainViewModel( // ViewModelの第1引数にSavedStateHandleを指定します。 private val handle: SavedStateHandle ) : ViewModel() { companion object { // 値の出し入れに使用するキーを定数宣言します。 private const val KEY_COUNT = "count" } // SavedStateHandleからキーKEY_COUNTにひも付くMutableLiveDataを探して取り出します。 // 存在しない場合は新しく作ります。 // そして、SavedStateHandle内でキーKEY_COUNTに対してひも付けます。 // 初期値0。 private val _count = handle.getLiveData(KEY_COUNT, 0) val count: LiveData<Int> get() = _count fun countUp() { // _count.valueはInt?型ですが、SavedStateHandle#getLiveData呼び出し時に初期値を設定しており、 // nullが入り込むことがありません。よって、!!によって強制的にアンラップしています。 val currentCount = _count.value!! // SavedStateHandle#getLiveDataにて値を取り出している場合、 // SavedStateHandle#setで値の格納と同時に監視者への更新通知が行われます。 // なお、SavedStateHandle#getにて値を取り出している場合には、 // 監視者への更新通知が行われません。 // また、内部的にはMutableLiveData#setValueが呼ばれているため、 // ワーカースレッドから呼び出してはいけません。 handle.set(KEY_COUNT, currentCount + 1) }
また、ViewModel
の取得でby viewModels()
を使用している場合、viewModels()
が ViewModel
に SavedStateHandle
のインスタンスを渡しているため、コードを変更する必要はありません。
class MainFragment : Fragment(R.layout.main_fragment) { private val viewModel: MainViewModel by viewModels()