kotlin小ネタ:Activity 関連の試行錯誤
Activity を扱う際の実験コード。
とりあえず思い付きで作ってみた。
ユニットテスト重視版。
たぶんおいらのプロジェクトではこんなことやらんだろうけどw
/** * Activity を扱う際のいろんなネタを詰め込んでみた実験コード。 * ※書式だけまとめたもので、実際に動くコードではない。 * * ポイントは以下。 * ・Intent を利用せずに Activity を起動するチート。 * ・XxxActivityHelper を分離することにより Activity の制約とは無縁のインスタンスで Activity の処理を記述できる。 * ・コンストラクタ引数の評価時(thisが生成される前)に可能な限り初期化を行うことにより初期化前のプロパティを参照 * してしまうバグの入れ込みを不可能にする。※コンパイラレベルで排除される * ・テスト用にコンストラクタの動的な差し替えが可能。※newInstance を var で定義している。 * ・ほぼすべてのメソッドが単体でユニットテスト可能。this も差し替えられるように extension を使用している。 * ・メソッド階層をすべて辿ったうえで var への代入が行われるコードかどうかをコンパイラレベルでチェック可能。 */ package com.example import android.app.Activity import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import com.example.XxxActivityHelperImplDelegate.getPcaSomethingCreatedByConstructor import com.example.XxxActivityHelperImplDelegate.onBackPressed_ import com.example.XxxActivityHelperImplDelegate.onBeforeConstructor import com.example.XxxActivityHelperImplDelegate.onPause_ import org.jetbrains.anko.AnkoLogger import org.jetbrains.anko.startActivity import com.example.XxxActivityHelperImpl as Assignable import com.example.XxxActivityHelperImpl as Impl /** * [Activity] 本体 * * 以下の理由で [onCreate] 以外の処理をすべて [XxxActivityHelper] に委譲している。 * ・ヘルパーを利用することにより [onCreate] 以降に生成された値を [XxxActivityHelper] の val として扱える。 * ・[Activity] の仕様に特化した処理とそれ以外を分離できる。※[onPause] で super.onPause() を呼ぶ処理など。 */ class XxxActivity : AppCompatActivity(), AnkoLogger { // backing field を持つ唯一のプロパティ // これ以外はすべて Helper 側に含まれる private lateinit var helper: XxxActivityHelper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Intent を利用せずに Activity を起動するチート用の処理。 // XxxHelper に委譲する前に必要な処理なのでここに記述している。 val activityArg1 = activityArg1 val activityArg2 = activityArg2 if (activityArg1 != null && activityArg2 != null) { helper = XxxActivityHelper.newInstance(this@XxxActivity, activityArg1, activityArg2) Companion.activityArg1 = null Companion.activityArg2 = null } else { finish() } } override fun onBackPressed() { helper.onBackPressed() } override fun onPause() { super.onPause() helper.onPause() } companion object { /** * [Intent] を利用せずに [Activity] を起動するチート用の情報 * [startActivity] 経由で起動される保証はないので lateinit にはできない。※端末の履歴機能からの Activity 起動など。 */ private var activityArg1: Something1? = null private var activityArg2: Something2? = null /** * [Intent] を利用せずに [Activity] を起動するチート */ fun startActivity(activity: XxxActivity, activityArg1: Something1, activityArg2: Something2) { Companion.activityArg1 = activityArg1 Companion.activityArg2 = activityArg2 activity.startActivity<XxxActivity>() } } } /** * [XxxActivity] から処理を移譲されるヘルパークラス */ interface XxxActivityHelper { fun onBackPressed() fun onPause() companion object { // テスト時には必要に応じて newInstance を差し替える var newInstance = ::Impl } } class XxxActivityHelperImpl private constructor( override val activity: XxxActivity, override val activityArg: Something1, override val somethingCreatedByConstructor: Something3 ) : XxxActivityHelper, XxxActivityHelperImplDelegate.NonAssignable { // Secondary constructors to create primary constructor arguments -------------------------------------------------- // this を参照しなくても生成可能な情報はすべてコンストラクタ引数として扱う。 // これにより、property initializer もしくは initializer block にて非 null に初期化のプロパティが null にならない // という勘違いによるバグを防ぐ。※初期化前に参照できるので @Suppress("unused") // for inspector's bug. constructor(activity: XxxActivity, activityArg1: Something1, activityArg2: Something2) : this( // どうしても評価前に行わなきゃならん処理がある場合の苦肉の策。 onBeforeConstructor(activity, activityArg1, activityArg2).run { activity }, activityArg1, getPcaSomethingCreatedByConstructor(activityArg2)) { // ここには可能な限りコードを書かない。 } // Properties with backing fields that refer to this@Impl ---------------------------------------------------------- override var isBackPressed = false // Initializer block ----------------------------------------------------------------------------------------------- init { // ここには可能な限りコードを書かない。 } // Delegate functions ---------------------------------------------------------------------------------------------- override fun onBackPressed() { onBackPressed_() } override fun onPause() { onPause_() } } object XxxActivityHelperImplDelegate : AnkoLogger { // Constants ------------------------------------------------------------------------------------------------------- // onBeforeConstructor --------------------------------------------------------------------------------------------- fun onBeforeConstructor(activity: XxxActivity, activityArg1: Something1, activityArg2: Something2) { } // Primary constructor argument initializers ----------------------------------------------------------------------- fun getPcaSomethingCreatedByConstructor(activityArg: Something2): Something3 { return Something3() } // Property initializers for properties that internally refer to this@Impl ----------------------------------------- // Initializer block delegation ------------------------------------------------------------------------------------ // Direct delegates ------------------------------------------------------------------------------------------------ fun Assignable.onBackPressed_() { isBackPressed = true } fun NonAssignable.onPause_() { with(activity) { // プロパティへの代入の無い処理をここで行う } } // Members that assign values to backing fields -------------------------------------------------------------------- // Members that have side effects ---------------------------------------------------------------------------------- // Non-assignable interface of implementation class ---------------------------------------------------------------- // NonAssignable が receiver のメソッドはプロパティへの代入が無いことがコンパイラにより保証される。保守時に便利。 // NonAssignable であってもイミュータブルである保証はない点に注意。 interface NonAssignable : AnkoLogger { val activity: XxxActivity val activityArg: Something1 val somethingCreatedByConstructor: Something3 val isBackPressed: Boolean } } class Something1 class Something2 class Something3