FlutterとGraphQLを使ったチケットアプリの作成

## はじめに

このチュートリアルでは、FlutterとGraphQLを使用してデジタルチケット販売アプリを作成します。ユーザーがチケットを購入でき、イベント主催者がイベントを登録できるアプリを構築します[1][3]。

## プロジェクトのセットアップ

まず、新しいFlutterプロジェクトを作成します:

```
flutter create digital_ticket_app
cd digital_ticket_app
```

次に、必要なパッケージをpubspec.yamlに追加します:

```yaml
dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^5.1.2
  intl: ^0.17.0
```

パッケージをインストールします:

```
flutter pub get
```

## GraphQLスキーマの定義

`lib/graphql/schema.graphql`ファイルを作成し、以下のスキーマを定義します:

```graphql
type Event {
  id: ID!
  title: String!
  description: String!
  date: String!
  price: Float!
  availableTickets: Int!
}

type Ticket {
  id: ID!
  eventId: ID!
  userId: ID!
  purchaseDate: String!
}

type Query {
  events: [Event!]!
  myTickets(userId: ID!): [Ticket!]!
}

type Mutation {
  createEvent(title: String!, description: String!, date: String!, price: Float!, availableTickets: Int!): Event!
  purchaseTicket(eventId: ID!, userId: ID!): Ticket!
}
```

## GraphQLクライアントの設定

`lib/graphql/client.dart`ファイルを作成し、GraphQLクライアントを設定します:

```dart
import 'package:graphql_flutter/graphql_flutter.dart';

final HttpLink httpLink = HttpLink('YOUR_GRAPHQL_ENDPOINT');

final GraphQLClient client = GraphQLClient(
  link: httpLink,
  cache: GraphQLCache(),
);

ValueNotifier<GraphQLClient> clientNotifier = ValueNotifier(client);
```

## イベント一覧画面の作成

`lib/screens/event_list_screen.dart`ファイルを作成し、イベント一覧を表示する画面を実装します:

```dart
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

class EventListScreen extends StatelessWidget {
  const EventListScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('イベント一覧')),
      body: Query(
        options: QueryOptions(
          document: gql('''
            query GetEvents {
              events {
                id
                title
                date
                price
                availableTickets
              }
            }
          '''),
        ),
        builder: (QueryResult result, {VoidCallback? refetch, FetchMore? fetchMore}) {
          if (result.hasException) {
            return Text(result.exception.toString());
          }

          if (result.isLoading) {
            return Center(child: CircularProgressIndicator());
          }

          List<dynamic> events = result.data?['events'] ?? [];

          return ListView.builder(
            itemCount: events.length,
            itemBuilder: (context, index) {
              final event = events[index];
              return ListTile(
                title: Text(event['title']),
                subtitle: Text('${event['date']} - ¥${event['price']}'),
                trailing: Text('残り${event['availableTickets']}枚'),
                onTap: () {
                  // イベント詳細画面に遷移
                },
              );
            },
          );
        },
      ),
    );
  }
}
```

## チケット購入機能の実装

`lib/screens/event_detail_screen.dart`ファイルを作成し、チケット購入機能を実装します:

```dart
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

class EventDetailScreen extends StatelessWidget {
  final String eventId;

  const EventDetailScreen({Key? key, required this.eventId}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('イベント詳細')),
      body: Query(
        options: QueryOptions(
          document: gql('''
            query GetEvent(\$id: ID!) {
              event(id: \$id) {
                id
                title
                description
                date
                price
                availableTickets
              }
            }
          '''),
          variables: {'id': eventId},
        ),
        builder: (QueryResult result, {VoidCallback? refetch, FetchMore? fetchMore}) {
          if (result.hasException) {
            return Text(result.exception.toString());
          }

          if (result.isLoading) {
            return Center(child: CircularProgressIndicator());
          }

          final event = result.data?['event'];

          return Padding(
            padding: EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(event['title'], style: Theme.of(context).textTheme.headline5),
                SizedBox(height: 8),
                Text(event['description']),
                SizedBox(height: 16),
                Text('日時: ${event['date']}'),
                Text('価格: ¥${event['price']}'),
                Text('残りチケット: ${event['availableTickets']}枚'),
                SizedBox(height: 24),
                ElevatedButton(
                  child: Text('チケットを購入'),
                  onPressed: () {
                    // チケット購入処理
                  },
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}
```

## イベント作成画面の実装

`lib/screens/create_event_screen.dart`ファイルを作成し、イベント作成機能を実装します:

```dart
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

class CreateEventScreen extends StatefulWidget {
  @override
  _CreateEventScreenState createState() => _CreateEventScreenState();
}

class _CreateEventScreenState extends State<CreateEventScreen> {
  final _formKey = GlobalKey<FormState>();
  String _title = '';
  String _description = '';
  DateTime _date = DateTime.now();
  double _price = 0.0;
  int _availableTickets = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('イベント作成')),
      body: Mutation(
        options: MutationOptions(
          document: gql('''
            mutation CreateEvent(\$title: String!, \$description: String!, \$date: String!, \$price: Float!, \$availableTickets: Int!) {
              createEvent(title: \$title, description: \$description, date: \$date, price: \$price, availableTickets: \$availableTickets) {
                id
                title
              }
            }
          '''),
          onCompleted: (dynamic resultData) {
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('イベントが作成されました')));
            Navigator.pop(context);
          },
        ),
        builder: (RunMutation runMutation, QueryResult? result) {
          return Form(
            key: _formKey,
            child: ListView(
              padding: EdgeInsets.all(16.0),
              children: [
                TextFormField(
                  decoration: InputDecoration(labelText: 'タイトル'),
                  validator: (value) => value!.isEmpty ? 'タイトルを入力してください' : null,
                  onSaved: (value) => _title = value!,
                ),
                TextFormField(
                  decoration: InputDecoration(labelText: '説明'),
                  validator: (value) => value!.isEmpty ? '説明を入力してください' : null,
                  onSaved: (value) => _description = value!,
                ),
                // 日付、価格、チケット数のフィールドも追加
                ElevatedButton(
                  child: Text('イベントを作成'),
                  onPressed: () {
                    if (_formKey.currentState!.validate()) {
                      _formKey.currentState!.save();
                      runMutation({
                        'title': _title,
                        'description': _description,
                        'date': _date.toIso8601String(),
                        'price': _price,
                        'availableTickets': _availableTickets,
                      });
                    }
                  },
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}
```

## まとめ

このチュートリアルでは、FlutterとGraphQLを使用してデジタルチケット販売アプリの基本的な機能を実装しました。イベント一覧の表示、チケット購入、イベント作成などの主要機能を含んでいます[4][5]。

実際のアプリケーションでは、認証機能やエラーハンドリング、より詳細なUI/UXの実装が必要になります。また、バックエンドのGraphQLサーバーの実装も必要です。

このアプリをさらに発展させるには、以下の機能を追加することをおすすめします:

- ユーザー認証
- チケットのQRコード生成
- イベント検索機能
- お気に入りイベント登録
- プッシュ通知

これらの機能を追加することで、より実用的なデジタルチケットアプリケーションを作成できます