- 2019年、自分用に色々作っては「まあええやん」「コレあかん」という感じで新しいものを試したり壊したりしてきた
- ライブラリを作るのは個人的な趣味であり、いろんな設計を試せる娯楽であり、勉強でもある
armyknife-*
- armyknifeシリーズ
- 自分用の十徳ナイフ、ちょっとしたことを楽に書くために作ってる
- だいたい拡張関数、ときどきObject
- 拡張関数なので、ライブラリをリンクしたくないときはコピペでもだいたい動くようになってる
- 作ったあとにKotlin標準/Jetpack標準で同等機能が生えたりして意味がなくなったりしてる
- その時は気づいたタイミングでDeprecatedにしたり。
armyknife-runtime
よく使う機能
Random.string(32)
ByteArray.encodeBase64()
Channel<E>.cancelByError(e: Throwable)
Channel<E>.receiveOrError()
よく使う機能
ApplicationRuntime.runIn(flags )
CoroutineContext.with(lifecycle: Lifecycle)
LiveDataFactory.transform()
LiveDataFactory.transformNullable()
LiveDataFactory.transformInto()
LiveDataFactory.transformNullableInto()
LiveDataFactory.contextFrom()
Parcelable.deepCopy()
Intent.putMarshalParcelableExtra(name: String, value: Parcelable?)
ntent.getMarshalParcelableExtra(name: String)
Task<T>.awaitInCoroutines()
PendingResult<T>.awaitInCoroutines()
armyknife-gms
よく使う機能
Firebase.app
Firebase.remoteConfig
Firebase.provideFromAssets()
Firebase.provideFromGoogleServiceJson()
FirebaseContext.getInstance().observe()
armyknife-android-junit4
- Robolectric/Instrumentation TestのJUnit4動作を補助する
- github
- そろそろJUnit5に移行したいところ
よく使う機能
instrumentationBlockingTest {}
localBlockingTest {}
compatibleBlockingTest {}
val targetApplication
val targetContext
val testContext
makeActivityViewModel()
makeFragmentViewModel()
ViewModel.activeAllLiveDataForTest()
armyknife-android-bundle
- Bundleに便利機能を追加する
- github
- 作った割に、「あ、あんまり使わないわ」みたいになった
よく使うと思って作った割に使わなかった機能
Bundle.delegateIntExtra()
Bundle.delegateStringExtra()
armyknife-reactivex
- RxKotlinとChannel/Lifecycle管理用ブリッジを提供する
- github
- 結局の所、
firearm-event
との組み合わせでしか使ってない
よく使う機能と思ったけど、用途が局所的
armyknife-jetpack-lifecycle
- Lifecycleのなかで、たまに面倒と思う処理を提供する
- github
まれによく使う機能
InternalLinkLiveData<T>
armyknife-widgets
たまに役立つ
buildAlertDialogChannel(): Channel<DialogResult>
armyknife-jetpack-camera
- CameraXをちょっと便利にする
- github
- CameraX自体がまだ不安定だったり、機能が増えていって不要になったりしてる
まれに使う
ImageAnalysis.setVisionAnalyzer
ImageAnalysis.setVisionBarcodeDetectorAnalyzer
firearm-*
firearm-channel
- startActivityForResult/RuntimePermission/DialogをChannelとして利用する
- github
- annotation-processorが要らなかったり、ほかのAPT系と競合しない
- Channelを使うので、suspend関数の中で終了待ちとか戻り値が取得できる
- Channelの特性上、Activityが破棄されたりプロセスがシャットダウンされると死ぬ
- ご利用は計画的に
計画的に使う機能
val activityDispatcher = ActivityResultDispatcher.get(fragment)
suspend fun example() {
val result = activityDispatcher.startActivityForResultWithResult(intent)
result.result
result.data
}
firearm-event
- ViewModelで発生したEventをpublishする
- github
- 内部はRxKotlinでできている
- UnitTestするとき、Channelに変換して「この処理をしたらこのイベントが発生する」といったUnitTestを書ける
- ViewModelで発生したイベントと、イベントに対するUI動作をFragmentやActivity等の外部で行いたいときに使う
- ViewModel内でハンドリングするとしても、FragmentやActivityで発生イベントをハンドリングできたほうが便利な場合は多い
- Notificationを出したり、別なFragmentやViewModelにアクションを行ったり
よく使う機能
val EVENT_CLICK_DONE_BUTTON = EventId("EVENT_CLICK_DONE_BUTTON ")
val EVENT_CLICK_OK_BUTTON = EventId("EVENT_CLICK_OK_BUTTON")
data class OnDataLoadError(cause: Int): Event
val event = EventStream { event ->
when(event) {
EVENT_CLICK_DONE_BUTTON -> true
is OnDataLoadError-> true
else -> false
}
}
fun onClickDone() {
event.next(EVENT_CLICK_DONE_BUTTON)
}
fun onClickLoad() {
launch {
event.next(OnDataLoadError(1))
}
}
fun onClickOk() {
event.next(EVENT_CLICK_OK_BUTTON)
}
firearm-di
- Factory/BuilderパターンのDependency Injectionを行う
- github
- Kotlinは言語機能が強力だし、Dagger等のDIって実際のところ無理に使うほどでもないな、という個人的見解
- 不安定だったり、ASバージョンアップで死んだり、色々あったから
- UnitTestだけちょっと挙動を変更したりMockを返したい場合に使う
よく使う機能
object FooClassFactory {
val provider = ProviderRegistry.newProvider<FooClass, Builder> {
return Foo(this.context)
}
class Builder(var context : Context) {
fun build() : Foo = provider(this)
}
}
val builder = FooClassFactory.Bulider(context)
val foo = builder.build()
FooClassFactory.provider.overwrite {
FooForTest(this.context)
}
val builder = FooClassFactory.Bulider(context)
val foo = builder.build()
FooClassFactory.provider.reset()
val builder = FooClassFactory.Bulider(context)
val foo = builder.build()
firearm-workflow
- firearm-channelがActivityの再生成やプロセス再起動に弱いため、それを克服するライブラリ
- github
- startActivityForResultやDialogFragmentやRuntime Permissionをプロセス再起動を前提に復旧可能にしている
- FragmentやActivityにonActivityResult()のハンドラを書かなくてもいい
- APTは使ってないので、他のAPT系ライブラリと共存できる
- startActivityとかDialogFragmentを起動する際に、一時的に値を保存する機能がある
- コレは一時的なイベントの途中のステートなので、メンバ変数に保存したくない
- けれどstartActivityForResultのタイミングでプロセス再起動されていたら困る
- 例えば。。。
- ユーザー一覧を開く
- ユーザーをクリックして別Activityでユーザー詳細が開いて、連絡先を選択させる
- 連絡先をIntentで受け取って、非同期でサーバーで処理させる
- DialogFragmentで「パターンA/Bどっちにする?」と聞く
- 処理前にRuntime Permissionで必要なパーミッションも取得する
- サーバーの結果をUIに反映する(ここまで一連の処理)
- 画面を開いたり、一時的なステートがいっぱいある
- firearm-channelとかでcoroutineに閉じ込められるけど、プロセス再起動とか画面回転とかしたら死ぬ
- 一時的なイベント用ステートがいっぱいある
- こんなとき、一時的なステートを保存する機能を提供したり、プロセス再起動してもちゃんとフローを継続できるようにする
面倒だけど使う機能
firearm-experimental
- 実験的な機能やアイディアをとりあえず試す
- github
- (ピコーン)これいいんじゃね!!で試してから冷静になるまでの間、このリポジトリに閉じ込める
割といい感じだった機能 / ViewModelSession
class ExampleViewModel : ViewModel() {
val session = ViewModelSession<Fragment>()
val context: LiveData<Context>
get() = session.context
private val observeSessionToken = Observer<ViewModelSession.Token<Fragment>> {
val token = it ?: return
token.launch(Dispatchers.IO) {
}
}
}
class ExampleFragment : Fragment() {
val viewModel: ExampleViewModel by lazy {
val viewModel: ExampleViewModel =
viewModel.session.refresh(this)
}
}
冷静になってDeprecatedした機能 / SingleTask
SingleTask.run {
}
転生した機能 / LiveTaskCounter
val counter = LiveTaskCounter()
val progressVisibility: LiveData<Int> =
LiveDataFactory.transform(counter) { snapshot->
if(snapshot.count > 0) {
View.VISIBLE
} else {
View.GONE
}
}
suspend fun asyncWrite() {
counter.withCount {
}
}
suspend fun asyncRead() {
counter.withCount {
}
}
こいつら使うとき
- build.gradleにrepositoryを追加
allprojects {
repositories {
maven(url = "https://dl.bintray.com/eaglesakura/maven/")
}
"implementation"("com.eaglesakura.armyknife.{リポジトリ名}:{リポジトリ名}}:{バージョン名}")
// armyknife-jetpackの場合
// 全部 `v1.4.2` とかタグ打ってるので、そのmajor.minor.build を入れる
// bintrayとかも参照で
// https://bintray.com/eaglesakura/maven/armyknife-jetpack
"implementation"("com.eaglesakura.armyknife.armyknife-jetpack:armyknife-jetpack:1.4.2")
色々つくったな