Flutterアプリケーション開発概論

YouTube風ミニアプリ用のクラスを設計する

この節で学ぶこと

前回の 3-7 では、List<Video> を使って、動画一覧を操作する方法を学びました。

たとえば、次のような操作です。

動画を1件ずつ取り出す
人気動画だけ取り出す
Dartカテゴリだけ取り出す
ライブ動画だけ取り出す
タイトルに特定の文字を含む動画を検索する
再生回数が多い順に並び替える

ここまでで、YouTube風ミニアプリの「動画一覧データ」をかなり扱えるようになってきました。

今回の 3-8 では、さらに一歩進んで、YouTube風ミニアプリに必要なデータを自分で設計する練習をします。

これまで中心に扱ってきたのは Video classでした。

class Video {
  const Video({
    required this.title,
    required this.channelName,
    required this.views,
    required this.publishedAt,
    required this.duration,
    required this.category,
    required this.isLive,
  });

  final String title;
  final String channelName;
  final int views;
  final String publishedAt;
  final String duration;
  final String category;
  final bool isLive;
}

しかし、実際のアプリでは、動画だけでなく、チャンネルやカテゴリもデータとして考えることができます。

動画データ
チャンネルデータ
カテゴリデータ

この節では、VideoChannelCategory のようなclassを考えながら、アプリ全体のデータ設計に近づいていきます。

この節のゴール

この節のゴールは、YouTube風ミニアプリに必要な情報を見つけ、それをclassとして設計できるようになることです。

最終的には、次のような考え方ができる状態を目指します。

画面を見る
↓
表示されている情報を見つける
↓
どの情報をデータとして持つべきか考える
↓
classにする
↓
Listとして複数持つ
↓
UIに渡す

この節で大切なのは、次の一文です。

クラス設計とは、アプリに登場する情報を、使いやすいデータの形に整理することである。

まず「設計」とは何か

プログラミング初心者にとって、「設計」という言葉は少し難しく感じるかもしれません。

でも、考え方はとてもシンプルです。

何を作りたいのかを考える。
そのために、どんなデータが必要かを考える。
そのデータを、どんな形で持つかを決める。

たとえば、YouTube風アプリを作りたいとします。

画面には、次のような情報が表示されます。

動画タイトル
チャンネル名
再生回数
投稿日
動画時間
サムネイル
チャンネルアイコン
カテゴリ
ライブ中かどうか

これらを何も考えずにバラバラの変数で持つと、すぐに管理が大変になります。

そこで、意味のある単位ごとにまとめます。

動画1本分の情報 → Video class
チャンネルの情報 → Channel class
カテゴリの情報 → Category class

これが、クラス設計の入口です。

画面からデータを見つける

YouTube風の動画カードを想像してください。

┌──────────────────────────┐
│        サムネイル画像      │
│                    12:34 │
└──────────────────────────┘
● FlutterでYouTube風UIを作る
  Code Studio
  12.8万回視聴・2日前

この画面から、データを見つけます。

画面に見えるものデータとして考えるもの
FlutterでYouTube風UIを作る動画タイトル
Code Studioチャンネル名
12.8万回視聴再生回数
2日前投稿日
12:34動画時間
サムネイルサムネイル色や画像
● チャンネルアイコンチャンネルアイコン色
ライブ配信中ライブ状態

このように、画面に表示されるものの多くは、裏側のデータから作られています。

まずはVideo classを設計する

動画カードの中心になるのは、やはり Video classです。

動画1本分に必要な情報を整理すると、次のようになります。

必要な情報property名
動画タイトルtitleString
チャンネル名channelNameString
再生回数viewsint
投稿日publishedAtString
動画時間durationString
カテゴリcategoryString
ライブ中かどうかisLivebool

これをclassにすると、次のようになります。

class Video {
  const Video({
    required this.title,
    required this.channelName,
    required this.views,
    required this.publishedAt,
    required this.duration,
    required this.category,
    required this.isLive,
  });

  final String title;
  final String channelName;
  final int views;
  final String publishedAt;
  final String duration;
  final String category;
  final bool isLive;
}

これは、これまで作ってきた Video classとほぼ同じです。

このclassがあることで、動画1本分の情報を安全にまとめて扱えます。

Video classだけで足りるのか

ここで、少し考えてみます。

YouTube風アプリを作るだけなら、Video classだけでもある程度は作れます。

Video(
  title: 'FlutterでYouTube風UIを作る',
  channelName: 'Code Studio',
  views: 128000,
  publishedAt: '2日前',
  duration: '12:34',
  category: 'Flutter',
  isLive: false,
)

しかし、もしチャンネルの情報をもっと詳しく扱いたくなったらどうでしょうか。

たとえば、次のような情報です。

チャンネル名
チャンネルアイコン色
登録者数
チャンネル説明
公式チャンネルかどうか

このような情報が増えてくると、Video classの中に全部入れるより、Channel classとして分けたほうが整理しやすくなります。

Video:
動画そのものの情報

Channel:
チャンネルそのものの情報

Channel classを考える

チャンネル情報をclassにするなら、たとえば次のように考えられます。

必要な情報property名
チャンネル名nameString
アイコン色avatarColorNameString
登録者数subscribersint
公式チャンネルかどうかisOfficialbool

まず、Dartだけで試しやすいように、Color は使わず、色名を文字で持つ形にします。

class Channel {
  const Channel({
    required this.name,
    required this.avatarColorName,
    required this.subscribers,
    required this.isOfficial,
  });

  final String name;
  final String avatarColorName;
  final int subscribers;
  final bool isOfficial;
}

この Channel classは、チャンネル1つ分の情報を表します。

たとえば、次のように使えます。

final channel = Channel(
  name: 'Code Studio',
  avatarColorName: 'red',
  subscribers: 24000,
  isOfficial: true,
);

Channel classを使うメリット

チャンネル情報を Channel classに分けると、次のようなメリットがあります。

分ける前分けた後
Videoの中にチャンネル情報が増え続けるChannelとして整理できる
チャンネル名だけしか扱いにくい登録者数や公式判定も持てる
同じチャンネル情報を何度も書くChannelデータとして再利用しやすい
動画とチャンネルの役割が混ざる役割が分かれる

たとえば、同じチャンネルが複数の動画を投稿している場合、チャンネル情報を別で持つと整理しやすくなります。

Code Studio
├─ FlutterでYouTube風UIを作る
├─ Flutterレイアウト入門
└─ ListViewの使い方

このように、チャンネルと動画は関係しています。

しかし、同じものではありません。

Videoの中にChannelを持たせる

Video classの中で、チャンネル名を String として持つ代わりに、Channel を持たせることもできます。

これまでの形です。

class Video {
  const Video({
    required this.title,
    required this.channelName,
  });

  final String title;
  final String channelName;
}

Channel classを使う形です。

class Video {
  const Video({
    required this.title,
    required this.channel,
  });

  final String title;
  final Channel channel;
}

この場合、VideoChannel データを持つことになります。

Video
├─ title
└─ channel
   ├─ name
   ├─ avatarColorName
   ├─ subscribers
   └─ isOfficial

このように、classの中に別のclassを持たせることもできます。

Channelを持つVideoをDartPadで試す

void main() {
  final channel = Channel(
    name: 'Code Studio',
    avatarColorName: 'red',
    subscribers: 24000,
    isOfficial: true,
  );

  final video = Video(
    title: 'FlutterでYouTube風UIを作る',
    channel: channel,
    views: 128000,
  );

  print(video.title);
  print(video.channel.name);
  print(video.channel.subscribers);
}

class Video {
  const Video({
    required this.title,
    required this.channel,
    required this.views,
  });

  final String title;
  final Channel channel;
  final int views;
}

class Channel {
  const Channel({
    required this.name,
    required this.avatarColorName,
    required this.subscribers,
    required this.isOfficial,
  });

  final String name;
  final String avatarColorName;
  final int subscribers;
  final bool isOfficial;
}

出力結果です。

FlutterでYouTube風UIを作る
Code Studio
24000

ここで注目するのは、次の書き方です。

video.channel.name

これは、次のように読めます。

videoの中にあるchannelの中にあるnameを取り出す。

少し長く見えますが、意味はそのままです。

video.channel.nameの読み方

video.channel.name は、3段階で読みます。

コード意味
video動画1本分のデータ
video.channelその動画のチャンネルデータ
video.channel.nameそのチャンネルの名前

図にすると、次のようになります。

video
├─ title: FlutterでYouTube風UIを作る
├─ views: 128000
└─ channel
   ├─ name: Code Studio
   ├─ avatarColorName: red
   ├─ subscribers: 24000
   └─ isOfficial: true

Video の中に Channel が入っているため、このように取り出します。

ただし初心者のうちはシンプルでもよい

ここで大切なことがあります。

実際の完成アプリでは、学習の分かりやすさを優先して、Video classの中に channelNamechannelColor を直接持たせています。

final String channelName;
final Color channelColor;

これは、初心者には分かりやすい形です。

video.channelName
video.channelColor

一方、Channel classに分けると、より設計らしくなります。

video.channel.name
video.channel.avatarColor

どちらが絶対に正しいということではありません。

学習段階では、まずシンプルな Video classで理解する。

慣れてきたら、Channel classに分ける。

この順番で大丈夫です。

設計メリット向いている場面
VideochannelName を直接持つ初心者に分かりやすい小さなモックアプリ
Channel classを分ける拡張しやすい本格的なアプリ設計

Category classを考える

次に、カテゴリをclassとして考えます。

YouTube風アプリには、上部にカテゴリバーがあります。

すべて
Flutter
Dart
UI
ライブ
最近アップロード
視聴済み

このカテゴリも、ただの文字列Listとして持つことができます。

final categories = [
  'すべて',
  'Flutter',
  'Dart',
  'UI',
  'ライブ',
];

これでも十分です。

しかし、カテゴリに追加情報を持たせたい場合は、Category classにできます。

たとえば、次のような情報です。

必要な情報property名
表示名labelString
選択中かどうかisSelectedbool
並び順orderint

これをclassにすると、次のようになります。

class Category {
  const Category({
    required this.label,
    required this.isSelected,
    required this.order,
  });

  final String label;
  final bool isSelected;
  final int order;
}

CategoryをDartPadで試す

void main() {
  final categories = [
    Category(
      label: 'すべて',
      isSelected: true,
      order: 0,
    ),
    Category(
      label: 'Flutter',
      isSelected: false,
      order: 1,
    ),
    Category(
      label: 'Dart',
      isSelected: false,
      order: 2,
    ),
  ];

  for (final category in categories) {
    print('${category.order}: ${category.label}');
  }
}

class Category {
  const Category({
    required this.label,
    required this.isSelected,
    required this.order,
  });

  final String label;
  final bool isSelected;
  final int order;
}

出力結果です。

0: すべて
1: Flutter
2: Dart

このように、カテゴリもclassとして設計できます。

StringのListとCategory classの違い

カテゴリを文字だけで持つ場合です。

final categories = [
  'すべて',
  'Flutter',
  'Dart',
];

カテゴリをclassで持つ場合です。

final categories = [
  Category(label: 'すべて', isSelected: true, order: 0),
  Category(label: 'Flutter', isSelected: false, order: 1),
  Category(label: 'Dart', isSelected: false, order: 2),
];

違いを整理します。

比較StringのListCategory class
書きやすさ簡単少し長い
表示名持てる持てる
選択状態別で管理が必要isSelected として持てる
並び順Listの順番で分かるorder として明示できる
拡張性低め高め

初心者のうちは、まずStringのListで十分です。

ただし、「カテゴリにも情報を持たせたい」と思ったら、Category classにできます。

ここまでのまとめ

前半では、YouTube風ミニアプリに出てくる情報を、classとして設計する考え方を学びました。

大切なポイントは、次の通りです。

- クラス設計とは、アプリに登場する情報を使いやすい形に整理すること
- 動画1本分の情報はVideo classにできる
- チャンネル情報はChannel classにできる
- カテゴリ情報はCategory classにできる
- classの中に別のclassを持たせることもできる
- 小さなアプリではシンプルな設計でよい
- 本格的にするなら、役割ごとにclassを分ける

前半で特に大切なこと

画面に表示されている情報を見つけ、意味のある単位ごとにclassへ整理する。

小さなアプリと本格的なアプリで設計は変わる

前半では、YouTube風ミニアプリに登場する情報を、VideoChannelCategory のようなclassとして整理する考え方を学びました。

ここで大切なのは、最初からすべてを細かく分けすぎなくてもよい、ということです。

小さな学習用アプリでは、次のように Video classだけで作るほうが分かりやすい場合があります。

class Video {
  const Video({
    required this.title,
    required this.channelName,
    required this.views,
    required this.publishedAt,
    required this.duration,
    required this.category,
    required this.isLive,
  });

  final String title;
  final String channelName;
  final int views;
  final String publishedAt;
  final String duration;
  final String category;
  final bool isLive;
}

一方、本格的なアプリに近づけるなら、チャンネル情報やカテゴリ情報を別classに分ける設計も考えられます。

Video
├─ title
├─ views
├─ duration
├─ channel
└─ category

このように、アプリの規模や目的によって、設計の細かさは変わります。

設計は「正解を当てる」ものではない

プログラミング初心者は、class設計を学ぶときに、次のように考えてしまうことがあります。

正しいclass設計を一発で作らなければいけない。

しかし、実際にはそうではありません。

設計は、作りながら見直していくものです。

最初はシンプルに作り、必要になったら分ける。

これで大丈夫です。

たとえば、最初は次のように作ります。

final String channelName;

あとから、チャンネル情報が増えてきたら、Channel classに分けます。

final Channel channel;

つまり、設計は一度決めたら終わりではありません。

アプリの成長に合わせて、少しずつ整理していくものです。

設計の判断基準

classを分けるべきか迷ったときは、次のように考えると分かりやすいです。

判断することclassを分けなくてもよい場合classを分けたほうがよい場合
情報量情報が少ない情報が多い
再利用1か所でしか使わない複数の場所で使う
意味のまとまりただの補足情報独立した存在として扱いたい
変更の可能性ほとんど変わらない今後増えそう
初心者の理解シンプルにしたい設計練習をしたい

たとえば、チャンネル名だけなら、VideochannelName を持たせても十分です。

final String channelName;

しかし、チャンネル名、アイコン、登録者数、公式判定、説明文などを扱いたくなったら、Channel classに分けたほうが自然です。

final Channel channel;

今回のYouTube風ミニアプリのおすすめ設計

今回の教材では、初心者がFlutter UIに進みやすいように、完成アプリでは次のようなシンプルな設計を採用します。

class Video {
  const Video({
    required this.title,
    required this.channelName,
    required this.views,
    required this.publishedAt,
    required this.duration,
    required this.category,
    required this.thumbnailColor,
    required this.channelColor,
    required this.isLive,
  });

  final String title;
  final String channelName;
  final int views;
  final String publishedAt;
  final String duration;
  final String category;
  final Color thumbnailColor;
  final Color channelColor;
  final bool isLive;

  String get viewLabel {
    if (views >= 100000000) {
      return '${(views / 100000000).toStringAsFixed(1)}億回視聴';
    }

    if (views >= 10000) {
      return '${(views / 10000).toStringAsFixed(1)}万回視聴';
    }

    return '$views回視聴';
  }

  String get metaLabel {
    if (isLive) {
      return 'ライブ配信中';
    }

    return '$viewLabel・$publishedAt';
  }
}

この設計では、Video classの中に、動画カードを表示するために必要な情報をまとめています。

Video
├─ 動画の情報
│  ├─ title
│  ├─ views
│  ├─ publishedAt
│  └─ duration
│
├─ チャンネルの簡単な情報
│  ├─ channelName
│  └─ channelColor
│
├─ カテゴリ情報
│  └─ category
│
├─ サムネイル情報
│  └─ thumbnailColor
│
└─ 表示用のふるまい
   ├─ viewLabel
   └─ metaLabel

小さなミニアプリとしては、このくらいの設計が分かりやすいです。

なぜ完成アプリではChannel classを分けないのか

前半では、Channel classを作る例を見ました。

class Channel {
  const Channel({
    required this.name,
    required this.avatarColorName,
    required this.subscribers,
    required this.isOfficial,
  });

  final String name;
  final String avatarColorName;
  final int subscribers;
  final bool isOfficial;
}

これは、設計としては良い考え方です。

ただし、今回の完成アプリでは、チャンネルの詳しいページや登録者数の表示までは作りません。

必要なのは、動画カードに表示するチャンネル名とアイコン色です。

そのため、次のように Video に直接持たせています。

final String channelName;
final Color channelColor;

このほうが、初心者には分かりやすくなります。

video.channelName
video.channelColor

もし Channel classに分けると、次のようになります。

video.channel.name
video.channel.avatarColor

こちらは本格的ですが、少し複雑になります。

今回の目標は、YouTube風の動画一覧UIを作ることなので、まずはシンプルな設計で進めます。

Categoryも最初はStringで十分

カテゴリも同じです。

本格的には、Category classにできます。

class Category {
  const Category({
    required this.label,
    required this.isSelected,
    required this.order,
  });

  final String label;
  final bool isSelected;
  final int order;
}

ただし、今回の完成アプリでは、カテゴリバーに文字を並べるだけで十分です。

そのため、カテゴリは次のような List<String> で扱えます。

const categories = [
  'すべて',
  'Flutter',
  'Dart',
  'UI',
  'ライブ',
  '最近アップロード',
  '視聴済み',
];

この形なら、初心者でも理解しやすいです。

Category classを使うのは、たとえば次のような機能を追加したくなったときで大丈夫です。

選択中カテゴリをデータとして持ちたい
カテゴリごとに色を変えたい
カテゴリごとにアイコンを持たせたい
カテゴリの並び順を管理したい

設計を段階的に成長させる

アプリの設計は、段階的に成長させることができます。

第1段階:文字列だけで持つ

final String channelName;
final String category;

初心者には分かりやすいです。

小さなアプリなら、これで十分です。

第2段階:classとして分ける

final Channel channel;
final Category category;

情報が増えてきたら、classに分けます。

第3段階:データ同士の関係を設計する

Channel
├─ 複数のVideoを持つ

Category
├─ 複数のVideoを分類する

本格的なアプリでは、データ同士の関係も考えます。

ただし、今は第1段階からで十分です。

完成アプリに近いデータ設計

ここで、完成アプリに近いデータの形を見てみます。

const videos = [
  Video(
    title: 'FlutterでYouTube風UIを作る|ListViewとCardの実践入門',
    channelName: 'Code Studio',
    views: 128000,
    publishedAt: '2日前',
    duration: '12:34',
    category: 'Flutter',
    thumbnailColor: Color(0xFF121212),
    channelColor: Color(0xFFE53935),
    isLive: false,
  ),
  Video(
    title: 'DartのListとMapをアプリ画面で使う方法をやさしく解説',
    channelName: 'App School',
    views: 8500,
    publishedAt: '5日前',
    duration: '08:21',
    category: 'Dart',
    thumbnailColor: Color(0xFF1E3A8A),
    channelColor: Color(0xFF1565C0),
    isLive: false,
  ),
  Video(
    title: 'ライブ:Flutter質問会|Widget・ListView・classの疑問を解決',
    channelName: 'Mobile Dev Live',
    views: 5600,
    publishedAt: '現在',
    duration: 'LIVE',
    category: 'LIVE',
    thumbnailColor: Color(0xFFE62117),
    channelColor: Color(0xFFD32F2F),
    isLive: true,
  ),
];

この videos は、Video classのListです。

List<Video>
├─ Video 1本目
├─ Video 2本目
└─ Video 3本目

完成アプリでは、この videos をもとに、動画カードを作ります。

VideoCard(video: videos[index])

つまり、データ設計がそのままUI設計につながっています。

データ設計からUIを考える

class設計は、ただデータをきれいにするためだけではありません。

UIを作りやすくするためにも重要です。

たとえば、動画カードを作る VideoCard には、Video を1つ渡します。

VideoCard(video: video)

VideoCard の中では、次のように分解して表示できます。

VideoCard
├─ Thumbnail
│  ├─ video.thumbnailColor
│  ├─ video.category
│  └─ video.duration
│
├─ ChannelAvatar
│  └─ video.channelColor
│
└─ VideoInfo
   ├─ video.title
   ├─ video.channelName
   └─ video.metaLabel

このように、Video classのpropertyが、UI部品にそのまま渡されます。

UI部品とVideo propertyの対応表

完成アプリで、どのpropertyがどのUIに使われるかを整理します。

Videoのproperty / getter使われるUI表示内容
thumbnailColorThumbnailサムネイル背景色
categoryThumbnailサムネイル中央のカテゴリ文字
durationThumbnail右下の動画時間
channelColorChannelAvatarチャンネルアイコンの色
titleVideoInfo動画タイトル
channelNameVideoInfoチャンネル名
viewLabelmetaLabelの中再生回数表示
publishedAtmetaLabelの中投稿日
isLivemetaLabel / 表示スタイルライブ表示の切り替え
metaLabelVideoInfo補足情報

この表を見ると、Video classが動画カードUIの材料になっていることが分かります。

設計演習1:画面から必要なデータを抜き出す

次の動画カードを作るとします。

┌──────────────────────────┐
│          DART            │
│                    08:21 │
└──────────────────────────┘
● DartのListとMapをアプリ画面で使う方法
  App School
  8500回視聴・5日前

この画面から必要なデータを抜き出してください。

解答例

画面に見えるものproperty名
DARTcategoryDart
08:21duration08:21
DartのListとMapをアプリ画面で使う方法titleDartのListとMapをアプリ画面で使う方法
App SchoolchannelNameApp School
8500回視聴views8500
5日前publishedAt5日前
通常動画isLivefalse

画面に表示される情報を見れば、必要なpropertyが見えてきます。

設計演習2:Videoデータにする

先ほどの情報を Video データとして書いてみます。

Video(
  title: 'DartのListとMapをアプリ画面で使う方法',
  channelName: 'App School',
  views: 8500,
  publishedAt: '5日前',
  duration: '08:21',
  category: 'Dart',
  thumbnailColor: Color(0xFF1E3A8A),
  channelColor: Color(0xFF1565C0),
  isLive: false,
)

このように、画面から抜き出した情報を、classのpropertyに当てはめます。

画面で見える情報
↓
propertyとして整理
↓
Video(...)として作る

これが、データ設計の実践です。

設計演習3:ライブ動画を設計する

次に、ライブ動画を考えます。

┌──────────────────────────┐
│          LIVE            │
│                    LIVE  │
└──────────────────────────┘
● ライブ:Flutter質問会
  Mobile Dev Live
  ライブ配信中

この場合、必要なデータは次のようになります。

画面に見えるものproperty名
LIVEcategoryLIVE
LIVEdurationLIVE
ライブ:Flutter質問会titleライブ:Flutter質問会
Mobile Dev LivechannelNameMobile Dev Live
ライブ配信中isLivetrue
投稿日publishedAt現在
再生回数views5600

isLivetrue の場合、metaLabelライブ配信中 になります。

String get metaLabel {
  if (isLive) {
    return 'ライブ配信中';
  }

  return '$viewLabel・$publishedAt';
}

つまり、画面に ライブ配信中 と直接持つのではなく、isLive というデータから表示を作ります。

ライブ動画をVideoデータにする

Video(
  title: 'ライブ:Flutter質問会|Widget・ListView・classの疑問を解決',
  channelName: 'Mobile Dev Live',
  views: 5600,
  publishedAt: '現在',
  duration: 'LIVE',
  category: 'LIVE',
  thumbnailColor: Color(0xFFE62117),
  channelColor: Color(0xFFD32F2F),
  isLive: true,
)

このデータでは、isLivetrue になっています。

そのため、UI側で video.metaLabel を使うと、次のように表示されます。

ライブ配信中

同じ Video classでも、isLive の値によって表示が変わります。

これが、データ設計と表示ルールを組み合わせる考え方です。

設計演習4:カテゴリバーを設計する

次に、カテゴリバーを考えます。

完成アプリでは、カテゴリを文字列のListで持つことができます。

const categories = [
  'すべて',
  'Flutter',
  'Dart',
  'UI',
  'ライブ',
  '最近アップロード',
  '視聴済み',
];

この categories は、カテゴリバーに表示する文字の一覧です。

List<String>
├─ すべて
├─ Flutter
├─ Dart
├─ UI
├─ ライブ
├─ 最近アップロード
└─ 視聴済み

カテゴリバーのUIでは、これを1件ずつ取り出して表示します。

for (final category in categories) {
  print(category);
}

出力結果です。

すべて
Flutter
Dart
UI
ライブ
最近アップロード
視聴済み

まずは、このようなシンプルな設計で十分です。

Category classにするならどうなるか

もしカテゴリにも選択状態を持たせたいなら、Category classを使えます。

class Category {
  const Category({
    required this.label,
    required this.isSelected,
  });

  final String label;
  final bool isSelected;
}

データは次のようになります。

final categories = [
  Category(label: 'すべて', isSelected: true),
  Category(label: 'Flutter', isSelected: false),
  Category(label: 'Dart', isSelected: false),
  Category(label: 'UI', isSelected: false),
];

この場合、UI側では isSelected を見て、選択中の見た目を変えることができます。

isSelectedがtrue
↓
黒背景・白文字

isSelectedがfalse
↓
グレー背景・黒文字

ただし、今回の完成アプリでは、まず List<String> で十分です。

設計演習5:Channel classにするならどうなるか

もしチャンネル情報を独立させるなら、次のように設計できます。

class Channel {
  const Channel({
    required this.name,
    required this.avatarColor,
    required this.subscribers,
    required this.isOfficial,
  });

  final String name;
  final Color avatarColor;
  final int subscribers;
  final bool isOfficial;
}

そして、VideoChannel を持ちます。

class Video {
  const Video({
    required this.title,
    required this.channel,
    required this.views,
  });

  final String title;
  final Channel channel;
  final int views;
}

データは次のようになります。

final codeStudio = Channel(
  name: 'Code Studio',
  avatarColor: Color(0xFFE53935),
  subscribers: 24000,
  isOfficial: true,
);

final video = Video(
  title: 'FlutterでYouTube風UIを作る',
  channel: codeStudio,
  views: 128000,
);

この場合、チャンネル名は次のように取り出します。

video.channel.name

これは本格的な設計ですが、初心者には少し複雑になります。

今回の完成アプリでは、まず channelNamechannelColorVideo に直接持たせます。

初心者におすすめの設計手順

アプリのclass設計に迷ったら、次の順番で考えると分かりやすいです。

1. 画面に表示されているものを書き出す
2. その情報が何の情報か分類する
3. 1つのまとまりにできるものを探す
4. まずはシンプルなclassにする
5. 情報が増えたらclassを分ける

YouTube風アプリなら、まずは動画カードを見ます。

動画タイトル
チャンネル名
再生回数
投稿日
動画時間
カテゴリ
ライブ状態

これらを Video classにまとめます。

慣れてきたら、ChannelCategory に分けます。

設計で大切な考え方

class設計で大切なのは、難しい名前をつけることではありません。

大切なのは、役割を分けることです。

Video:
動画1本分の情報

Channel:
チャンネルの情報

Category:
分類の情報

名前を見たときに、何を表しているのか分かることが重要です。

悪い例です。

class Data {
  final String name;
}

何のデータか分かりにくいです。

よい例です。

class Video {
  final String title;
}

動画のデータであることが分かります。

class Channel {
  final String name;
}

チャンネルのデータであることが分かります。

名前の付け方も設計の一部

class名やproperty名は、コードの読みやすさに大きく関わります。

表したいものよい名前避けたい名前
動画VideoData
チャンネルChannelInfo
カテゴリCategoryItem
動画タイトルtitletext1
チャンネル名channelNamename2
再生回数viewsnum
ライブ中かどうかisLiveflag

isLive のように、is から始まる名前は、true / false の値でよく使います。

final bool isLive;

これは、読んだときに「ライブ中かどうか」だと分かりやすいです。

完成アプリに向けた最終設計

この章の完成アプリでは、次のような設計で進めます。

class Video {
  const Video({
    required this.title,
    required this.channelName,
    required this.views,
    required this.publishedAt,
    required this.duration,
    required this.category,
    required this.thumbnailColor,
    required this.channelColor,
    required this.isLive,
  });

  final String title;
  final String channelName;
  final int views;
  final String publishedAt;
  final String duration;
  final String category;
  final Color thumbnailColor;
  final Color channelColor;
  final bool isLive;

  String get viewLabel {
    if (views >= 100000000) {
      return '${(views / 100000000).toStringAsFixed(1)}億回視聴';
    }

    if (views >= 10000) {
      return '${(views / 10000).toStringAsFixed(1)}万回視聴';
    }

    return '$views回視聴';
  }

  String get metaLabel {
    if (isLive) {
      return 'ライブ配信中';
    }

    return '$viewLabel・$publishedAt';
  }
}

カテゴリは、まず文字列のListで持ちます。

const categories = [
  'すべて',
  'Flutter',
  'Dart',
  'UI',
  'ライブ',
  '最近アップロード',
  '視聴済み',
];

動画一覧は、List<Video> で持ちます。

const videos = [
  Video(
    title: 'FlutterでYouTube風UIを作る|ListViewとCardの実践入門',
    channelName: 'Code Studio',
    views: 128000,
    publishedAt: '2日前',
    duration: '12:34',
    category: 'Flutter',
    thumbnailColor: Color(0xFF121212),
    channelColor: Color(0xFFE53935),
    isLive: false,
  ),
];

これで、YouTube風ミニアプリのデータ設計の土台ができます。

手を動かす練習1:画面からpropertyを考える

次の表示をもとに、必要なpropertyを考えてください。

Flutterレイアウト入門
Code Studio
3.2万回視聴・1週間前
10:05

解答例

表示property名
Flutterレイアウト入門titleString
Code StudiochannelNameString
32000viewsint
1週間前publishedAtString
10:05durationString
FluttercategoryString
通常動画isLivebool

手を動かす練習2:Videoデータを書く

練習1の情報をもとに、Video データを書いてください。

解答例

Video(
  title: 'Flutterレイアウト入門',
  channelName: 'Code Studio',
  views: 32000,
  publishedAt: '1週間前',
  duration: '10:05',
  category: 'Flutter',
  thumbnailColor: Color(0xFF121212),
  channelColor: Color(0xFFE53935),
  isLive: false,
)

手を動かす練習3:Channel classを設計する

チャンネルに次の情報を持たせたいとします。

チャンネル名
登録者数
公式チャンネルかどうか

Channel classを書いてください。

解答例

class Channel {
  const Channel({
    required this.name,
    required this.subscribers,
    required this.isOfficial,
  });

  final String name;
  final int subscribers;
  final bool isOfficial;
}

手を動かす練習4:Category classを設計する

カテゴリに次の情報を持たせたいとします。

表示名
選択中かどうか

Category classを書いてください。

解答例

class Category {
  const Category({
    required this.label,
    required this.isSelected,
  });

  final String label;
  final bool isSelected;
}

手を動かす練習5:classを分けるべきか考える

次のうち、Channel classに分けたほうがよさそうな場面はどれですか。

A. チャンネル名だけを表示したい
B. チャンネル名、登録者数、公式マーク、説明文を表示したい
C. 学習用の小さな動画カードだけ作りたい

解答例

B です。

チャンネルに関する情報が増えているため、Channel classとして分けたほうが整理しやすくなります。

AC のような小さな設計では、まずは channelNameVideo に直接持たせても大丈夫です。

よくあるつまずき1:最初から完璧な設計をしようとする

初心者がよくつまずくのは、最初から完璧なclass設計を作ろうとして、手が止まってしまうことです。

しかし、最初から完璧である必要はありません。

まず動く形を作る。
必要になったら整理する。
情報が増えたらclassを分ける。

この順番で大丈夫です。

今回の完成アプリでも、まずは Video class中心のシンプルな設計にしています。

よくあるつまずき2:classを分けすぎる

classを学ぶと、何でもclassに分けたくなることがあります。

しかし、分けすぎると、初心者には読みにくくなります。

たとえば、小さなアプリで次のように分けると、少し複雑です。

Video
Channel
Category
Thumbnail
Duration
ViewCount
PublishedDate

本格的なアプリでは有効な場合もあります。

しかし、学習用のミニアプリでは、まずは次のくらいで十分です。

Video
categories
videos

必要になったら分ける、という考え方を持ちましょう。

よくあるつまずき3:Stringでよいものまでclassにしてしまう

カテゴリ名だけを表示するなら、String のListで十分です。

const categories = [
  'すべて',
  'Flutter',
  'Dart',
];

カテゴリに選択状態やアイコンなどを持たせたくなったら、Category classを考えます。

class Category {
  final String label;
  final bool isSelected;
}

つまり、情報が少ないうちはシンプルに。

情報が増えたらclassにする。

これが分かりやすい進め方です。

よくあるつまずき4:property名があいまいになる

名前があいまいだと、あとから読みにくくなります。

避けたい例です。

final String text;
final String name;
final int number;
final bool flag;

何を表しているのか分かりにくいです。

分かりやすい例です。

final String title;
final String channelName;
final int views;
final bool isLive;

何のデータか分かります。

名前は、コードを読む人への説明です。

自分があとで見ても分かる名前をつけることが大切です。

よくあるつまずき5:UIから逆算しない

class設計で迷ったら、画面を見るのが一番分かりやすいです。

画面に何が表示されるか
↓
その表示にはどんなデータが必要か
↓
どのclassに持たせるか

たとえば、動画時間をサムネイル右下に表示したいなら、duration が必要です。

final String duration;

ライブ表示を切り替えたいなら、isLive が必要です。

final bool isLive;

このように、UIから逆算すると、必要なpropertyが見つけやすくなります。

この節で覚える対応表

考え方説明
動画1本分Video動画カードの元データ
チャンネル情報Channelチャンネル名、登録者数など
カテゴリ情報Category表示名、選択状態など
小さなアプリVideo中心シンプルで理解しやすい
本格的なアプリclassを分ける拡張しやすい
カテゴリ文字だけList<String>まずはこれで十分
複数の動画List<Video>動画一覧の元データ
UIへの受け渡しVideoCard(video: video)データをカードに渡す

確認問題1

クラス設計とは、どのような作業ですか。

答え

アプリに登場する情報を、使いやすいデータの形に整理することです。

YouTube風アプリでは、動画情報を Video、チャンネル情報を Channel、カテゴリ情報を Category のように整理できます。

確認問題2

今回の完成アプリで、まず Video class中心の設計にする理由は何ですか。

答え

学習用の小さなミニアプリでは、シンプルな設計のほうが理解しやすいからです。

チャンネル情報やカテゴリ情報が増えてきたら、あとから Channel classや Category classに分けることもできます。

確認問題3

Channel classに分けたほうがよいのは、どのような場合ですか。

答え

チャンネル名だけでなく、登録者数、アイコン、公式マーク、説明文など、チャンネルに関する情報が増えてきた場合です。

確認問題4

カテゴリ名を表示するだけなら、どのようなデータで十分ですか。

答え

List<String> で十分です。

const categories = [
  'すべて',
  'Flutter',
  'Dart',
];

確認問題5

UIから逆算してpropertyを考えるとは、どういうことですか。

答え

画面に表示したいものを見て、その表示に必要なデータをpropertyとして考えることです。

たとえば、動画時間を表示したいなら duration、ライブ表示を切り替えたいなら isLive が必要になります。

この節のまとめ

この節では、YouTube風ミニアプリに必要なclassを設計する考え方を学びました。

クラス設計は、アプリに登場する情報を整理する作業です。

動画1本分の情報は Video classにできます。

チャンネル情報が増えてきたら Channel classにできます。

カテゴリ情報が増えてきたら Category classにできます。

ただし、最初から細かく分けすぎる必要はありません。

今回の完成アプリでは、初心者が理解しやすいように、まずは Video class中心のシンプルな設計にします。

この節のポイント:
- 画面に表示される情報から必要なデータを考える
- 動画1本分の情報はVideo classにまとめる
- 情報が増えたらChannelやCategoryに分ける
- 小さなアプリではシンプルな設計でよい
- 本格的にするなら役割ごとにclassを分ける
- class名やproperty名は、意味が分かる名前にする

大切なこと

クラス設計は、画面に必要な情報を、意味のある単位で整理する作業である。

次の 3-9 では、ここまで作ってきた Video データを、FlutterのUIにどのようにつなげるかを学びます。

List<Video>ListView.builder に渡し、VideoCard として表示する考え方へ進みます。

教材トップへ戻る