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

List・Map・classを組み合わせて動画一覧を操作する

この節で学ぶこと

ここまでの第3章では、YouTube風ミニアプリを作るために、動画データの持ち方を少しずつ学んできました。

3-1では、動画データを「ただの値」ではなく、「動画1本分の情報」として見る考え方を学びました。

3-2では、ListMap を使って、複数の動画データをまとめる方法を学びました。

3-3では、class を使って、動画1本分のデータを Video として設計しました。

3-4では、constructorproperty を使って、動画の持ち物を決めました。

3-5では、getter を使って、再生回数を 12.8万回視聴 のような表示用テキストに変える方法を学びました。

3-6では、finalgetter を使って、動画データを安全に扱うカプセル化を学びました。

今回の 3-7 では、これらを組み合わせます。

YouTube風アプリでは、動画一覧の中から、

人気動画だけ表示する
Dartカテゴリだけ表示する
ライブ配信だけ表示する
タイトルを順番に表示する
動画カード用のデータを1件ずつ取り出す

といった処理が必要になります。

この節では、List<Video> を使って、動画一覧データを操作する方法を学びます。

この節のゴール

この節のゴールは、複数の Video データを List として持ち、その中から条件に合う動画を取り出せるようになることです。

最終的には、完成アプリの次のようなコードが読みやすくなります。

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,
  ),
];

そして、次のような操作ができるようになります。

for (final video in videos) {
  print(video.title);
}
final popularVideos = videos.where((video) {
  return video.views >= 10000;
}).toList();

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

List<Video> は、YouTube風アプリに表示する動画一覧の元データである。

まずListとは何か

List<Video> は、Video データが複数入った一覧です。

これまで、動画1本分のデータは Video classで表しました。

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

しかし、YouTube風アプリでは動画が1本だけではありません。

動画カードが何枚も並びます。

そのため、複数の VideoList に入れます。

final videos = [
  Video(
    title: 'FlutterでYouTube風UIを作る',
    channelName: 'Code Studio',
    views: 128000,
    publishedAt: '2日前',
    duration: '12:34',
    category: 'Flutter',
    isLive: false,
  ),
  Video(
    title: 'DartのListとMapを解説',
    channelName: 'App School',
    views: 8500,
    publishedAt: '5日前',
    duration: '08:21',
    category: 'Dart',
    isLive: false,
  ),
];

この videos が、動画一覧の元データです。

videos
├─ Video 1本目
├─ Video 2本目
└─ Video 3本目

Listを図で理解する

List<Video> は、次のようなイメージです。

List<Video>
├─ Video
│  ├─ title: FlutterでYouTube風UIを作る
│  ├─ channelName: Code Studio
│  ├─ views: 128000
│  └─ category: Flutter
│
├─ Video
│  ├─ title: DartのListとMapを解説
│  ├─ channelName: App School
│  ├─ views: 8500
│  └─ category: Dart
│
└─ Video
   ├─ title: UIデザインの基本
   ├─ channelName: Design Lab
   ├─ views: 24000
   └─ category: UI

List は、複数のデータを順番に並べるものです。

Video は、動画1本分のデータです。

つまり、List<Video> は、動画1本分のデータが複数並んだものです。

Mapの一覧とclassの一覧を比べる

3-2では、Mapを使って動画一覧を作りました。

final videos = [
  {
    'title': 'FlutterでYouTube風UIを作る',
    'views': 128000,
  },
  {
    'title': 'DartのListとMapを解説',
    'views': 8500,
  },
];

この形でも、動画一覧らしいデータは作れます。

しかし、Mapでは取り出すときに次のように書きます。

video['title']
video['views']

classを使うと、次のように書けます。

video.title
video.views

比較すると、classのほうが読みやすくなります。

比較Mapの一覧classの一覧
動画タイトルvideo['title']video.title
再生回数video['views']video.views
キーの打ち間違い起こりやすい起こりにくい
見えにくい分かりやすい
完成アプリ練習用実装で使う

今回の完成アプリでは、List<Map> ではなく、List<Video> を使います。

Dartだけで試す最小コード

まずは、Flutterの Color を使わない形で、List<Video> を試します。

void main() {
  final videos = [
    Video(
      title: 'FlutterでYouTube風UIを作る',
      channelName: 'Code Studio',
      views: 128000,
      publishedAt: '2日前',
      duration: '12:34',
      category: 'Flutter',
      isLive: false,
    ),
    Video(
      title: 'DartのListとMapを解説',
      channelName: 'App School',
      views: 8500,
      publishedAt: '5日前',
      duration: '08:21',
      category: 'Dart',
      isLive: false,
    ),
    Video(
      title: 'ライブ:Flutter質問会',
      channelName: 'Mobile Dev Live',
      views: 5600,
      publishedAt: '現在',
      duration: 'LIVE',
      category: 'LIVE',
      isLive: true,
    ),
  ];

  for (final video in videos) {
    print(video.title);
  }
}

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;
}

出力結果です。

FlutterでYouTube風UIを作る
DartのListとMapを解説
ライブ:Flutter質問会

ここで確認することは、次の2つです。

videos は、複数のVideoが入ったListである。
for文を使うと、Videoを1件ずつ取り出せる。

for文で動画を1件ずつ取り出す

次のコードを見てください。

for (final video in videos) {
  print(video.title);
}

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

videosの中から、videoを1件ずつ取り出す。
取り出したvideoのtitleを表示する。

もう少し具体的にすると、次の流れです。

1回目:
1本目のVideoをvideoに入れる
video.titleを表示する

2回目:
2本目のVideoをvideoに入れる
video.titleを表示する

3回目:
3本目のVideoをvideoに入れる
video.titleを表示する

video という名前は、自分で決めた一時的な名前です。

for (final video in videos)

ここでは、「videosの中の1本分」を video と呼んでいます。

動画タイトルとチャンネル名を表示する

次に、タイトルだけでなくチャンネル名も表示してみます。

void main() {
  final videos = [
    Video(
      title: 'FlutterでYouTube風UIを作る',
      channelName: 'Code Studio',
      views: 128000,
      publishedAt: '2日前',
      duration: '12:34',
      category: 'Flutter',
      isLive: false,
    ),
    Video(
      title: 'DartのListとMapを解説',
      channelName: 'App School',
      views: 8500,
      publishedAt: '5日前',
      duration: '08:21',
      category: 'Dart',
      isLive: false,
    ),
  ];

  for (final video in videos) {
    print('${video.title} / ${video.channelName}');
  }
}

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;
}

出力結果です。

FlutterでYouTube風UIを作る / Code Studio
DartのListとMapを解説 / App School

video.titlevideo.channelName を使うことで、動画1本分の情報を取り出せています。

getterを組み合わせて表示する

3-5で学んだ viewLabelmetaLabel も使えます。

次のように、Video classにgetterを追加します。

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

  return '$views回視聴';
}

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

  return '$viewLabel・$publishedAt';
}

これを使うと、一覧表示でもきれいな表示ができます。

void main() {
  final videos = [
    Video(
      title: 'FlutterでYouTube風UIを作る',
      channelName: 'Code Studio',
      views: 128000,
      publishedAt: '2日前',
      duration: '12:34',
      category: 'Flutter',
      isLive: false,
    ),
    Video(
      title: 'DartのListとMapを解説',
      channelName: 'App School',
      views: 8500,
      publishedAt: '5日前',
      duration: '08:21',
      category: 'Dart',
      isLive: false,
    ),
    Video(
      title: 'ライブ:Flutter質問会',
      channelName: 'Mobile Dev Live',
      views: 5600,
      publishedAt: '現在',
      duration: 'LIVE',
      category: 'LIVE',
      isLive: true,
    ),
  ];

  for (final video in videos) {
    print('${video.title} / ${video.metaLabel}');
  }
}

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;

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

    return '$views回視聴';
  }

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

    return '$viewLabel・$publishedAt';
  }
}

出力結果です。

FlutterでYouTube風UIを作る / 12.8万回視聴・2日前
DartのListとMapを解説 / 8500回視聴・5日前
ライブ:Flutter質問会 / ライブ配信中

ここで大切なのは、for 文の中では video.metaLabel と書くだけでよいことです。

metaLabel の中で、通常動画かライブ動画かを判断してくれています。

lengthで動画の件数を調べる

List には、何件入っているかを調べる length があります。

print(videos.length);

動画が3本入っていれば、出力結果は次のようになります。

3

完成アプリでは、この考え方が ListView.builderitemCount につながります。

itemCount: videos.length

これは、次の意味です。

videosの件数分だけ、動画カードを作る。

たとえば、videos.length が4なら、動画カードを4枚作ります。

コード意味
videos.length動画データの件数
itemCount: videos.length動画の件数分だけ表示する
videos[index]index番目の動画を取り出す

indexで何番目かを取り出す

List は、順番でデータを持っています。

最初のデータは0番目です。

print(videos[0].title);
print(videos[1].title);

videos[0] は1本目の動画です。

videos[1] は2本目の動画です。

書き方意味
videos[0]1本目のVideo
videos[1]2本目のVideo
videos[2]3本目のVideo

初心者がよく間違える点ですが、Listの番号は 1 ではなく 0 から始まります。

videos[index]の意味

完成アプリでは、次のコードが出てきます。

VideoCard(video: videos[index])

この videos[index] が少し難しく見えるかもしれません。

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

index番目の動画を取り出す。

たとえば、index が0なら、

videos[0]

です。

index が1なら、

videos[1]

です。

ListView.builder は、この index を使って、動画を1本ずつ取り出します。

index = 0
↓
videos[0] を取り出す
↓
1枚目のVideoCardを作る

index = 1
↓
videos[1] を取り出す
↓
2枚目のVideoCardを作る

今は、FlutterのUIを完全に理解しなくても大丈夫です。

videos[index] は、動画一覧から1件取り出す書き方だと理解してください。

条件に合う動画だけ取り出す

YouTube風アプリでは、動画一覧の中から条件に合う動画だけを表示したくなることがあります。

たとえば、次のような条件です。

再生回数が10000以上の動画だけ
Dartカテゴリの動画だけ
ライブ配信中の動画だけ

このようなときに使うのが、where です。

where は、Listの中から条件に合うものだけを取り出すために使います。

人気動画だけ取り出す

まず、再生回数が10000以上の動画だけを取り出します。

void main() {
  final videos = [
    Video(
      title: 'FlutterでYouTube風UIを作る',
      channelName: 'Code Studio',
      views: 128000,
      publishedAt: '2日前',
      duration: '12:34',
      category: 'Flutter',
      isLive: false,
    ),
    Video(
      title: 'DartのListとMapを解説',
      channelName: 'App School',
      views: 8500,
      publishedAt: '5日前',
      duration: '08:21',
      category: 'Dart',
      isLive: false,
    ),
    Video(
      title: 'UIデザインの基本',
      channelName: 'Design Lab',
      views: 24000,
      publishedAt: '1週間前',
      duration: '15:10',
      category: 'UI',
      isLive: false,
    ),
  ];

  final popularVideos = videos.where((video) {
    return video.views >= 10000;
  }).toList();

  for (final video in popularVideos) {
    print('${video.title} / ${video.viewLabel}');
  }
}

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;

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

    return '$views回視聴';
  }
}

出力結果です。

FlutterでYouTube風UIを作る / 12.8万回視聴
UIデザインの基本 / 2.4万回視聴

DartのListとMapを解説8500 回なので、今回は表示されません。

whereの読み方

次のコードを分解します。

final popularVideos = videos.where((video) {
  return video.views >= 10000;
}).toList();

初心者には少し難しく見えるかもしれません。

日本語で読むと、次の意味です。

videosの中から、
video.views が 10000以上のものだけを残して、
新しいListにする。

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

部分意味
videos元の動画一覧
where条件に合うものだけを取り出す
(video) { ... }動画を1件ずつ確認する
video.views >= 10000再生回数が10000以上かどうか
.toList()結果をListにする

where は、YouTube風アプリのカテゴリ切り替えや絞り込みに使える重要な考え方です。

ここまでのまとめ

前半では、List<Video> を使って、動画一覧を操作する基本を学びました。

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

- List<Video> は、複数の動画データを持つ一覧である
- for文を使うと、動画を1件ずつ取り出せる
- videos.length で動画の件数が分かる
- videos[index] でindex番目の動画を取り出せる
- whereを使うと、条件に合う動画だけを取り出せる
- viewLabelやmetaLabelと組み合わせると、表示用の文字も簡単に使える

前半で特に大切なこと

YouTube風アプリの動画一覧は、List<Video>を1件ずつ取り出して表示することで作られる。

カテゴリで動画を取り出す

前半では、where を使って、再生回数が10000以上の人気動画だけを取り出しました。

final popularVideos = videos.where((video) {
  return video.views >= 10000;
}).toList();

後半では、YouTube風アプリらしく、カテゴリで動画を取り出す方法を学びます。

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

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

このようなカテゴリを押したときに、該当する動画だけを表示できるようにするには、動画データの category を使います。

categoryとは何か

category は、動画がどの種類に属しているかを表すpropertyです。

final String category;

たとえば、動画データには次のように入れます。

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

この動画は、Flutter カテゴリの動画です。

別の動画なら、次のようになります。

Video(
  title: 'DartのListとMapを解説',
  channelName: 'App School',
  views: 8500,
  publishedAt: '5日前',
  duration: '08:21',
  category: 'Dart',
  isLive: false,
)

この動画は、Dart カテゴリの動画です。

動画タイトルcategory
FlutterでYouTube風UIを作るFlutter
DartのListとMapを解説Dart
UIデザインの基本UI
ライブ:Flutter質問会LIVE

Dartカテゴリだけを取り出す

まずは、Dart カテゴリの動画だけを取り出してみます。

void main() {
  final videos = [
    Video(
      title: 'FlutterでYouTube風UIを作る',
      channelName: 'Code Studio',
      views: 128000,
      publishedAt: '2日前',
      duration: '12:34',
      category: 'Flutter',
      isLive: false,
    ),
    Video(
      title: 'DartのListとMapを解説',
      channelName: 'App School',
      views: 8500,
      publishedAt: '5日前',
      duration: '08:21',
      category: 'Dart',
      isLive: false,
    ),
    Video(
      title: 'UIデザインの基本',
      channelName: 'Design Lab',
      views: 24000,
      publishedAt: '1週間前',
      duration: '15:10',
      category: 'UI',
      isLive: false,
    ),
  ];

  final dartVideos = videos.where((video) {
    return video.category == 'Dart';
  }).toList();

  for (final video in dartVideos) {
    print('${video.title} / ${video.category}');
  }
}

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;
}

出力結果です。

DartのListとMapを解説 / Dart

このコードでは、video.category == 'Dart' の動画だけを残しています。

== の意味

次の部分を見てください。

video.category == 'Dart'

== は、左と右が同じかどうかを確認する記号です。

video.category が Dart と同じなら true
違うなら false
video.category条件結果
Fluttercategory == 'Dart'false
Dartcategory == 'Dart'true
UIcategory == 'Dart'false

where は、この結果が true になったものだけを残します。

true になった動画だけが、新しいListに残る。

ライブ動画だけを取り出す

次に、ライブ配信中の動画だけを取り出します。

ライブ配信中かどうかは、isLive で判断できます。

final bool isLive;

isLivetrue ならライブ配信中です。

void main() {
  final videos = [
    Video(
      title: 'FlutterでYouTube風UIを作る',
      channelName: 'Code Studio',
      views: 128000,
      publishedAt: '2日前',
      duration: '12:34',
      category: 'Flutter',
      isLive: false,
    ),
    Video(
      title: 'ライブ:Flutter質問会',
      channelName: 'Mobile Dev Live',
      views: 5600,
      publishedAt: '現在',
      duration: 'LIVE',
      category: 'LIVE',
      isLive: true,
    ),
  ];

  final liveVideos = videos.where((video) {
    return video.isLive;
  }).toList();

  for (final video in liveVideos) {
    print('${video.title} / ライブ中');
  }
}

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;
}

出力結果です。

ライブ:Flutter質問会 / ライブ中

ここで使っている条件は、次の部分です。

return video.isLive;

isLive はもともと true または false なので、そのまま条件として使えます。

isLivewhereで残るか
true残る
false残らない

isLive == true と書いてもよい

初心者のうちは、次のように書いたほうが分かりやすいかもしれません。

return video.isLive == true;

これは、「isLiveがtrueなら残す」という意味です。

ただし、isLive 自体が true / false の値なので、Dartでは次のように短く書けます。

return video.isLive;

どちらも同じ意味です。

書き方意味
video.isLive == trueisLiveがtrueか確認する
video.isLiveisLiveがtrueならそのままtrue

最初は分かりやすさを優先して、video.isLive == true と読んでも大丈夫です。

ライブではない動画を取り出す

逆に、ライブではない通常動画だけを取り出したい場合もあります。

そのときは、! を使います。

final normalVideos = videos.where((video) {
  return !video.isLive;
}).toList();

! は、「ではない」という意味です。

!video.isLive
↓
isLiveではない
↓
ライブ配信中ではない
isLive!isLive
truefalse
falsetrue

つまり、!video.isLive は、通常動画だけを残す条件になります。

「すべて」カテゴリを考える

YouTube風アプリのカテゴリバーには、よく「すべて」があります。

すべて
Flutter
Dart
UI
ライブ

「すべて」を選んだときは、絞り込みをせず、全部の動画を表示したいです。

考え方は次の通りです。

選ばれたカテゴリが「すべて」
↓
videosをそのまま表示する

選ばれたカテゴリが「Dart」
↓
categoryがDartの動画だけ表示する

コードでは、次のように考えられます。

List<Video> filterVideosByCategory(
  List<Video> videos,
  String selectedCategory,
) {
  if (selectedCategory == 'すべて') {
    return videos;
  }

  return videos.where((video) {
    return video.category == selectedCategory;
  }).toList();
}

このコードは、少し実践的です。

でも、分解すると難しくありません。

filterVideosByCategoryの読み方

次の関数を見てみます。

List<Video> filterVideosByCategory(
  List<Video> videos,
  String selectedCategory,
) {
  if (selectedCategory == 'すべて') {
    return videos;
  }

  return videos.where((video) {
    return video.category == selectedCategory;
  }).toList();
}

これは、次の意味です。

動画一覧と選ばれたカテゴリを受け取る。

もし選ばれたカテゴリが「すべて」なら、
動画一覧をそのまま返す。

そうでなければ、
選ばれたカテゴリと同じcategoryの動画だけを返す。

表で整理します。

部分意味
List<Video> videos元の動画一覧
String selectedCategory選ばれたカテゴリ
selectedCategory == 'すべて'すべてが選ばれているか
return videos;全部の動画を返す
video.category == selectedCategory動画のカテゴリが選択中カテゴリと同じか
.toList()結果をListにする

カテゴリ絞り込みをDartPadで試す

実際に、カテゴリで動画を絞り込んでみます。

void main() {
  final videos = [
    Video(
      title: 'FlutterでYouTube風UIを作る',
      channelName: 'Code Studio',
      views: 128000,
      publishedAt: '2日前',
      duration: '12:34',
      category: 'Flutter',
      isLive: false,
    ),
    Video(
      title: 'DartのListとMapを解説',
      channelName: 'App School',
      views: 8500,
      publishedAt: '5日前',
      duration: '08:21',
      category: 'Dart',
      isLive: false,
    ),
    Video(
      title: 'UIデザインの基本',
      channelName: 'Design Lab',
      views: 24000,
      publishedAt: '1週間前',
      duration: '15:10',
      category: 'UI',
      isLive: false,
    ),
  ];

  final selectedCategory = 'Dart';

  final filteredVideos = filterVideosByCategory(
    videos,
    selectedCategory,
  );

  for (final video in filteredVideos) {
    print('${video.title} / ${video.category}');
  }
}

List<Video> filterVideosByCategory(
  List<Video> videos,
  String selectedCategory,
) {
  if (selectedCategory == 'すべて') {
    return videos;
  }

  return videos.where((video) {
    return video.category == selectedCategory;
  }).toList();
}

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;
}

出力結果です。

DartのListとMapを解説 / Dart

selectedCategory を変えると、表示される動画も変わります。

final selectedCategory = 'Flutter';

にすれば、Flutterカテゴリの動画が表示されます。

final selectedCategory = 'すべて';

にすれば、全部の動画が表示されます。

検索の考え方も同じ

YouTube風アプリでは、検索機能も考えられます。

たとえば、タイトルに Dart を含む動画だけを取り出したいとします。

その場合も where を使えます。

final searchResults = videos.where((video) {
  return video.title.contains('Dart');
}).toList();

contains は、「含んでいるか」を調べるmethodです。

video.title に Dart という文字が含まれているか確認する。
titlecontains('Dart')
FlutterでYouTube風UIを作るfalse
DartのListとMapを解説true
UIデザインの基本false

このように、検索も「条件に合う動画だけを取り出す」という点では、カテゴリ絞り込みと同じです。

タイトル検索をDartPadで試す

void main() {
  final videos = [
    Video(
      title: 'FlutterでYouTube風UIを作る',
      channelName: 'Code Studio',
      views: 128000,
      publishedAt: '2日前',
      duration: '12:34',
      category: 'Flutter',
      isLive: false,
    ),
    Video(
      title: 'DartのListとMapを解説',
      channelName: 'App School',
      views: 8500,
      publishedAt: '5日前',
      duration: '08:21',
      category: 'Dart',
      isLive: false,
    ),
    Video(
      title: 'UIデザインの基本',
      channelName: 'Design Lab',
      views: 24000,
      publishedAt: '1週間前',
      duration: '15:10',
      category: 'UI',
      isLive: false,
    ),
  ];

  final results = videos.where((video) {
    return video.title.contains('Dart');
  }).toList();

  for (final video in results) {
    print(video.title);
  }
}

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;
}

出力結果です。

DartのListとMapを解説

検索も、カテゴリ絞り込みも、人気動画の抽出も、基本は同じです。

Listの中から、
条件に合うVideoだけを取り出す。

sortで並び替える

動画一覧では、並び替えをしたくなることもあります。

たとえば、再生回数が多い順に並べたい場合です。

そのときは、sort を使えます。

videos.sort((a, b) {
  return b.views.compareTo(a.views);
});

少し難しく見えますが、これは「viewsが多い順に並べる」という意味です。

ただし、sort は元のListの順番を直接変えます。

初心者のうちは、元のListを直接変えないように、コピーを作ってから並び替えると安全です。

final sortedVideos = [...videos];

sortedVideos.sort((a, b) {
  return b.views.compareTo(a.views);
});

[...videos] は、videos のコピーを作る書き方です。

再生回数が多い順に並べる

void main() {
  final videos = [
    Video(
      title: 'FlutterでYouTube風UIを作る',
      channelName: 'Code Studio',
      views: 128000,
      publishedAt: '2日前',
      duration: '12:34',
      category: 'Flutter',
      isLive: false,
    ),
    Video(
      title: 'DartのListとMapを解説',
      channelName: 'App School',
      views: 8500,
      publishedAt: '5日前',
      duration: '08:21',
      category: 'Dart',
      isLive: false,
    ),
    Video(
      title: 'UIデザインの基本',
      channelName: 'Design Lab',
      views: 24000,
      publishedAt: '1週間前',
      duration: '15:10',
      category: 'UI',
      isLive: false,
    ),
  ];

  final sortedVideos = [...videos];

  sortedVideos.sort((a, b) {
    return b.views.compareTo(a.views);
  });

  for (final video in sortedVideos) {
    print('${video.title} / ${video.views}');
  }
}

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;
}

出力結果です。

FlutterでYouTube風UIを作る / 128000
UIデザインの基本 / 24000
DartのListとMapを解説 / 8500

再生回数が多い順に並びました。

sortの読み方

次のコードを分解します。

sortedVideos.sort((a, b) {
  return b.views.compareTo(a.views);
});

初心者のうちは、まず次の理解で大丈夫です。

b.views.compareTo(a.views)
↓
再生回数が多い順に並べる
書き方並び順
b.views.compareTo(a.views)多い順
a.views.compareTo(b.views)少ない順

並び替えは少し難しいので、この節では「こう書くと再生回数順にできる」と理解できれば十分です。

完成アプリのListView.builderにつながる

完成アプリでは、videosListView.builder に渡して、動画カードを作っています。

Expanded(
  child: ListView.builder(
    padding: EdgeInsets.zero,
    itemCount: videos.length,
    itemBuilder: (context, index) {
      return VideoCard(video: videos[index]);
    },
  ),
)

このコードの中心は、次の3つです。

コード意味
itemCount: videos.length動画の件数分だけ作る
index何番目を作っているか
videos[index]index番目の動画データを取り出す

つまり、前半で学んだ内容が、そのままFlutterのUIにつながっています。

List<Video>
↓
videos.length
↓
videos[index]
↓
VideoCard(video: videos[index])
↓
動画カードとして表示

絞り込んだListを表示に使うイメージ

今の完成アプリでは、単純に videos をそのまま表示しています。

VideoCard(video: videos[index])

しかし、カテゴリで絞り込むなら、次のようなイメージになります。

final filteredVideos = filterVideosByCategory(
  videos,
  selectedCategory,
);

そして、表示に使うListを filteredVideos にします。

ListView.builder(
  itemCount: filteredVideos.length,
  itemBuilder: (context, index) {
    return VideoCard(video: filteredVideos[index]);
  },
)

つまり、UIに表示する前に、どのListを使うかを決めます。

元のvideos
↓
カテゴリで絞り込む
↓
filteredVideos
↓
ListView.builderで表示

この考え方が分かると、YouTube風アプリのカテゴリ切り替えや検索機能に近づきます。

手を動かす練習1:Dartカテゴリだけ取り出す

次の videos から、categoryDart の動画だけを取り出してください。

final videos = [
  Video(
    title: 'FlutterでYouTube風UIを作る',
    category: 'Flutter',
  ),
  Video(
    title: 'DartのListとMapを解説',
    category: 'Dart',
  ),
  Video(
    title: 'UIデザインの基本',
    category: 'UI',
  ),
];

解答例

final dartVideos = videos.where((video) {
  return video.category == 'Dart';
}).toList();

手を動かす練習2:ライブ動画だけ取り出す

isLivetrue の動画だけを取り出してください。

解答例

final liveVideos = videos.where((video) {
  return video.isLive;
}).toList();

または、初心者向けに分かりやすく書くなら、次のようにも書けます。

final liveVideos = videos.where((video) {
  return video.isLive == true;
}).toList();

手を動かす練習3:「すべて」なら全部返す

選択されたカテゴリが すべて の場合は全部の動画を返し、それ以外ならカテゴリで絞り込む関数を作ってください。

解答例

List<Video> filterVideosByCategory(
  List<Video> videos,
  String selectedCategory,
) {
  if (selectedCategory == 'すべて') {
    return videos;
  }

  return videos.where((video) {
    return video.category == selectedCategory;
  }).toList();
}

手を動かす練習4:タイトル検索をする

タイトルに Flutter を含む動画だけを取り出してください。

解答例

final results = videos.where((video) {
  return video.title.contains('Flutter');
}).toList();

手を動かす練習5:再生回数が多い順に並べる

動画を再生回数が多い順に並べてください。

解答例

final sortedVideos = [...videos];

sortedVideos.sort((a, b) {
  return b.views.compareTo(a.views);
});

よくあるつまずき1:whereの中で何を書けばよいか分からない

where の中には、「残したい条件」を書きます。

videos.where((video) {
  return 条件;
}).toList();

たとえば、Dartカテゴリだけ残したいなら、

return video.category == 'Dart';

ライブ動画だけ残したいなら、

return video.isLive;

人気動画だけ残したいなら、

return video.views >= 10000;

このように、where は「この条件に合うものだけ残す」と考えると分かりやすいです。

よくあるつまずき2:toListを忘れる

where の最後には、よく .toList() をつけます。

final dartVideos = videos.where((video) {
  return video.category == 'Dart';
}).toList();

初心者向けには、次の理解で大丈夫です。

whereで絞り込んだ結果を、もう一度Listとして使える形にする。

.toList() を忘れると、Listとして扱いたい場面で分かりにくくなることがあります。

この教材では、where の後は .toList() をつける、と覚えておくと詰まりにくいです。

よくあるつまずき3:= と == を間違える

条件を書くときは、== を使います。

video.category == 'Dart'

これは、「同じかどうかを確認する」という意味です。

一方、= は値を入れるときに使います。

final selectedCategory = 'Dart';
記号意味
=値を入れるselectedCategory = 'Dart'
==同じか確認するvideo.category == 'Dart'

初心者がとてもよく間違える部分なので、注意してください。

よくあるつまずき4:Listのindexは0から始まる

videos[0] は1本目の動画です。

videos[0]

videos[1] は2本目の動画です。

書き方意味
videos[0]1本目
videos[1]2本目
videos[2]3本目

3本の動画がある場合、最後は videos[2] です。

videos[3] は存在しません。

よくあるつまずき5:元のListを直接sortしてしまう

sort は、Listの順番を直接変えます。

videos.sort((a, b) {
  return b.views.compareTo(a.views);
});

これは便利ですが、元の順番を残したい場合は注意が必要です。

安全に練習するなら、コピーを作ってから並び替えます。

final sortedVideos = [...videos];

sortedVideos.sort((a, b) {
  return b.views.compareTo(a.views);
});

初心者のうちは、並び替えではコピーを作ると覚えておくと安心です。

この節で覚える対応表

やりたいこと使うもの
複数の動画を持つList<Video>final videos = [...]
件数を調べる.lengthvideos.length
何番目かを取り出す[index]videos[index]
1件ずつ取り出すforfor (final video in videos)
条件で絞るwherevideo.category == 'Dart'
結果をListにする.toList()where(...).toList()
文字を含むか調べるcontainsvideo.title.contains('Dart')
並び替えるsortb.views.compareTo(a.views)

確認問題1

List<Video> は何を表しますか。

答え

複数の Video データをまとめた一覧です。

YouTube風アプリでは、動画一覧画面の元データになります。

確認問題2

次のコードは何をしていますか。

final dartVideos = videos.where((video) {
  return video.category == 'Dart';
}).toList();

答え

videos の中から、categoryDart の動画だけを取り出して、新しいListにしています。

確認問題3

videos.length は何を表しますか。

答え

videos の中に入っている動画データの件数です。

完成アプリでは、動画カードを何枚作るかを決めるために使えます。

確認問題4

videos[index] は何を表しますか。

答え

videos の中から、index 番目の動画を取り出す書き方です。

index が0なら1本目、1なら2本目の動画です。

確認問題5

カテゴリが すべて の場合、なぜそのまま videos を返すのですか。

答え

すべて は絞り込みをしないという意味だからです。

そのため、条件で減らさず、元の動画一覧をそのまま返します。

この節のまとめ

この節では、ListMapclass の考え方を組み合わせて、動画一覧を操作する方法を学びました。

完成アプリでは、動画1本分を Video classで表し、それを List<Video> として複数持ちます。

final videos = [
  Video(
    title: 'FlutterでYouTube風UIを作る',
    channelName: 'Code Studio',
    views: 128000,
    publishedAt: '2日前',
    duration: '12:34',
    category: 'Flutter',
    isLive: false,
  ),
  Video(
    title: 'DartのListとMapを解説',
    channelName: 'App School',
    views: 8500,
    publishedAt: '5日前',
    duration: '08:21',
    category: 'Dart',
    isLive: false,
  ),
];

この videos に対して、次のような操作ができます。

- forで1件ずつ取り出す
- lengthで件数を調べる
- indexで何番目かを取り出す
- whereで条件に合う動画だけ取り出す
- containsで検索する
- sortで並び替える

完成アプリの ListView.builder は、まさにこの考え方を使っています。

ListView.builder(
  itemCount: videos.length,
  itemBuilder: (context, index) {
    return VideoCard(video: videos[index]);
  },
)

後半で一番大切なこと

YouTube風アプリの動画一覧は、List<Video>を条件に応じて操作し、1件ずつVideoCardに渡すことで表示される。

次の 3-8 では、YouTube風ミニアプリ用のクラス設計を行います。

Video だけでなく、ChannelCategory のようなデータも考えながら、アプリ全体のデータ設計に近づいていきます。

教材トップへ戻る