2023年振り返り

ひっそりと振り返り

仕事

今年の3/4はAWSでのインフラ構築(?)やNestJS + TypeScriptでバックエンドとかやってたんですが、転職して残りの1/4はAndroidエンジニアに戻り後半からはAndroidを楽しくやらさせていただきました 。
来年も引き続きAndroidやっていきます👊

買ってよかったもの

ゲーム

今年も色々なゲームをやったんですが、特に印象に残ったのが太閤立志伝

www.gamecity.ne.jp

小学生の頃に父親がPS2でやっていたのを見てプレイし始めた思い入れのある作品で、また一からやり始めました。

PC版だと専用スクリプトでイベントを作成することができるので、試しに作ってみたら案外いけました。(JIS使わないといけないのがネックですが...)

1-2週間くらいでプレイ時間が40時間越えくらいになっていたので、だいぶ沼ゲーです。

お酒

友達の付き添いで酒屋に寄った際にウィスキーに興味を持ち、響を買ったところ沼にハマりました。 知り合いとバーに行ったりして色々な種類を飲んだんですが、今でも響が一番好きです。 丸氷の製氷器なんかも買って宅飲みを楽しんでます。

旅行

今年はたくさん旅行したので雑多に

大阪

1月下旬に大阪に行ってきました。ヨルシカのライブが大阪であり大阪自体も行く機会が少ないため、ちょうどよい機会だなと思い遠征含めて楽しんできました。

神奈川

割と近場の横浜へ。前職の後輩が横浜近辺に住んでいたので観光案内をしてもらいながら最後は後輩宅でタコスパーティ。赤レンガ倉庫や中華街などを一日で周り、かなり充実感ある一日を過ごせました。

宇都宮

ただただ宇都宮で餃子が食べたくなり無計画で友達と宇都宮旅行。餃子食べた後、都度都度次行くところを決めて一日満喫。ぶらり途中下車みたいな旅であまりない経験でしたが、とても楽しみました。まさしく一期一会の旅でした。

新潟

高所恐怖症でなんだかんだでウィンタースポーツをやったことがなく、友人指導のもとスキーに初挑戦。 めっちゃ楽しかったんですが、来年は年始に予定が詰まっており行けない可能性があるのが悔やまれる...

宮城

仙台住みの友人とヨルシカのライブに行くため仙台遠征。牛タンは激混みしそうなので夜に焼肉で牛タン満喫しました。 仙台城にも行ってみたんですが、意外に道のりが困難でしんどく戦国時代での城攻めが難しかったんだろうなと思いました笑

北海道

ウィスキーにハマり余市蒸溜所で蒸留所限定モデルが買いたくなり北海道に訪れました。蒸留所以外にも洞爺湖周りも見て回り、自分の中では北海道をちょっぴり満喫できた旅行でした。

バーが併設されてるホテルに泊まり、最高の旅行でした。

福島

大学の頃の友人と、いつかまた会津に行こうと話をして未だ達成できていなかった会津旅行が遂に達成。 学生時代毎週食べていた懐かしのラーメンを食べて大学時代に思いを馳せる旅行でした。

福岡・大分

一昨年、去年とISUCON旅行ができなかったので今年は開催。 チームメンバーの1人が福岡に住んでいたのと、自分自身も福岡に行きたかったのもあり福岡・大分旅行をすることに。

ISUCONは別府を満喫しながら挑戦しました。

終わりに

今年はフットワークを少しでも軽くしたいなと思い、旅行を増やそうと思ったのですが改めて振り返るとたくさん旅行したなという印象です。 今年やりきれなかったことややりたかったことを目標に来年も頑張っていきたいなと思います。

Compose for Desktopでデスクトップアプリを作って画面遷移をする

この記事はKotlin AdeventCalendarの4日目の記事です

qiita.com

Compose for Desktopとは

www.jetbrains.com

端的に説明すると、JetBrainsが提供するKotlin向けのUIフレームワークです。
AndroidのUIフレームワークであるJetpackComposeを利用し、各Platformにネイティブなデスクトップアプリを作ることができます。

簡単なアプリを作ってみる

まず最初にIntellijからプロジェクトを作成します。

これだけで下記のような画面が作成できています

UI周りはJetpackComposeと同様に書くことができますが、今回は画面遷移にフォーカスした話のため割愛します。

画面遷移してみる

本来JetpackComposeであれば navigation-compose を利用することで画面遷移を実装することができるのですが、 navigation-composeAndroidでしか使えないためCompose for Desktopで使うことはできないです。 公式的には特定のライブラリを強制していないですが、参考実装としてDecomposeを使って画面遷移の実装例を上げてくれています。

The Jetpack Compose navigation library (navigation-compose) is an Android-only library, and so can not be used together with Compose for Desktop. Our general attitude is not to “force” people to use a particular first-party library. However there are third-party libraries available. One could consider Decompose as possible solution.

ref) https://github.com/JetBrains/compose-jb/tree/master/tutorials/Navigation

ということでDecomposeを使って画面遷移を実装していきます。

今回はRootでの遷移先として HomeHelloWorld を設定します。

interface RootComponent {
  val childStack: Value<ChildStack<*, Child>>

  // 遷移処理
  fun navigateToHome()
  fun navigateToHelloWorld()

  // 画面
  sealed class Child() {
    object Home : Child()
    object HelloWorld : Child()
  }
}

具体的な実装を書いていきます。

class DefaultRootComponent(
  componentContext: ComponentContext,
) : RootComponent, ComponentContext by componentContext {

  private val navigation = StackNavigation<Configuration>()
  override val childStack = childStack(source = navigation, initialStack = { listOf(Configuration.Home) }, childFactory = ::child)

  private fun child(configuration: Configuration, componentContext: ComponentContext): RootComponent.Child {
    return when (configuration) {
      is Configuration.Home -> RootComponent.Child.Home
      is Configuration.HelloWorld -> RootComponent.Child.HelloWorld
    }
  }

  override fun navigateToHome() {
    navigation.replaceCurrent(Configuration.HomePage)
  }

  override fun navigateToHelloWorld() {
    navigation.replaceCurrent(Configuration.HelloWorldPage)
  }

  // Decomposeに合わせた画面の設定
  sealed class Configuration : Parcelable {
    @Parcelize
    object Home : Configuration()
    @Parcelize
    object HelloWorld: Configuration()
  }
}

ではここから画面遷移の実装を入れていきます

@Composable
fun RootContent(component: RootComponent) {
  val childStack by component.childStack.subscribeAsState()
  val lifecycle = LifecycleRegistry()

  Children(
    stack = childStack,
  ) {
    when (it.instance) {
      is RootComponent.Child.Home -> HomePage(component)
      is RootComponent.Child.HelloWorld -> HelloWorldPage(component)
    }
  }
}

subscribeAsStatechildStack が更新するたびにRootContentがレンダリングされ、現在の子要素が表示されるようになります。 あとは navigateToXXX を画面遷移したいタイミングで実行すれば完成です。

引数を渡したい場合

単純な画面遷移の実装は完了しました。ただ商品一覧から商品詳細に遷移する場合のように、画面遷移では特定の引数を渡して上げたくなるケースが多々あります。 その場合下記のように実装していくことで解決できます。

まず画面遷移に使う navigateToXXX で引数を渡して、Child で該当の引数を受け取れるように修正していきます。

interface RootComponent {
  // etc

  // 画面
  sealed class Child() {
    object Home : Child()
    data class HelloWorld(val text: String) : Child()
  }
}

class DefaultRootComponent(
  componentContext: ComponentContext,
) : RootComponent, ComponentContext by componentContext {

  //etc

  override fun navigateToHelloWorld(text: String) {
    navigation.replaceCurrent(Configuration.HelloWorldPage(text))
  }

  // Decomposeに合わせた画面の設定
  sealed class Configuration : Parcelable {
    @Parcelize
    object Home : Configuration()
    @Parcelize
    data class HelloWorld(val text: String): Configuration()
  }
}

最後に伝播して渡ってきた引数を画面切り替え時に利用します。

@Composable
fun RootContent(component: RootComponent) {
  val childStack by component.childStack.subscribeAsState()
  val lifecycle = LifecycleRegistry()

  Children(
    stack = childStack,
  ) {
    when (val current = it.instance) {
      is RootComponent.Child.Home -> HomePage(component)
      is RootComponent.Child.HelloWorld -> HelloWorldPage(component, current.text)
    }
  }
}

こうすることで実際の画面遷移実行時の引数が画面切り替え時に利用することができます。

まとめ

以上がCompose for Desktopの初歩的な実装とDecomposeを利用した画面遷移の実装でした。 今回はRootのみの画面遷移を例として上げましたが、場合によっては子の画面遷移Componentを作成して画面遷移をネストできるので状況に応じて使っていければと思います。

今年の振り返り 2021

f:id:slme9364:20211221082619j:plain

買ってよかったもの

  • WF1000-XM4
  • Pixel6
  • Fire TV Stick
  • Nile洗顔 & 化粧水
  • Razer BlackWidow V3 TKL
  • 音楽的同位体 可不(KAFU)
  • KORG microKEY Air-25

読んでよかった本

ゲーム

イベント

仕事, 技術

DMMからスタートアップに転職しました
Android -> Backendになり今はKotlinでSpringNativeやってます
触ってる技術スタックはこんな感じ

  • Kotlin
  • SpringNative
  • Exposed
  • GCP

現在フル稼働できるエンジニアは自分だけなのでwebフロント以外は自分が見てます

プライベート

DTMしたりゲームしたり楽器弾いたりと、色々と趣味に没頭しまくった一年でした。
ISUCONやSECCON Beginnersに参加したりと色々挑戦したりしますが、それ含めても趣味に没頭しまくったと思います

来年の抱負

  • Flutter
  • Androidネイティブやっていき
  • DTMちゃんとやってギターも弾く

Kotlinの型周りのちょっとした話

この記事は Aizu Advent Calendar 2021 - Adventar20日目の記事です

adventar.org

特にネタがなかったので実際のプロダクトコードで罠にハマった話をします
記事にあるコードはすべてKotlinです

型パラメータ

Kotlinだとおなじみのスコープ関数の let を例に上げてみます

public inline fun <T, R> T.let(f: (T) -> R): R = f(this)

このとき T, R が型パラメータにあたり、コンパイル時に実際の型が当てはまります

またこのとき、 T, R に上限境界がないため、任意な型になりえます
KotlinのNullableを型パラメータ上で表現したい場合は、上限境界を Any にしておきましょう

fun <T> functionA(value: T): String? {
    return value?.toString()
}

fun <T : Any> functionB(value: T?): String? {
    return value?.toString()
}

型消去とreified

上記の型パラメータは型消去があるため実行時には基本的に参照することができません
一応Kotlinには reified があるため実行時に参照することが可能です

inline fun <reified T> function(value: T): Boolean {
    return value is T
}

reified を使うと以下のことができます

  • is, asなどの型チェック、キャスト
  • ::class, ::class.javaなどのリフレクション、型クラス取得
  • 別の関数への型パラメータとしての利用

注意点として inline fun なのでインライン展開されるのを意識してください
(例えばJavaから呼び出しとかはできないです)

罠にハマった話

ここがこの記事を書こうと思った本題です サンプルコードなので命名や実装は適当です🙏

open class C()

open class D() : C()

open class A<T : C>(vararg value: T) {
    val clazz = value::class.java.componentType
}

class B<T : C>() : A<T>()

fun main() {
    val a = A<D>()
    val b = B<D>()
    
    println(a.clazz) // class D
    println(b.clazz) // class C
}

脳死で書いていたら無事罠にかかりまして、b.clazz はDではなくCなんですよね
reified を使って回避しようにも、コンストラクタに使えないのでダメですね
色々解決策はありましたが、結構コード自体闇が深くなりそうなので結果的には全く別の実装をしました
戒めとして記事に残します

今年の振り返り 2019

買ってよかったもの

  • ViewSonic 32インチ ディスプレイ (アンチグレア)
  • モニターアーム
  • 横幅2mくらいのデスク
  • 珪藻土バスマット
  • ノイズキャンセリングワイヤレスイヤホン
  • 人工観葉植物 + 間接照明
  • ダメ着4G (着る毛布的な)

今年読んだ中でよかった本

  • Web API The Good Parts
  • DEEP WORK
  • UNIXという考え方
  • Android テスト全書
  • わかる!ドメイン駆動設計 ~もちこちゃんの大冒険~

ゲーム

イベント

  • マジカルミライ2019
  • TGS
  • DIVE XR FESTIVAL
  • ほんだのバイク 1周年イベント
  • グラブルフェス
  • UNSER Tour at Tokyo DOME (UVERWorld)
続きを読む

Gradleでライブラリのアップデート自動検知

この記事はAizu AdventCalendar 8日目の記事です。

adventar.org

はじめまして。元会津大学生のスルメです。
いまはDMMでAndroidエンジニアやってます。

はじめに

Gradleでマルチモジュールで開発しているとライブラリのバージョン管理を一か所でやりたくなります。 そのためversion.gradleといったようなファイルで一括管理をして各モジュールでそこで定義した変数から値を取得しるような実装をします。 このようにするとversion.gradleに使用ライブラリや言語のバージョンなどが集約されて変更が容易になります。 ただし、version.gradleはあくまで変数が書いてある場所でしかないのでIntellijやAndroidStudioなどでライブラリのアップデートを検知できません そのため今回はCI時にライブラリのアップデートを自動で検知す仕組みを導入したのでそれについて話していきます。

CIで自動検知

今回導入したものはこちらです。

github.com

こちらを導入するとgradle taskが新しく増えます

gradle dependencyUpdates
./gradlew dependencyUpdates

これだけでライブラリのアップデートを自動で検知してくれます。またデフォルトだっとtxtをはいてくれますがオプションを付けるとjson, xmlをはいてくれます。 これで準備は完了です。 あとは .circleci/config.yml でこのコマンドを実行すればライブラリのアップデートを自動で検知してくれます。 ただこの段階だと検知した物を外に投げる手段がありません。そのためDangerを用います。

github.com

あとはjsonで出力したものをdanger側のコードで解析してPRのコメントに流せば完成です!!!

./gradlew dependencyUpdates -Drevision=release -DoutputFormatter=json

f:id:slme9364:20191203083326p:plain

注意事項としてgradle-version-pluginsは初期設定だと最新版を検知するので設定を加えないとα, β, rcなども対象に含まれます。個人プロダクトの場合は問題ないかもしれませんが業務プロダクトだとそうもいかないのでrelease版のみを検知するようにしましょう。

公式docにも書いてありますが特定versionをrejectすると動きます。

def isNonStable = { String version ->
  return ['ALPHA', 'BETA', 'RC', 'EAP', 'DEV'].any { it -> version.toUpperCase().contains(it) }
}
dependencyUpdates {
  rejectVersionIf {
    isNonStable(it.candidate.version)
  }
}

ちなみにoutputしてきたjsonを解析して警告するとこのDangerfileの実装です。Ruby力は皆無なのでコードがクソなのは許してください

require 'json'
Dir.glob("**/dependencyUpdates/report.json").each { |report_file_path|
  results = File.open(report_file_path) do |io|
    JSON.load(io)
  end
  available_update_lib = results["outdated"]["dependencies"]
  available_update_lib.each { |lib|
    report = "#{lib["group"]}.#{lib["name"]}: #{lib["version"]} -> #{lib["available"]["release"]}"
    warn(report)
  }
}

新卒AndroidエンジニアがAndroidのチームリーダーをやった話

この記事は「DMMグループ Advent Calendar 2019」の7日目の記事です。

qiita.com

今年新卒でDMMに入社いたしましたスルメです。
僕は今新規事業系の部署に配属となり四ヶ月くらいが経ちました。
現在AQUIZっていうアプリを作ってます。

aquiz.jp

自分がチームに対して何を思ってどう行動したか時系列順に書いていきます。

  • 初期リリース
  • 大型アップデート
  • その後
  • これから

チームリーダーになった経緯

チームリーダーになった経緯を軽く説明します。
もともと自分が配属になったチームは、横断支援チーム的な人にがっつり依存して開発されていました。そのため事業部としてはそことの依存を切って事業部の人にチームリーダーをやって欲しいという意図があり自分がやることになりました。実は配属2日目に「チームリーダーをやって欲しい」と言われて当時は戸惑いしかなかったですが、「挑戦するだけするか」的な精神で取り合えず承諾しました。経緯も何もないですねw

ちなみに弊社はDMM.ESSENCEというのを掲げています。

inside.dmm.com

特に

  • 本気の失敗を肯定する
  • 好奇心を忘れない

あたりを意識した挑戦になったので、良い挑戦だなと思いました。

初期リリース

先ほども言った通り、もともと自分が配属になったチームは横断支援チーム的な人にがっつり依存して開発されていました。そのためこのフェーズに関しては新規実装を支援部隊の方に任せてコードの理解や細かいバグ修正などをメインに行っていました。さらにiOS版がAndroidリリース予定日より一カ月早くリリースしているので現行のiOS版が初期リリースから先行して実装してある機能も並列で実装する必要がでてそこら辺の実装なんかも担当したりしました。
このフェーズは自分の実装担当のほかに(横断支援チーム含む)チーム全体のタスク管理、テストケースの作成、申請周りなどなどやることが多く一番忙しかったです。
この段階だと正直チームの成熟度などはわからないため個々のチームメンバーの力を見ると同時にリリース最優先で動いたため、特にこれといった行動はしてないです。

大型アップデート

初期リリースが終わり一段落かと思ったらそうでもなくて、次の大型アップデートの準備が始まってました。支援部隊の方も初期リリース後は極力関わらないようになり、初期リリースという一つの山を超えたので自分が最低限必要だなと感じたものだけ作りました。

  • PRのテンプレート
  • issueのテンプレート
  • branch運用

あたりです。
開発方針やフローに関しては支援部隊の人が既に作ってくれていたので、自分が初期リリースで足りないと感じていたPR, issueのテンプレ作成を行いました。まだ自分含めて開発メンバーが未熟なのでテンプレを作っておかないと記述漏れが発生することが多々あったためです。実際ここら辺がテンプレ化することで記述漏れはなくなりスムーズにレビューができたりできました。特によかったのはセルフレビューという文化です。もともと支援部隊の人が入れてくれていた文化なのですが、自分で再度読み直すことで単純なミスコードがなくなり指摘箇所が減るのでコストが減ります。概要はこんな感じです。

f:id:slme9364:20191203100141p:plain

どの項目もPR出す前に当たり前にやっていないといけないことですが急いでたりすると忘れてしまうこともあるのでPRのテンプレに入れておくには良い文化だなと思いました。

issueのテンプレに関しては「どのPBIと関連しているか」や「解決策はどんな感じか」、「アラートライン」などをテンプレートとして提供しています。各々が未熟なのでここら辺を明示することで「なにも声があがらない」といった問題に対処しやすくなります。
プログラミングをする前に徹底的に考えてから実装する」といった文化が根付いてないのもあるので解決策をこの段階からある程度提示してもらうようにもしています。

ただこれらのテンプレ作成などは、所詮自分の暗黙知を表出化させて形式知に変えただけです。

f:id:slme9364:20191203100800p:plain

大型アップデートを終えて、そしてこれから

大型アップデートを終えて

大型アップデートを終えて上記の文化はチームに馴染み色々と改善していきました。この大型アップデートを通してチーム全体の成熟度を把握したので次は「最低限守って欲しいルール」を作りました。ただここら辺のルールは本当に最低限のことしか書いてないので今後チームに合わせてどのレベルまでルール化していくかは検討中です。

これから

僕自身はチームに一番必要なのはルールや文化だと思っています。
先ほど言った通り、まだ自分は最低限守って欲しいことしかルール化してないです。 なのでここから、チームがもっと良くなるためには、あるいは各々の力を最大化していくためにはどうしていけば良いかを考えていかなくてはなりません。
そこで恩恵があるのが新卒やインターン生です。ここの層はあまり特定の会社に染まってなくて色んな会社を見ていることもあり思考も新鮮なので鋭い疑問を投げかけてくれます。 実際、弊Androidチームではインターン生が投げてくれた疑問から新しく文化として定着したものもあります。ライブラリのアップデート自動検知であったり、実装に関する別アプローチの提案だったり色々あります。こういった疑問をちゃんと受け止めてパワーを持ってる人が検討するのが一番健全です。

まとめに入りますが、今回自分はAndroidチームリーダーをやって学んだことは色々あります。
そもそもAndroidでちゃんと開発するのは初めてなのでマルチモジュールやDagger、Androidチーム開発も学びになりましたし、Android以外にもAPI設計やデザインレビューもあるのでそういったところも勉強する良いきっかけになりました。また今までこういったチーム開発もはじめてなので、スコープが大きくなって取り組むことも変わってきたのも一つの変化ですね。

まだまだ未熟なところがありますが、これからも頑張っていきたいです。

参考リンク

以下自分が色々と参考になったリンク集です

プログラミングの前に、徹底的に考えよう - ボクココ

Google エンジニアリング・プラクティス ドキュメント | eng-practices