FlutterとFlaskを使用したTimeeみたいなパートタイム求人アプリの作成



### はじめに

こんにちは！今回は、FlutterとFlaskを使用して、ユーザーが1時間または2時間のパートタイム求人に応募でき、管理者が新しい求人を投稿できるアプリを作成する方法をご紹介します。

APIはローカルホストのポート8000で動作すると仮定します。

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

まず、新しいFlutterプロジェクトを作成しましょう。

```bash
flutter create part_time_job_app
cd part_time_job_app
```

### APIとの通信

FlutterアプリからFlask APIと通信するために、`http`パッケージを使用します。`pubspec.yaml`に以下を追加してください：

```yaml
dependencies:
  http: ^0.13.3
```

### 求人一覧の表示

FlaskAPIから求人一覧を取得し、表示する機能を実装します。

```dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Part-Time Job App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: JobListScreen(),
    );
  }
}

class Job {
  final int id;
  final String title;
  final String description;
  final int duration; // Duration in hours

  Job({required this.id, required this.title, required this.description, required this.duration});

  factory Job.fromJson(Map<String, dynamic> json) {
    return Job(
      id: json['id'],
      title: json['title'],
      description: json['description'],
      duration: json['duration'],
    );
  }
}

class JobListScreen extends StatefulWidget {
  @override
  _JobListScreenState createState() => _JobListScreenState();
}

class _JobListScreenState extends State<JobListScreen> {
  late Future<List<Job>> futureJobs;

  @override
  void initState() {
    super.initState();
    futureJobs = fetchJobs();
  }

  Future<List<Job>> fetchJobs() async {
    final response = await http.get(Uri.parse('http://localhost:8000/jobs'));
    if (response.statusCode == 200) {
      List<dynamic> jobsJson = json.decode(response.body);
      return jobsJson.map((job) => Job.fromJson(job)).toList();
    } else {
      throw Exception('Failed to load jobs');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Part-Time Jobs'),
      ),
      body: FutureBuilder<List<Job>>(
        future: futureJobs,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
            return Center(child: Text('No jobs available'));
          } else {
            return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) {
                Job job = snapshot.data![index];
                return ListTile(
                  title: Text(job.title),
                  subtitle: Text('${job.description} - ${job.duration} hours'),
                  onTap: () => applyForJob(job.id),
                );
              },
            );
          }
        },
      ),
    );
  }

  Future<void> applyForJob(int jobId) async {
    final response = await http.post(
      Uri.parse('http://localhost:8000/apply'),
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode(<String, int>{
        'job_id': jobId,
      }),
    );

    if (response.statusCode == 200) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Applied successfully')));
    } else {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to apply')));
    }
  }
}
```

### 管理者用求人投稿機能

管理者が新しい求人を投稿できる機能を実装します。

```dart
class AdminScreen extends StatelessWidget {
  final TextEditingController titleController = TextEditingController();
  final TextEditingController descriptionController = TextEditingController();
  final TextEditingController durationController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Post New Job'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: titleController,
              decoration: InputDecoration(labelText: 'Job Title'),
            ),
            TextField(
              controller: descriptionController,
              decoration: InputDecoration(labelText: 'Job Description'),
            ),
            TextField(
              controller: durationController,
              decoration: InputDecoration(labelText: 'Duration (hours)'),
              keyboardType: TextInputType.number,
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => postNewJob(
                titleController.text,
                descriptionController.text,
                int.parse(durationController.text),
              ),
              child: Text('Post Job'),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> postNewJob(String title, String description, int duration) async {
    final response = await http.post(
      Uri.parse('http://localhost:8000/jobs'),
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode(<String, dynamic>{
        'title': title,
        'description': description,
        'duration': duration,
      }),
    );

    if (response.statusCode == 201) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Job posted successfully')));
    } else {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to post job')));
    }
  }
}
```

### Flask APIの実装

最後に、Flask APIを実装します。

```python
from flask import Flask, request, jsonify

app = Flask(__name__)

jobs = []
applications = []

@app.route('/jobs', methods=['GET', 'POST'])
def manage_jobs():
    if request.method == 'POST':
        job = request.get_json()
        job['id'] = len(jobs) + 1
        jobs.append(job)
        return jsonify(job), 201
    else:
        return jsonify(jobs)

@app.route('/apply', methods=['POST'])
def apply_job():
    application = request.get_json()
    applications.append(application)
    return '', 200

if __name__ == '__main__':
    app.run(port=8000)
```

### まとめ

以上が、FlutterとFlaskを使用して1時間または2時間のパートタイム求人に応募できるアプリを作成する基本的な実装例です。

実際のアプリケーションでは、ユーザー認証、エラーハンドリング、UIの改善など、さらに多くの機能を追加する必要があります。