LaravelとFlutterを連携させる時のモデルのあり方を考える

最近LaravelとFlutterを使ったアプリ開発をしているのですが、モデルを書く時にどうすればいいか困ったので備忘録もかねて投稿しました。

Flutterはまだ2ヶ月なので慣れてない部分もありますがご了承ください！

# モデル（DB)

今回は単純な下記モデルで考えていこうと思います。

![sample.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724713/814daf1c-3e6e-8fa1-53c4-dbb93e8aa3fd.png)

簡単に説明するとusersモデルとprofilesモデルが１：１で、usersとpostsが１：他の関係になっているモデルになっています。
簡単なブログのようなものをイメージしてもらえると良いかなと思います。


# Laravel側の実装
まずLaravel側の実装です。
と言ってもとても単純なモデルなのでチュートリアルや公式ドキュメント通り実装していけば問題ないかなと思います。


```php:App\Models\User.php
<?php

namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    protected $guard = 'user';

    protected $fillable = [
        'id',
        'name',
        'verified_at',
    ];

    public function posts()
    {
        return $this->hasMany('App\Models\Post', 'user_id', 'id');
    }
    public function profile()
    {
        return $this->hasOne('App\Models\Profile', 'user_id', 'id');
    }
}

```

```php:App\Models\Profile.php
<?php

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Profile extends Model
{
    protected $fillable = [
        'id',
        'user_id',
        'email',
        'address',
        'tel',
    ];

    public function user()
    {
        return $this->belongsTo('App\Models\User', 'user_id', 'id');
    }
}

```

```php:App\Models\Post.php
<?php

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = [
        'id',
        'user_id',
        'title',
        'content',
    ];

    public function user()
    {
        return $this->belongsTo('App\Models\User', 'user_id');
    }
}

```


# Flutter側の実装
次にFlutter側のモデルについてです。　
変に説明してもわかりづらいので早速完成形を載せたいと思います。

```dart:lib/models/user.dart
import 'post.dart';
import 'profile.dart';

class User {
  final int id;
  final String name;
  final DateTime verified_at;
  final List<Post> posts;
  final Profile profile;

  User({
    required this.id,
    required this.name,
    required this.verified_at,
    required this.posts,
    required this.profile,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    List<Post> newPosts = [];
    if (json['posts'] != null) {
      for (var p in json['posts']) {
        newPosts.add(Post.fromJson(p));
      }
    }

    return User(
      id: json['id'],
      name: json['name'],
      // toLocal()しないとDateTime.parse()でUTCに変換されてしまうので注意
      verified_at: DateTime.parse(json['verified_at']).toLocal(),
      posts: newPosts,
      profile: Profile.fromJson(json['profile']),
    );
  }
}

```

```dart:lib/models/profile.dart
import 'user.dart';

class Profile {
  final int id;
  final int user_id;
  final String email;
  final String address;
  final String tel;
  final User user;

  Profile({
    required this.id,
    required this.user_id,
    required this.email,
    required this.address,
    required this.tel,
    required this.user,
  });

  factory Profile.fromJson(Map<String, dynamic> json) {
    return Profile(
      id: json['id'],
      user_id: json['user_id'],
      email: json['email'],
      address: json['address'],
      tel: json['tel'],
      user: User.fromJson(json['user']),
    );
  }
}
```

```dart:lib/models/post.dart
import 'user.dart';

class Post {
  final int id;
  final int user_id;
  final String title;
  final String content;
  final User user;

  Post({
    required this.id,
    required this.user_id,
    required this.title,
    required this.content,
    required this.user,
  });

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'],
      user_id: json['user_id'],
      title: json['title'],
      content: json['content'],
      user: User.fromJson(json['user']),
    );
  }
}

```

flutterでのモデルは上記のような形になります。

モデルを生成する時はfromJsonメソッドでモデルを生成する形になります。

正直factoryについてはいまいちよく理解できていないのですが参考にした記事で上記のような形になっていたのでこうなりました。
（誰か詳しい人教えてくれると嬉しい。。）

つまづきポイントとしてはusers.dartのverified_atについての部分です。

どうやらDateTime.parse()はタイムゾーンがUTC以外の場合にはUTC形式に変換する処理が入っているようで、toLocal()で日本時間に変換してあげないと生成したDateTimeが9時間ずれてしまうようです。

https://qiita.com/umechanhika/items/fad9a75e44f2a492806a


# Laravelでモデルを取得してFlutterにJSON経由で受け渡す

最後にLaravelからFlutterへデータを受け渡す部分についてです。
Laravel側は特に悩む必要はなく、普通に　Eloquentで取得したモデルをjsonで受け渡せばよいだけです。

ひとまず適当にAuth::user()でとってきたUserモデルを受け渡してみます。

```php:App/Http/Controllers/HogeController.php

public function getUser(Request $request)
{
    $user = Auth::user();
    // Auth::user()でリレーション先のモデルを取得する時はwithじゃなくてloadで取れます
    $user->load("profile");
    return response()->json([
        'user' => $user
    ]);
}
```

次にflutterでjsonを受け取ります

```dart:hogehoge.dart
http.Response res = await http.post(Uri.parse(url),
    body: jsonEncode(data), headers: _setHeaders());
jsonMap = json.decode(res.body);

User user = User.fromJson(jsonMap["user"]);
```

できた！

# コレクションをflutterで受け取る時

Laravel側でget()で撮ってきた場合は下記のような形になります。


```php:App/Http/Controllers/HogeController.php
public function getUser(Request $request)
{
    // userも同時に取得したい時はwithを使う
    $posts = Post::with("user")->get();
    return response()->json([
        'posts' => $posts
    ]);
}
```

```dart:hogehoge.dart
http.Response res = await http.post(Uri.parse(url),
    body: jsonEncode(data), headers: _setHeaders());
jsonMap = json.decode(res.body);

List<Post> posts = [];
for (var p in jsonMap["posts"]) {
    posts.add(Post.fromJson(p));
}
```

まあほとんど上と同じですね。for文回しただけです。

# 最後に
いかがだったでしょうか。何となくもっと綺麗に書ける気もするのでもしもっといいやり方を知っている人がいるなら教えてくれるとありがたいです。

また今回はモデルを定義するところに焦点を当てましたが、実は開発する時はそれよりもLaravelとFlutterを連携させるところの方が難しかったりもしました。
（さらっとAuth::user()したけどもちろん認証済でないとユーザー情報はとれず、認証済みかどうかの判定はリクエストヘッダーにユーザーのトークン情報入れる必要がある、、、とかそういうやつ）

LaravelとFlutterの連携は英語の情報ですが下記がとても親切で分かりやすかったです。Laravel Passportに関してはこの記事に出会うまでは存在すら知りませんでした。

https://medium.com/@godilite/laravel-6-api-authentication-with-laravel-passport-72dccc5c47f7

https://medium.com/swlh/authenticating-flutter-application-with-laravel-api-caea30abd57


以上になります。ありがとうございました！