ViewModel が画面回転しても保持される仕組み

この記事は Android Advent Calendar 2018 12/6 の記事です。

  • この記事は androidx をベースに書いてます。
    • compileSdkVersion 28
    • androidx.appcompat:appcompat:1.0.2
  • 簡潔に説明するため、一部のコードを省略しています。

次のコードのように Android Architecture Components の ViewModelProvider を経由して ViewModel のインスタンスを取得しておくと、 HogeActivity が回転した後も、回転前と同じ ViewModel のインスタンスを取得できる仕組みになっています。

class HogeActivity : AppCompatActivity {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    Log.d(TAG, "onCreate")

    val activity: FragmentActivity = this
    val factory: ViewModelProvider.Factory = ViewModelProvider.NewInstanceFactory()

    // viewModel は Activity 回転前も後も同じインスタンス
    val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)
    // 直接インスタンス化しては、 Activity 回転前後で同じインスタンスが使えない
//  val viewModel = FooViewModel()

    Log.d(TAG, "  - Activity :${this.hashCode()}")
    Log.d(TAG, "  - ViewModel:${viewModel.hashCode()}")
  }
}
// Activity が起動した
onCreate
  - Activity :132818886
  - ViewModel:249530701
onStart
onResume

// Activity が回転した
onPause
onStop
onRetainNonConfigurationInstance
onDestroy
onCreate
  - Activity :103312713
  - ViewModel:249530701 <- 同じ ViewModel であることがわかる
onStart
onResume

ViewModel は Activity 回転時のライフサイクルを超えてインスタンスが保持されることが、公式サイトに書いてあります。

https://developer.android.com/topic/libraries/architecture/viewmodel#lifecycle

Activity が回転しても ViewModel が保持されるのはどういう仕組みなのか、紐解いていきたいと思います。

まず、お目当ての ViewModel は ViewModelProvider から取得していることがわかりますね。

val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)

この ViewModelProvider の中身がどういう実装なのか見ていきます。

ViewModelProvider

ViewModelProvider は薄い実装になっています。

ViewModelProvider.Factory と ViewModelStore をインスタンスフィールドに持ち、

package androidx.lifecycle;

public class ViewModelProvider {

    public interface Factory {
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

    private final Factory mFactory;
    private final ViewModelStore mViewModelStore;

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        this.mViewModelStore = store;
    }

    ...
}

ViewModel を返す get() メソッドを持っています。

package androidx.lifecycle;

public class ViewModelProvider {
    ...

    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();

        ...

        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

    ...
}

ViewModelProvider はクラス名のとおり、 ViewModel のインスタンスを外部へ提供しています。

その提供の仕組みは単純で、 ViewModelStore から ViewModel を取得して返します。もし取得できなかった場合は ViewModelProvider.Factory に ViewModel を新たに生成してもらい、 ViewModelStore に格納してから返していることがわかります。

つぎに、ViewModelProvider が持っている ViewModelStore も見ていきます。

ViewModelStore

ViewModelStore も ViewModelProvider と同じぐらい薄い実装になっています。

package androidx.lifecycle;

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

get()put() のアクセス修飾子がデフォルトになっているので、アプリから ViewModelStore に対して ViewModel の操作をすることはできないようになっています。実際は ViewModelProvider の get() の中からアクセスされており、 ViewModel の取得は必ず ViewModelProvider の get() を経由する必要があることがわかります。

ここでもう一度、冒頭のコードを見てみましょう。

class HogeActivity : AppCompatActivity {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val activity: FragmentActivity = this
    val factory: ViewModelProvider.Factory = ViewModelProvider.NewInstanceFactory()

    // viewModel は Activity 回転前も後も同じインスタンス
    val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)
  }
}

ViewModelProvider コンストラクタの第 1 引数に渡しているのは FragmentActivity ですが、実際は ViewModelStoreOwner 型として ViewModelProvider は受け取っています。

その ViewModelStoreOwner の getViewModelStore() を呼び出して取得した ViewModelStore のインスタンスを、 ViewModelProvider は自身の mViewModelStore に代入していますね。

public class ViewModelProvider {

    private final ViewModelStore mViewModelStore;

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        this.mViewModelStore = store;
    }

この ViewModelStoreOwner も薄いインターフェースです。

package androidx.lifecycle;

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

FragmentActivity がどのように ViewModelStoreOwner#getViewModelStore() を実装しているか見てみましょう。

FragmentActivity implements ViewModelStoreOwner

FragmentActivity が実装している ViewModelStoreOwner#getViewModelStore() は次のようになっています。

package androidx.fragment.app;

public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner ... {

    private ViewModelStore mViewModelStore;

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        ...

        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
    }

    ...
}

FragmentActivity は ViewModelStore をインスタンスフィールドに持っていて、 getViewModelStore() を呼び出した場合は必ず mViewModelStore に ViewModelStore のインスタンスが代入されます。

さて、 ViewModelStore のインスタンスの取得方法を見ると、 getLastNonConfigurationInstance() から NonConfigurationInstances クラスのインスタンスを取得し、それに格納されている viewModelStore を取得していますね。

この getLastNonConfigurationInstance() は何者なんでしょうか?

Activity#getLastNonConfigurationInstance()

getLastNonConfigurationInstance() の実装は次のようになっています。

package android.app;

public class Activity extends ContextThemeWrapper implements ... {

    /* package */ NonConfigurationInstances mLastNonConfigurationInstances;

    @Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

https://developer.android.com/reference/android/app/Activity.html#getLastNonConfigurationInstance()

Retrieve the non-configuration instance data that was previously returned by onRetainNonConfigurationInstance(). This will be available from the initial onCreate(Bundle) and onStart() calls to the new instance, allowing you to extract any useful dynamic state from the previous instance.

ドキュメントを見るに、 onRetainNonConfigurationInstance() が返していたインスタンスgetLastNonConfigurationInstance() から取得できるようです。

ここでいう "instance" はおそらく Activity のことでしょうか、 Activity 回転前で onRetainNonConfigurationInstance() が返していたインスタンスを、回転後に getLastNonConfigurationInstance() を呼び出すことで、そのインスタンスを取得できるようですね。

このあたりで察しが付けられそうですが、 Activity が回転しても ViewModel が保持される仕組みは、この onRetainNonConfigurationInstance() がカギを握っています。

Activity#onRetainNonConfigurationInstance()

以下は、Activity が回転したときのライフサイクルをログに出力したものです。

// Activity が起動した
onCreate
  - Activity :132818886
  - ViewModel:249530701
onStart
onResume

// Activity が回転した
onPause
onStop
onRetainNonConfigurationInstance
onDestroy
onCreate
  - Activity :103312713
  - ViewModel:249530701 <- 同じ ViewModel であることがわかる
onStart
onResume

Activity が回転したときのライフサイクルの中では onRetainNonConfigurationInstance() というメソッドも呼ばれます。このメソッドは onStop()onDestroy() の間で呼ばれ、 Activity が回転して再生成されることがわかっているときに任意のオブジェクトを返しておくことで、再生成後に getLastNonConfigurationInstance() から任意のオブジェクトを取得することができます。

https://developer.android.com/reference/android/app/Activity#onRetainNonConfigurationInstance()

package android.app;

public class Activity extends ContextThemeWrapper implements ... {

    public Object onRetainNonConfigurationInstance() {
        return null;
    }

    ...
}

この onRetainNonConfigurationInstance() は Activity に定義されていますが、 FragmentActivity がオーバーライドしています。

package androidx.fragment.app;

public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner, ... {

    @Override
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }

    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
    }

    ...
}

FragmentActivity の onRetainNonConfigurationInstance() では NonConfigurationInstances クラスのインスタンスを返しています。そのインスタンスの中には ViewModelStore のインスタンスを格納していますね。

そうです、つまり、 FragmentActivity が回転した後に getLastNonConfigurationInstance() を呼び出すと、回転前に生成された ViewModelStore のインスタンスが格納された NonConfigurationInstances クラスのインスタンスを取得できます。

実際に FragmentActivity#onCreate() のコードを見ると...

package androidx.fragment.app;

public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner ... {

    private ViewModelStore mViewModelStore;

    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);

        super.onCreate(savedInstanceState);

        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
            mViewModelStore = nc.viewModelStore;
        }
        ...
    }
}

getLastNonConfigurationInstance() から NonConfigurationInstances クラスのインスタンスを取得し、 ViewModelStore のインスタンスmViewModelStore に代入していますね。

ViewModel が画面回転しても保持される仕組みとは

ViewModelProvider を経由して ViewModel のインスタンスを取得しておくと、 Activity が回転した後も、回転前と同じ ViewModel のインスタンスを取得できる仕組みになっています。

class HogeActivity : AppCompatActivity {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val activity: FragmentActivity = this
    val factory: ViewModelProvider.Factory = ViewModelProvider.NewInstanceFactory()

    // viewModel は Activity 回転前も後も同じインスタンス
    val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)
  }
}

このコードをもとに、 HogeActivity の回転前後の軌跡をまとめると...

回転前

  • FragmentActivity#onCreate() が呼ばれる
    • この時点ではまだ FragmentActivity の mViewModelStore は null
  • HogeActivity#onCreate() が呼ばれる
    • ViewModelProvider インスタンスを生成する
      • この時点で、 FragmentActivity#getViewModelStore() が呼ばれるため FragmentActivity の mViewModelStoreインスタンスが代入されている
    • ViewModelProvider#get() から ViewModel のインスタンスを取得できる
    • この ViewModel のインスタンスは、 FragmentActivity が持つ mViewModelStore に格納されている

回転時

  • FragmentActivity#onRetainNonConfigurationInstance() が呼ばれる
    • FragmentActivity が持つ mViewModelStore を格納した NonConfigurationInstances クラスのインスタンスを返している

回転後

  • FragmentActivity#onCreate() が呼ばれる
    • Activity#getLastNonConfigurationInstance() から、NonConfigurationInstances クラスのインスタンスを取得している
    • NonConfigurationInstances クラスのインスタンスに格納されている、回転前の FragmentActivity が持っていた mViewModelStore を、 回転後の FragmentActivity は自身の mViewModelStore に代入している
  • HogeActivity#onCreate() が呼ばれる

ということになります。

自分を変えられるのは自分しかいない

自分への戒めの日記。採用と自分の将来について。

数十年の時間を経て形成される人格は、自分自身が強い意志を持って変えようとしない限り、そう簡単に変わらない

こんな考えが、自分の中にある。

採用

自分以外の人とのフィーリングが極端に合わなかったら、 ああそういう人なんだなー 、と自分から引き下がり、お互いに不幸せにならないように離れて行動するのは、昔からの自分の出来事だ。

そういう考えなので、次の記事はなんとなく共感した。

i47.hatenablog.com

多様性は認めつつも”合わない”と思うメンバーはチームへ入れない、早くに外すべきです。 そして、気の合うメンバーでチームを構成すべきです。 それは、ネガティブメンバーにとっても新たな活躍の場所に所属するキッカケにも繋がります。

この記事ではネガティブメンバーにフォーカスしているが、ネガティブであろうとなかろうと合う合わないはあるので、ネガティブでなくとも当てはまるケースはある。

可能なら、採用の時点で篩にかける。お互いに幸せに暮らしたいからだ。

自分の将来

自分は比較的、ダメなほうの楽観的な性格を持っている。

まあなんとかなるだろう。 まあなんとか生きられるだろう。

そういう性格だから、フリーターでチャラチャラしてた 22 歳の頃には、就職先も見つけられてないのに 絶対に一人暮らしするんだ と無計画なセリフを威勢よく張り、その引っ越し後に適当に決めたブラックな企業からろくに収入も得られず半年で実家に帰る、という馬鹿なことをした。

金銭面なんかは特にそうだ。人生におけるお金の綿密な計画がない。これでよくもまあ、キャッシュレスだ、電子マネーとクレカだけの生活最高、と言い続けているわけだ。

しかし、いつまでもそんなんでいいわけがない。

将来、もし素敵な人と一緒に暮らすことになっても、ただただ不安を与え続けるだけだ。そんな状況下では相手を幸せにできない。お互いに幸せに暮らしたい。

こんな当たり前のことに気付かず大切なものを失って後悔する人生、自分を変えられるのは自分しかいない、変わるんだと強く決意した平成最後のハロウィーンの夜だった。

相談してよかったと感じる、相談にのる側の姿

ヒトは不完全な生き物で、だからこそ集団を形成し支え合って生きる生き物だ。 支え合って生きるために、時には、ヒトがヒトに対して、自身が考え持つネガティブな思考を共有し、解決方法を導き出すこともある。 ヒトはそれを「相d

...

やめやめ

狙ったようなクソエモ日記なんて少し時間が経った日には枕に顔面を埋めたくなるような産業廃棄物にしかならないのに、なに自己陶酔して "ヒト" とか書いてるんだ恥ずかしい

ということで、最近こういうことがありました。

相談しやすいという評価をもらった

畑の違う知り合い2人から、ありがたいことに自分の評価を頂戴しました。

あれなんですよ、 sho5nn さんは相談しやすいんですよねなんか

お話聞くの上手いねえ、モテるなあこれは

ありがてえありがてえ

何故そう思うのか、を教えてもらいました。

感情的にかつ論理的に相談にのっていた

すると..

相手の気持ちを汲みつつ、「自分もこういう経験があってね。。。」と披露しつつ、最後は「こう思うよね、だから大変だよね。でもこういう風になれたらもっといいよね。僕もそう思う、応援してるね。」っていう感じ。

なんかあんまり男らしくないというか、でも共感ばかりじゃないというか、程よい

ほほうなるほどなるほどそういう感じですがほほーん(まんざらでもねえ顔面

自分の解釈ではありますが、相談に対して感情的な共感や理解の意を表明しつつ論理的に解決策を伝えている、ということなのか?と受け止めました。

全く意識はしてなかったのですが、思い返すとそんな言い方をしているような気がしたので、相談にのる側はどういう姿でいることが相談する側にとって良いのだろうか、と考えました。

相談してよかったと感じる、相談にのる側の姿

誰しもが悩みを持ってますよね。持ってると思うんです。持っててください。僕は持ってます。

誰かに相談するときの、相談する側の気持ち、自分だったら、共感や理解を求めたいし具体的にどうしたらいいかという意見も聞きたいなあ、という、相談にのる側に対して感情的な共感や理解の反応と論理的な反応を求めます。

時には、相談の内容的に、相談する側自身が間違っていた場合もありえますよね。

そうであっても、最初は感情的な共感や理解があった上で、 でもそれは間違っているよ、なぜなら... という論理的な反応があると、相談する側としては、共感や理解をしてもらったという安心感の上で、自身が間違っていたという事実を素直に受け止められると思うんです。事実や正論を受け止めるのって堪えますよね。堪えると思うんです。堪えててください。僕は堪えます。

どういう相談内容にせよ、感情的な共感や理解の反応と論理的な反応で相談にのってあげると、 相談してよかったな と、 相談という行為自体に対する心理的障壁が低くなり 、以降も相談をしやすい環境であり続けられるんじゃないかな、と思った次第です。

Dagger によって Fragment に注入されるオブジェクトを Fragment.setUserVisibleHint の中で使う場合の注意

次のようなコードがあったとして、

class FooFragment : Fragment() {
    @Inject
    lateinit var bar: Bar

    override fun onAttach(context: Context) {
        AndroidSupportInjection.inject(this)
        super.onAttach(context)
    }

    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        bar.baz()
    }
}

ここで setUserVisibleHint(Boolean) のドキュメントを見てみると、

https://developer.android.com/reference/android/support/v4/app/Fragment#setuservisiblehint

Note: This method may be called outside of the fragment lifecycle. and thus has no ordering guarantees with regard to fragment lifecycle method calls.

Fragment のライフサイクルに関係なく呼び出されるということはつまり、 onAttach(Context) で初期化される前提の barsetUserVisibleHint(Boolean)の中で参照しようとする上記のコードは NG なのか。 ということで、どうしよっかって感じ。

NonNull -> Nullable

bar を Nullable にする方法が考えられるけど、本来 bar は NonNull 前提で扱いたい。 Fragment に対してコンストラクタインジェクションが使えないからしょうがなく onAttach(Context) で初期化せざるを得ないけど、 onAttach(Context) で必ず初期化されるからという絶対前提があることで NonNull で lateinit にしてたのもある。

class FooFragment : Fragment() {
    @JvmField
    @Inject
    var bar: Bar? = null

    override fun onAttach(context: Context) {
        AndroidSupportInjection.inject(this)
        super.onAttach(context)
    }

    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        bar?.baz()
    }

isInitialized

isInitialized で初期化されているか判定する方法が考えられる。

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/is-initialized.html

絶対初期化されるから!と宣言してるのに isInitialized で判定するのは矛盾している気がする。 判定処理の実装漏れの要因になりそうだけど、そもそも setUserVisibleHint(Context) を使う上では setUserVisibleHint(Context) の挙動は知っておこうぜみたいなところがあるので、やむなしって感じ。

class FooFragment : Fragment() {
    @Inject
    lateinit var bar: Bar

    override fun onAttach(context: Context) {
        AndroidSupportInjection.inject(this)
        super.onAttach(context)
    }

    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        if (this::bar.isInitialized) {
            bar.baz()
        }
    }
}

まとめ

setUserVisibleHint(Context) 以外でも bar を参照するのであれば後者を選ぶのがいいのかな。 setUserVisibleHint(Context) のために Nullable にするのは微妙だけど、どちらを選んでも微妙なところ。 isInitialized が実装された背景が気になる。

未婚の自分が「エンジニアとデザイナの夫婦 FM」 第 1 , 2 回目を聴いて

聴きながら感想を書きなぐろう

パパエンジニアが日々どうやって家族と過ごしたり、仕事したり、子育てしたり、技術を磨いたりしてるのかなー知りたいなーってところから、そんなpodcastないなーってところで勢いでやってみました。

note.mu

note.mu

こうして聴き始めました

書きなぐった文章の前に、聴いたキッカケを。

31 歳で未婚の自分。 20 代と比べて金銭的余裕は生まれ、未婚がゆえ自分の自由時間は多い。

そんな中で、将来は結婚して家庭を持ちたいという願望もあり、そうなるために 1 つの参考として、センパイ方の話は掻い摘んでおきたいな、という気持ちから聴き始めた。

PodCast を始めた経緯が、他のエンジニアさんはどのようにして家庭にコミットしているのか?なかなか自分の時間が取れないがみんなどうしてる?というものだったので、将来家庭を持ちたい者としては気になる部分。

生活のスケジュールは?

こちらの夫婦にはお子さんが 2 人いて、どちらも保育園に通っているとのこと。

よく言われていることなので想像は付いていたけど、朝5時に起きて支度して子供を保育園に送り、そのあと仕事を始め、仕事が終わったら保育園に迎えに行き、ご飯を食べさせお風呂に入れて寝かしつけて、さあ寝るまでは自分の時間だ!というライフサイクル。

今の自分のライフサイクルとは昼夜逆転といっても過言ではないものだったけど、まあそのあたりは「知ってる!!!実践したことないけどな!!!」という感じ。

大変そう(こなみかん)。

といえど、子供を持ったら、必然と無意識になっていくんだろうなあという、漠然とした "慣れ" が出てきて自然に適応できていくのかな。

生活スケジュールを確立する上で、あっそれよさそうって思ったのは、子供 2 人が 同じ保育園に通っている ということ。なるほど〜〜って感じ。送り迎えがより楽になりますねえ。子供が 1 人だとしても、職場や家に近い場所、というやり方でも楽になり、結果的に自分の時間が増えそうだ。うーん学び。

日々の学習どうしてる?

朝 4 ~ 5 時ぐらいに勉強するとのことで、家庭にコミットしたい状況だからこそ、そのぐらいしか時間が取れないのか〜、と。

自分は、じっくり時間をかけて自分の体や脳に知識を染み込ませていくような勉強スタイルなので、短時間で良い感じの学習効果を得られるスタイルを確率させないと、子供を持ったときに、学習しても実感がでなさそうだな〜という感想を持った。

逆に、寝かしつけたあとに勉強はどうだろう?という話に対して、そもそもその時間になっていたら疲労困憊の状況であり、学習効率は皆無であろうとのこと。わかる。人間、睡魔には勝てない。むしろ睡眠の質によって快適な生活が左右されるので「学習 < 睡眠」の評価式は確定も同然。

学習効率という意味でこれは優先度高いなと思うのは、仕事の中で技術的チャレンジができる環境に自分の身を置けることなのかな。やっぱり個人でやるより仕事でやったほうが、気合いの入れ方 1 つでも違うし、売り上げに繋がり回り回って自分の給料に影響があると思うと、最短最適な学習を無意識にするんだと思う。そういう環境を実現できる会社を選んでいくのも、家庭を持つ上で重要だよね、と再認識した。

子供を持つことによる価値観の変化

デザイナーである奥さまは、出産を契機に感性や興味の対象などがガラッと変わったとのこと。

子供を持ったことがないのでわからないが、もし子供を持ったとき、たぶん「あ〜自分の親もこう感じてたんだな」と、自分の昔を思い出し、あの時の親の気持ちを想像することはありそう。けど、このへんは、生活スケジュールだとか学習リズムなど形や数字で表せられるものとは違う、ヒトの気持ちの部分なんで全然想像ができなかった。

生活と仕事との両立で意識してること

コード、いつまで書き続けたいですかね。

老いても書き続けたい」という声をぼちぼちよく耳にするたびに、 それって言うても今の自身の脳みそから出てきた言葉なんだよね っていつも心の中でひねくれながら思っちゃう自分がいる。

なので、自分の中での "仕事に対する思い" としては 今やれることを大切にしたい という考えがある。将来、何かのキッカケでカメラマンに本気で取り組むこともあるだろうし。

ただ、どのような仕事にせよ、妻と子供を食べさせていけて将来子供が大きくなっても安定した家庭を築けるだけの賃金をもらうこと、は前提に置いておきたい。それが結果的に仕事と生活を両立する上での土台となるから。とはいえ、お金を管理する部分は少し苦手なので出来れば妻に任せたい部分がある。おこづかい制でも全然いいですので!

雑感

会話の内容だけ見れば感じられないけど、合間の笑いだったり相槌だったり、旦那様と奥様の仲の良さが滲み出てる会話だった。第 2 回目はデザイナーである奥様のパーソナリティがわかる会だったのもあり、夫婦の現実味を感じられる PodCast で良かった。あと、無意識に奥様が司会進行してるような感じが微笑ましかった。

人生って感じ。

セカンドキャリア

将来、何をしているのかわからない自分が「昔はこういうときがあったなあ」と、

過去を思い返してほしいがために、最近の心境を将来の自分へ向けて残しておく。


この業界に入りソフトウェアエンジニア職になってから丸6年が経とうとしています。

ノルマ未達成だと月のお給料が5万になってしまうブラックな仲介業を営む不動産屋で体を壊し、物件の内見時に活用していたスマホの地図アプリに感激したことから、もともとやってみたかったプログラミングでモノを創る仕事に身を寄せてからというもの、元気に過ごしてますか。

今、自分は、この職に対して少しばかり、疲れあるいはストレスのようなものを感じています。

...

この業界の技術進歩はとにかく早いです。

少し前に修得したと思った技術は、更にパワーアップして帰ってきます。いつでもワクワクできます。

この業界に入ってからは、目をギラギラと輝かせながら新しい技術を追ってプログラミングをしていましたね。 Android が 2.3 から 3.0 / 4.0 にアップデートされたとき、青くてカッチョいい Holo Theme が好きでした。 5.0 から登場した Material Design には酔狂な気持ちでいつも見つめていました。まわりのエンジニアと切磋琢磨して技術を吸収し成長していく環境、一見、青臭いドラマのような言い回しだけど割と嫌いじゃなかったですよね。

───── そして現在。

今は何かに追われるかのような感覚で新しい技術を学習し続けています。

その感覚があることで、この職に対して少しばかり、疲れあるいはストレスのようなものを感じているようです。

この職に飽きてしまったけど、いまのところは売り手市場であるこの業界にしがみつきたい、という気持ちで居続けているからな気がしますが、本当のところはよくわかりません。

コレだという理由が確定しておらず、うまく言語化が出来ていない状態ですが、 部屋の中で流しそうめんをやる ぐらいには元気なので大丈夫です、心配ご無用です。

...

最近、こんな記事を見ました。もう忘れてると思うので引用もしておきますね。

hikakujoho.com

─── お二人はゲームイベント企画などを行う会社「忍ism」も経営されているとうかがいました。会社経営で食べていくというのは考えているのでしょうか。

ももち それは自分たちの将来の目標ですね。もちろん、プロゲーマーとして賞金もお給料もあるんですけど、プロゲーマーになった1年後くらいから"引退したとき自分たちに何が残るんだろう"と考えていたんです。そこでゲームの啓発活動を始めるために今の会社を作ったんです。

チョコ もちろん好きだからやっている、という大前提はあります。私はイベント運営が好きで、ももちは人に教えるのが好きで後進の育成がしたいという思いはあったので。

ももち 「ゲームをプレイする」以外でゲームに関われるセカンドキャリアが欲しかったんです。本当は引退してから考えればいいことなのかもしれないんですけど、それだと遅いですし、プロゲーマーとしての仕事がいつなくなるかも分からないですし。

ももちプロゲーマーが言い放った最後の文章。

視線移動が止まりましたよね。止まりました。天井を仰いだんです。転職、大いにアリだと思いました。

この業界に関われる今とは別の何か、を仕事にするのも悪くないと思います。

今の自分は肩までカメラ沼に浸かっています。自分の思うよう好きなように写真を撮っています。フォトグラファーは軌道に乗るまでが非常に大変だと聞いてますが、好きなことを仕事にするのはこれが初めてじゃないですよね。

DroidKaigi というカンファレンスの運営スタッフでもやりたいことをやらせてもらってます。カメラヌマーなので撮影チームで好きなように撮ってますよね。 DroidKaigi に限らずイベント運営業みたいな仕事もアリなんじゃない、と無責任に思っているんです。

hikakujoho.com

この業界に入りソフトウェアエンジニア職になってから丸6年が経とうとしています。 2018/03/30 には 31 歳になりました。

30 代が一番楽しいと今は豪語していますが、 30 代後半になったら 40 代が一番楽しいと言っていることでしょう。

どちらに転んでも成るように成ると思うので、疲れあるいはストレスのような今の気持ちを感じつつも、もう少しだけセカンドキャリアについて自問自答して考えてみたいと思います。

Flutter Meetup Tokyo ♯1 で LT した

Flutter Meetup Tokyo #1 - connpass で LT した

LT 要約

speakerdeck.com

イベント雑感

Flutter フレームワークの仕組み、マルチプラットフォーム開発やネイティブブリッジまわりの tips など、Flutter 経験者による発表がある傍ら、最近 Flutter を始めた方々からの発表などがあった。

参加者アンケートにおいても Flutter 経験時間にばらつきがあったので、まだまだ Flutter はこれからって印象だった。

会場のスクリーンは3枚ぐらいあったように見えたけど、当日は中央1枚で運用していた模様。このあたりは、参加者人数が多くて席が横に広がっていたのを鑑みると、両サイドにあるスクリーンも使ってよかったのかなと感じた。(両サイドにあったっけ?ちゃんと確認はしていない)

初回イベントに参加できたので運営・登壇者・参加者の方々に感謝と、フラッタラー目指していこうなというお気持ちが強まった次第です。