これはフェンリル デザインとテクノロジー Advent Calendar2025 17日目の記事です。
昨年に引き続きアドベントカレンダーに登板しております、NILTOエンジニアの小西です。
前回は大学生時代に慣れ親しんだGoogle Maps APIについておもしろおかしく書かせていただきましたが、今回は激痛の走る思い出に踏み込もうかと思います。
筆者はかつて卒業論文でAndroidアプリの制作に挑戦しました。当時のプログラミング経験といえばRubyとJavaScriptをそこそこ触った程度で、アプリ開発は未知の領域。
「難しそうだけどアプリ作れたら絶対かっこいいし、社会人になる前に何か成し遂げたい!!」
と大層な意気込みで始めたものの、思うように進みません。
手当たり次第にコードを触り時間を浪費する日々。微動だにしないアプリに何度絶望したことでしょう。最終的に、想定していたものとかけ離れた出来となってしまいました。
それから年月は過ぎ、筆者はなんだかんだ社会人4年目に。いよいよこの失敗を成功体験で上書きしないといけない気がするーーーーーーー
ということで、今年は漆黒の黒歴史★卒論アプリのリベンジ がテーマです。どうぞお楽しみください。
注意事項
- 本記事で紹介している内容は執筆時点(2025年12月17日)での情報であり、今後の動作を保証するものではありません。Android開発は技術の変化が早く、数年でベストプラクティスが変わることも珍しくありません。
適宜、公式ドキュメントを参照し、最新の情報をご確認ください。 - また、本記事は筆者の個人的な経験に基づいており、当時の失敗を振り返る内容も含まれます。あくまで「4年前の自分と今の自分の比較」という視点でお読みください。
リベンジの基準
アドベントカレンダー執筆にあたり、リベンジの基準を以下の通り設定しました。
- 当時作ったアプリ以上のものを作る
- アプリのアイデアはそのまま
- 最新の技術を採用する
エンジニアを生業としている以上、学生時代の自分に負けるわけにはいきません。純粋な「成長の差」を測るため、あえて同じお題で使って作り直すことにしました。見てろよあの頃のわたし...!
4年ぶりに Android の世界に戻ってみると、なにやら色々と変わっていました。 Android Studio(IDE) の見た目もそうですが、印象的だったのは UI実装方法です。かつて筆者は XML ベースでコードを書いていたのですが、 Jetpack Compose が登場していました。
せっかくだし面白そうなので今回の開発に採用してみることにしました。
XMLとJetpack Compose の違い
4年前はUI(画面のレイアウト)とロジック(処理)を別々のファイルに書く仕組みとなっていました。具体例があるとわかりやすいので、ボタンを1つ配置するのを例として比較します。
4年前
2つのファイルを行き来して実装する必要があり、しかも書き方が異なります(ゼロ知識で飛び込んだ学生にはつらかったのです)。
<!-- activity_main.xml --> <Button android:id="@+id/timer_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="タイマー" />
// MainActivity.kt val button = findViewById(R.id.timer_button) button.setOnClickListener { // タイマー画面を開く処理 val intent = Intent(this, TimerActivity::class.java) startActivity(intent) }
今
一方で、Jetpack Compose では UIもロジックも同じファイルで書くことができます。ふだんコードを書かない方も、すっきり具合をお分かりいただけるかなと思います。
@Composable fun MenuScreen() { Button(onClick = { showTimer.value = true }) { Text("タイマー") } }
Webがわかる人向けに説明するなら…
- XML = HTML:レイアウトだけを記述
- Jetpack Compose = Next.js(React):UIとロジックをまとめて書ける
筆者は一時期 Next.jsを触っていたこともあり、Jetpack Compose のコードを読んだとき「あ、Reactっぽいかも」とピンときました。フロントエンドの知見がここで活きるとは棚からぼたもち。
こういう気づきがあるからエンジニアって楽しいなと思います。
実装内容
今回のアプリでは、こんな機能を実装しています。
- メニュー画面:デジタル時計+ ボタン
- タイマー:1、5、10、30分を計測
- カレンダー:今月のカレンダーを表示
- カウントダウン:選択した日付をカウントダウン


すべてを解説すると記事が長くなってお互いに大変なので、今回は画面遷移の仕組みに絞って説明しましょう。
(今回作成したアプリのソースコードは GitHub で公開しています。興味があれば覗いてみてください!)
5Ni4/RevengeMyAndroidApp (GitHub)
なぜに画面遷移?
みなさんが普段使っているアプリを思い出してほしいのですが、画面が1枚しかないアプリってあるでしょうか。ほとんどないはずです。
ボタンやメニューをタップすれば、いま表示されている画面から別の画面に切り替わる。それが画面遷移です(私たちは1日に何回画面を切り替えてるんでしょうね、3桁いったりするんだろうか…)。
この「当たり前」な処理の裏側が、いったいどんな仕組みになっているのか気になる人も多いはず。
筆者が卒論でつまづいたのは実はここで、画面遷移を実装するのに数ヶ月以上の時間を溶かしてしまいました。画面の切り替わらないアプリは流石にまずい、そう冷や汗をかいていたのも懐かしいものです。
画面遷移のしくみ
今回は状態管理という考え方を使いました。「今、どの画面を表示するか」という情報を変数で管理します。例えるなら電源スイッチです。
メニュー画面には、ボタンを3つ設置しました。
- タイマーの電源
- カレンダーの電源
- カウントダウンの電源
それぞれのボタンがタップされる = 電源ON というイメージです。

実際のコードはこんな感じ。
// メニュー画面のボタン Button(onClick = { showTimer.value = true }) { Text("タイマー") } Button(onClick = { showCalendar.value = true }) { Text("カレンダー") } Button(onClick = { showCountdown.value = true }) { Text("カウントダウン") }
そして、画面を切り替える仕組みはこう。
// 画面の切り替え val showTimer = remember { mutableStateOf(false) } if (showTimer.value) { TimerScreen(onBack = { showTimer.value = false }) } else { MenuScreen(onTimerClick = { showTimer.value = true }) }
showTimerという変数がtrueになるとタイマー画面が表示される。falseに戻せば、メニュー画面に戻る。シンプルで理解しやすいですよね。
おわりに
なんとか4年越しのリベンジを達成し、思い残すことなく年末を迎えられそうです。
誰しも過去に「うまくいかなかったこと」があると思います。筆者にとっては、それが卒論で作ったAndroidアプリでした。しかし、あの頃できなかったことも、時間が経てばすんなり出来るようになっているものです。
この記事が、みなさんの「もう一度挑戦してみよう」と思うきっかけになれば幸いです。
フェンリルに入社して1年2ヶ月、たくさんの学びと失敗と反省がありましたが来年からも引き続き頑張っていく所存です。
それでは、みなさんよいクリスマスをお過ごしください。最後までご覧いただきありがとうございました!