【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()
参考
Pythonでプロパティを実装する
今回は、Pythonでのプロパティの実装方法を解説します。
プロパティとは?
ここで取り扱うプロパティとは、クラス利用者がそのメンバ変数にアクセスするときにgetterやsetterを意識することなくアクセス出来るようにする仕組みです。
通常、クラスを実装するときはメンバ変数を直接外部に公開せず、プライベートにしつつ、別途getterとsetterを用意します。これをカプセル化と呼びます。よって、クラス利用者はgetterやsetterを介してメンバ変数にアクセスします。
class Name1: def __init__(self): self._firstname = '' self._lastname = '' def get_firstname(self): return self._firstname def set_firstname(self, firstname): self._firstname = firstname def get_lastname(self): return self._lastname def set_lastname(self, lastname): self._lastname = lastname def get_fullname(self): return self._firstname + self._lastname name = Name1() name.set_firstname('Fugataro') name.set_lastname('Hogemoto') print(name.get_firstname()) print(name.get_lastname()) print(name.get_fullname()) # Fugataro # Hogemoto # FugataroHogemoto
しかし、プロパティを用いることで、カプセル化を維持しつつ、クラス利用者はgetterやsetterを意識することなくメンバ変数にアクセス出来るようになります。
Pythonでプロパティを実装する方法は2通りあるため、両方解説していきます。
property関数を用いる方法
1つ目はproperty関数を用いる方法です。
まず、今まで通りにgetterとsetterを宣言します。そして、property関数に先ほど宣言したgetterとsetterの関数名を渡します。こうすることにより、クラス利用者はインスタンス名.メンバ変数名
のフォーマットでメンバ変数にアクセス出来るようになりつつ、クラス内部では、先ほどproperty関数に渡したgetterとsetterを経由して、メンバ変数の受け渡しが行われます。
class Name2: def __init__(self): self._firstname = '' self._lastname = '' def get_firstname(self): return self._firstname def set_firstname(self, firstname): self._firstname = firstname def del_firstname(self): del self._firstname firstname = property( get_firstname, set_firstname, del_firstname ) def get_lastname(self): return self._lastname def set_lastname(self, lastname): self._lastname = lastname def del_lastname(self): del self._lastname lastname = property( get_lastname, set_lastname, del_lastname ) def get_fullname(self): return self._firstname + self._lastname fullname = property(get_fullname) name = Name2() name.firstname = 'Fugataro' name.lastname = 'Hogemoto' print(name.firstname) print(name.lastname) print(name.fullname) # Fugataro # Hogemoto # FugataroHogemoto
また、メンバ変数をdel
する関数も登録することが出来ます。以下サンプルソースでは実際にメンバ変数をdel
していますが、del
した後にそのメンバ変数にアクセスすると例外が発生するため、del
が正常に機能していることがわかります。
name = Name2() name.firstname = 'Fugataro' print(name.firstname) del name.firstname print(name.firstname) # Fugataro # AttributeError: 'Name2' object has no attribute '_firstname'
デコレータを用いる方法
2つ目はデコレータを用いる方法です。
デコレータを用いる場合も、getter、setter、deleterを宣言しますが、それら関数に、@property
、@メンバ変数名.setter
、@メンバ変数名.deleter
を付与するだけです。
class Name3: def __init__(self): self._firstname = '' self._lastname = '' @property def firstname(self): return self._firstname @firstname.setter def firstname(self, firstname): self._firstname = firstname @firstname.deleter def firstname(self): del self._firstname @property def lastname(self): return self._lastname @lastname.setter def lastname(self, lastname): self._lastname = lastname @lastname.deleter def lastname(self): del self._lastname @property def fullname(self): return self._firstname + self._lastname name = Name3() name.firstname = 'Fugataro' name.lastname = 'Hogemoto' print(name.firstname) print(name.lastname) print(name.fullname) # Fugataro # Hogemoto # FugataroHogemoto
name = Name3() name.firstname = 'Fugataro' print(name.firstname) del name.firstname print(name.firstname) # Fugataro # AttributeError: 'Name3' object has no attribute '_firstname'
参考
画像を ASCII 表示させる方法
はじめに
テストに用いるデータなど、サーバ上にあるイメージ(画像)を確認したいときないですか?このファイルなんだっけ?みたいな。そんなとき、jp2a を使えば画像を ASCII 表示できます。テキストで表示されるので、手軽にコンソールからも確認できるってことです。
Talinx/jp2a: Converts jpg/png images to ASCII
続きを読むPythonで読み取り専用コレクションを実装する
今回は、Pythonで読み取り専用コレクションのメンバ変数を実装する方法を解説します。
概要
通常、読み取り専用のメンバ変数を実装するときは、メンバ変数をプライベートにしつつ、getterのみ宣言して、setterを宣言しないようにします。
class Name: def __init__(self): self._firstname = 'Fugataro' @property def firstname(self): return self._firstname name = Name() print(name.firstname) name.firstname = 'Hogemi' # Fugataro # AttributeError: can't set attribute
注意が必要なのは、list
やdict
などのコレクション型変数をメンバ変数とする場合です。
上記と同様に、getterのみ宣言して、setterを宣言しないことにより、コレクション型のメンバ変数を別のコレクション型変数に置き換えることは出来ません。
class NumbersList: def __init__(self): self._data = [1, 2, 3] @property def data(self): return self._data numbers = NumbersList() print(numbers.data) numbers.data = [4, 5, 6] # [1, 2, 3] # AttributeError: can't set attribute
しかし、append()
やremove()
などでコレクション型メンバ変数の中身を書き換えることが出来てしまっています。これは、getterのみの宣言により、メンバ変数そのものの置き換えは出来ないようになりましたが、コレクション型メンバ変数の関数はいまだ機能しているため、結果としてそれら関数により、中身の書き換えが出来てしまっているのです。
class NumbersList: def __init__(self): self._data = [1, 2, 3] @property def data(self): return self._data numbers = NumbersList() print(numbers.data) numbers.data.append(4) numbers.data.extend([5, 6]) numbers.data.remove(1) print(numbers.data) # [1, 2, 3] # [2, 3, 4, 5, 6]
通常、この書き換えを防ぐために、別途用意されている読み取り専用のコレクションクラスを利用します。
Pythonの場合、読み取り専用のコレクションクラスは用意されていません。しかし、基本的なコレクションであるlist
、dict
、set
に対して、以下を用いることにより、代用可能です。
通常 | 読み取り専用 |
---|---|
list | tuple |
dict | types.MappingProxyType |
set | frozenset |
実装方法
list
、dict
、set
に対して、対応する代用のコレクションにてキャストすればよいです。以下にサンプルソースを記載します。
tuple
class ReadOnlyList: def __init__(self): self._data = tuple([1, 2, 3]) @property def data(self): return self._data l = ReadOnlyList() print(l.data) print(l.data[0]) print(l.data[1]) print(l.data[2]) for i in l.data: print(i) l.data.append(4) # (1, 2, 3) # 1 # 2 # 3 # 1 # 2 # 3 # AttributeError: 'tuple' object has no attribute 'append'
types.MappingProxyType
import types class ReadOnlyDict: def __init__(self): d = {'msg': 'Hello,World!', 'param': 1} self._data = types.MappingProxyType(d) @property def data(self): return self._data d = ReadOnlyDict() print(d) print(f'msg: {d.data["msg"]}') print(f'param: {d.data["param"]}') for key in d.data: print(key) for value in d.data.values(): print(value) for key, value in d.data.items(): print(key, value) d.data['name'] = 'Foge' # <__main__.ReadOnlyDict object at 0x0000024B239D7D88> # msg: Hello,World! # param: 1 # msg # param # Hello,World! # 1 # msg Hello,World! # param 1 # TypeError: 'mappingproxy' object does not support item assignment
frozenset
class ReadOnlySet: def __init__(self): s = set((1, 2, 3)) self._data = frozenset(s) @property def data(self): return self._data s = ReadOnlySet() print(s.data) for i in s.data: print(i) s.data.add(4) # frozenset({1, 2, 3}) # 1 # 2 # 3 # AttributeError: 'frozenset' object has no attribute 'add'
Pythonで抽象クラスを実装する
今回は、Pythonで抽象クラスを実装する方法を解説します。
抽象クラスとは?
プログラミング言語によりある程度違いはありますが、抽象クラスとは、以下の特徴を持ったクラスになります。
- 継承されることを前提としています。
- 空実装のメソッドが定義されています。(これを抽象メソッドと呼びます。)
- このクラスを継承したクラスは、抽象メソッドを再定義(オーバーライド)しなければなりません。
よって、抽象クラスは子クラスに対してメソッドの定義を強制する機能を持っています。
用途としては、多態性(ポリモーフィズム)を実装するときに、メソッドの実装漏れ、引数または戻り値の違いによるエラーの回避、などがあります。
実装方法
ここでは、抽象クラスと抽象メソッドの実装方法を解説します。
まず、抽象クラスの実装方法はABCMeta
というメタクラスを用います。抽象クラスにしたいクラスのメタクラスにABCMeta
を設定することにより、そのクラスが抽象クラスになります。(ちなみに、ABC
とはAbstract Base Class
の略だそうです。)
from abc import ABCMeta class vehicle(metaclass = ABCMeta): pass
また、PythonにはABC
というクラスがあり、このクラスを継承することでも抽象クラスを実現することが可能です。
from abc import ABC class vehicle(ABC): pass
このABC
の実態は、ABCMeta
をメタクラスに設定しているだけのクラスです。よって、初めのコードと実質同じです。
次に抽象メソッドを実装するときは、@abstractmethod
デコレータを使用します。このデコレータを付与されたメソッドが抽象メソッドになります。抽象メソッドには実際の処理を記述しないので、pass
またはraise NotImplementedError()
を実装しておきます。(NotImplementedError()
とは、未実装エラーを表す例外です。)
from abc import ABCMeta from abc import abstractmethod class vehicle(metaclass = ABCMeta): """抽象クラスvehicleの定義。""" @abstractmethod def start(self): raise NotImplementedError() @abstractmethod def stop(self): raise NotImplementedError()
ABCMeta
には、抽象メソッドが実装されている場合にインスタンスを生成しようとすると、例外が発生する仕様になっています。よって、上記vehicle
のインスタンスを生成しようとすると、以下のように例外が発生します。
v = vehicle()
# TypeError: Can't instantiate abstract class vehicle with abstract methods start, stop
なお、ABCMeta
をメタクラスとしているが抽象メソッドが実装されていないクラスや、抽象メソッドは実装されているがABCMeta
をメタクラスとしていないクラスのインスタンスは生成出来てしまいます。抽象クラスを実装するときは、ABCMeta
をメタクラスにすることと抽象メソッドの実装をセットで行ってください。
以下のように、抽象クラスを継承して抽象メソッドをオーバーライドすれば、インスタンスの生成が出来るようになります。
from abc import ABCMeta from abc import abstractmethod class vehicle(metaclass = ABCMeta): """抽象クラスvehicleの定義。""" @abstractmethod def start(self): raise NotImplementedError() @abstractmethod def stop(self): raise NotImplementedError() class car(vehicle): """vehicleを継承したクラスcarの定義。""" def start(self): print("car start.") def stop(self): print("car stop.") class motorcycle(vehicle): """vehicleを継承したクラスmotorcycleの定義。""" def start(self): print("moto start.") def stop(self): print("moto stop.") my_car = car() my_car.start() my_car.stop() my_motorcycle = motorcycle() my_motorcycle.start() my_motorcycle.stop() # car start. # car stop. # moto start. # moto stop.
抽象メソッドを表すデコレータについては、@abstractmethod
以外にも存在します。ここでは詳細を省きますが、抽象メソッドを表すデコレータを以下に列挙しておきます。
@abstractmethod
@abstractclassmethod
@abstractstaticmethod
@abstractproperty
ちなみに、誤って抽象クラスのインスタンスを生成してしまっているコードをflake8
にかけてみましたが、こちらではなにも表示されませんでした。抽象クラスのインスタンス生成エラーは実行時にしかわからないようです。
参考
-
公式ドキュメントです。
-
こちらはWikipediaの抽象クラスに関する説明です。
【Android】コンストラクタに引数があるViewModelを使う
AndroidでViewModelを使用するとき、以下のようなコンストラクタに独自の引数があるViewModelを使用したいときがあります。今回は、その方法を解説します。
class MainViewModel(id: Int) : ViewModel()
独自Factoryの定義
ViewModelインスタンスの生成はViewModelのコンストラクタを直接呼び出すのではなくて、以下のようにFactoryクラスのcreateメソッドを呼び出して生成します。
if (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) mFactory).create(key, modelClass); } else { viewModel = mFactory.create(modelClass); }
よって、まずはコンストラクタに引数があるViewModelに対応したFactoryクラスを定義します。
class MainViewModel(id: Int) : ViewModel() { class Factory( private val id: Int ) : ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return MainViewModel(id) as T } } }
独自Factoryの使用
独自に定義したFactoryクラスは、そのインスタンスをViewModelProviderに渡す必要があります。ViewModelProviderのコンストラクタの中に、Factoryインスタンスを引数に持つコンストラクタがオーバーロードされているので、以下のようにそのコンストラクタを使用します。
class MainActivity : AppCompatActivity(R.layout.main_activity) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModel = ViewModelProvider( this, MainViewModel.Factory(0) ).get(MainViewModel::class.java) } }
以上のようにして、コンストラクタに引数があるViewModelを使用できます。