0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

モダンAndroidアプリの「骨格」を作る - シンプルなメモアプリの基本設計ログ

Last updated at Posted at 2025-09-26

はじめに

こんにちは!

前回の要件定義の記事に引き続き、今回はアプリの基本設計を行っていきます。要件定義が「何を作るか」を決める地図だとしたら、基本設計は「どう作るか」という具体的な航路を描く、いわばアプリの「骨格」作りです。

いきなりコーディングを始めるのではなく、一度立ち止まって設計を考えることで、手戻りが少なく、見通しの良いコードを書くことができるようになります。今回は、その思考プロセスをログとして残していこうと思います。


設計の全体像:MVVM + UiState

今回のアーキテクチャは、Google推奨の MVVM (Model-View-ViewModel) を採用します。

  • View: ユーザーが見る画面 (Jetpack Compose)
  • ViewModel: アプリの頭脳。ロジックや状態を管理
  • Model: データそのもの

というシンプルな役割分担ですね。そして、今回の設計で一番のこだわりポイントが、**UiState**という考え方の導入です。

ViewModelは、単に「メモのリスト(List<Memo>)」をViewに渡すのではありません。「ローディング中」「成功(データあり)」「データが空」「エラー発生」といった、UIが取りうる全ての状態UiStateとして定義し、それごとViewに渡します。

正直、ただのメモアプリにUiStateを導入するのは少し大げさかな?とも悩みました(笑)。でも、今後の拡張性を考えたときや、非同期処理が入ってきたときのことを考えると、最初からこの「型」で設計しておくことが、未来の自分を絶対に助けてくれると信じて、この構成に踏み切りました。


画面設計

画面はシンプルに一つです。ごちゃごちゃさせず、メモの追加と閲覧に集中できるように設計します。

  • 画面名: メモ一覧・追加画面 (MemoScreen.kt)
  • 主な構成コンポーネント:
    • MemoInputSection: テキスト入力欄と「追加」ボタンをまとめた、画面上部の領域。
    • MemoListSection: メモのリストを表示する領域。UiStateに応じて、ローディング表示や「メモはありません」といった表示を切り替える賢い子です。

機能の振る舞い

ユーザー操作によって、各機能がどのように動くのかを定義します。

メモが追加されるまでの流れ

  1. ユーザーが文字を入力し、「追加」ボタンをタップします。
  2. (入力チェック): もし入力が空っぽだったら? -> ViewModel側で「空のメモは許可しない」というロジックを入れて、処理を中断させます。ユーザーには何も起こりませんが、不正なデータは登録させません w
  3. (正常な流れ): ViewはViewModelのaddMemo()関数を呼び出します。
  4. ViewModelは新しいメモデータを作り、リストの先頭に追加して、UiStateを「成功(Success)」状態で更新します。
  5. UiStateの変化を検知したViewが自動的に再描画され、ユーザーは新しいメモがリストの一番上に追加されたのを確認できます。

データの形を決める(データ設計)

アプリの心臓部とも言える、データの「形」をここで定義しておきます。

Memo.kt

一つのメモがどんな情報を持つのか、という定義です。

// packages/data/model/Memo.kt

import java.time.LocalDateTime
import java.util.UUID

// 一つのメモを表すデータクラス
data class Memo(
    // 重複しないようにUUIDで一意なIDを付与
    val id: UUID = UUID.randomUUID(),
    // メモの本文
    val text: String,
    // 並び替えに使う作成日時
    val createdAt: LocalDateTime = LocalDateTime.now()
)

MemoUiState.kt

今回の設計の肝となる、UIの状態を定義したものです。


// packages/ui/state/MemoUiState.kt

// UIの状態を型安全に表現するためのsealed class
sealed class MemoUiState {
    // ローディング中
    object Loading : MemoUiState()
    // 成功(メモリストを持つ)
    data class Success(val memos: List<Memo>) : MemoUiState()
    // データが空
    object Empty : MemoUiState()
    // エラー(エラーメッセージを持つ)
    data class Error(val message: String) : MemoUiState()
}

これを使うことで、when式で全ての状態を網羅しないとコンパイルエラーになるので、実装漏れを防げるというメリットもあります。

コードの住所録(パッケージ構成)

最初が肝心、ということで、コードをしまう棚(パッケージ)も最初に決めておきます。
今回は、data(データ関連)とui(画面関連)という大きな棚に分けて整理します。

com.takahashi.kazuya.simplememoapp
│
├── data
│   └─ model
│      └─ Memo.kt         // データクラス
│
├── ui
│   ├── screen
│   │   └─ MemoScreen.kt   // 画面のCompose実装
│   ├── state
│   │   └─ MemoUiState.kt  // UI状態の定義
│   └─ viewmodel
│      └─ MemoViewModel.kt// ViewModel
│
└─ MainActivity.kt         // アプリのエントリポイント

datauiがしっかり分かれているのがポイントですね。
このように役割ごとにパッケージを分けることで、将来アプリが大きくなっても、どこに何があるか迷子になりにくくなります。

まとめ

以上が、今回のメモアプリの基本設計(骨格)になります。

  • MVVMアーキテクチャをベースにする

  • UiStateでUIの状態を厳密に管理する

  • 機能やデータの「形」を事前に定義しておく

この骨格に沿って、次回からはいよいよ実装(詳細設計)という肉付け作業に入っていこうと思います。
ここまで読んでいただきありがとうございました!(サボらないように頑張ります w)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?