【Flutter/Dart】sealedクラスを用いて、SNSフィード画面のような複数の型を持つリストの表現する

こんにちは、エキサイト株式会社でアプリエンジニアをしている岡島です。今回はsealedクラスを用いて複数の型を持つリストを表現する方法について共有したいと思います。

複数の型を持つリストの表現

例えば、SNSのフィード画面のように投稿、広告、おすすめユーザーなどをまとめてリストで持ちたいときはどうすればいいか、sealedクラスを用いた実装を見ていきたいと思います。

実装例

sealedクラスを用いた型の定義

以下の例では、フィード画面に存在する投稿(Post)、広告(Ad)、おすすめユーザー(UserSuggestion)という3つの異なる型を定義しました。

sealed class FeedItem {}

class Post extends FeedItem {
  Post(this.content);
  final String content;
}

class Ad extends FeedItem {
  Ad(this.title);
  final String title;
}

class UserSuggestion extends FeedItem {
  UserSuggestion(this.username);
  final String username;
}

投稿、広告、おすすめユーザー用のウィジェットの用意

今回の例では、SNSのフィード画面を想定し、投稿・広告・おすすめユーザーという三種類のウィジェットを用意します。

例では簡単に、Textウィジェットの色をそれぞれ変えるようにしました。

class PostWidget extends StatelessWidget {
  const PostWidget({super.key, required this.post});
  final Post post;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(
        '投稿: ${post.content}',
        style: const TextStyle(color: Colors.black),
      ),
    );
  }
}

class AdWidget extends StatelessWidget {
  const AdWidget({super.key, required this.ad});
  final Ad ad;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(
        '広告: ${ad.title}',
        style: const TextStyle(color: Colors.red),
      ),
    );
  }
}

class UserSuggestionWidget extends StatelessWidget {
  const UserSuggestionWidget({super.key, required this.suggestion});
  final UserSuggestion suggestion;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(
        'おすすめユーザー: ${suggestion.username}',
        style: const TextStyle(color: Colors.blue),
      ),
    );
  }
}

sealedクラスのパターンマッチを利用したリストの表示

続いて、sealedクラスのパターンマッチを利用して、リストを表示していきます。 この実装により、異なる型のアイテムを一つのリストで管理し、それぞれのアイテムでウィジェットを使い分けることができます。

class MyHomePage extends StatelessWidget {
  MyHomePage({super.key, required this.title});
  final String title;

  final List<FeedItem> feedList = [
    Post('Hello world!'),
    UserSuggestion('user1'),
    Ad('Ad1!'),
    Ad('Ad2!'),
    Post('hoge!hoge!'),
    UserSuggestion('user2'),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: ListView.builder(
        itemCount: feedList.length,
        itemBuilder: (context, index) {
          final item = feedList[index];
          return switch (item) {
            final Post post => PostWidget(post: post),
            final Ad ad => AdWidget(ad: ad),
            final UserSuggestion suggestion =>
            UserSuggestionWidget(suggestion: suggestion),
          };
        },
      ),
    );
  }
}

実際の画面

sealedクラスの利点

  • バグになりにくい
    sealedクラスを使用することで、FeedItemの全てのサブタイプを網羅的に処理することできます。 新しい種類のフィードアイテムを追加した場合、コンパイラがパターンマッチングの不足を指摘してくれるため、バグの早期発見につながります。

  • コードの可読性向上
    異なる型のリストを一箇所のリストで保持することができるため、プログラムを簡潔結に書くことができます。

  • サブクラスごとに異なるパラメータを持つことができる
    Postのクラスにいいね数やimageを持たせ、UserSuggestionにはユーザーネームやフォロー状態を持たせるというように、各サブクラスに合わせたデータ構造を定義することができます。

まとめ

sealedクラスを使用することで、SNSのフィード画面のような複数の型を持つリストを扱う実装ができました。他にもAPIのレスポンスを扱う時なども役に立ちそうです。sealedクラスについて今まで触ったことがなかったのですが、実際に使ってみると結構便利だということが分かりました。

最後まで読んでいただき、ありがとうございました。