Flutter+FireStoreやってみた

##はじめに

今回はFlutter+FireStore開発やってみたのでプロジェクト構成について記事を書かせて頂きます。

※筆者は普段iOS、Androidのネイティブ開発がメインで初のFlutter開発+初のFireStoreとなるのでもっといい構成があるかもしれません。


##プロジェクト構成

基本の構成は以下としてみました。

・エンティティ

・データストア

・Api

・リポジトリ


##エンティティとデータストア

こちらは実際にFireStoreとやりとりする核の部分です。

こちらに関しては下記記事が非常に参考になり使いやすかったので参考に作成させて頂きました。

[[Flutter] DartのclassとFirestoreデータを相互変換、そしてCRUDへ](https://qiita.com/sekitaka_1214/items/129f41c2fbb1dc05b5c3)


##エンティティのJSON化

今回開発したプロジェクトではエンティティをjson形式に変換してローカル保存する必要があったので作成したエンティティに[json_annotation](https://pub.dev/packages/json_annotation)を使用してJsonSerializableを可能にしました。

ここは特に記載するつもりはありませんでしたが、
FireStoreのDocumentReference型を含んでいるエンティティの場合は別途コンバーターを用意して変換を行う必要があるようで少しハマったので軽くコードを記載します。


```Dart
@JsonSerializable(explicitToJson: true)
@_DocumentReferenceConverter()
class User {
	User(
      this.uid,
      this.deviceRef,
      ~~
      );
      
   String uid;
   DocumentReference deviceRef;
   ~~
}

class _DocumentReferenceConverter
    implements JsonConverter<DocumentReference, String> {
  const _DocumentReferenceConverter();

  @override
  DocumentReference fromJson(String json) => FirebaseFirestore.instance
      .doc(DeviceDatastore.getDocumentPath(json));

  @override
  String toJson(DocumentReference object) => object.id;
}
```

##API

今回のプロジェクトでは各画面で似たような通信を行うパターンがあったので、データストアを使用してFireStoreからのデータ取得を行う処理を機能目的毎にAPIとして用意しました（サーバAPIではありません）

これにより各Widgetはデータストアを直接使わずに済むためFireStoreを意識する必要がなくなり、サーバAPIと似たような実装になります。

今後サーバAPIを追加で用意する場合などに対処しやすくなるかと考えました。


```Dart
class GetUser {
	Future<User> process() async {
		return await UserDatastore.getUser(FirebaseAuth.instance.currentUser.uid);
	}
}
```

呼び出す際は

```Dart
GetUser().process;
```
といった感じです。


##リポジトリ

APIを使用してFireStoreにデータの取得、更新を行ったり、ローカル保存されているデータの取得、更新を行うためのクラスです。各種データの取得、更新はすべてこのリポジトリを介して行います。


リポジトリクラスのメインはAppRepositoryクラスで、AppRepositoryクラスではローカルから取得するか、FireStoreから取得するかを判断し、AppRepositoryLocal、AppRepositoryFirestore
を使用してデータの取得更新を行います。

下記はローカルにユーザ情報があるかをチェックし、なければFireStoreに取得にいくサンプルです。

```Dart
class AppRepository {
	static final AppRepository _instance = AppRepository._internal();

	factory AppRepository() => _instance;

	AppRepository._internal();

	Future<Persons> getUser() async {
		User user = await AppRepositoryLocal().getUser();
		return user ??= await AppRepositoryFirestore().getUser();
	}
}
```
```Dart
class AppRepositoryLocal {
  static final AppRepositoryLocal _instance =
      AppRepositoryLocal._internal();

  factory AppRepositoryLocal() {
    return _instance;
  }

  AppRepositoryLocal._internal();

  User user;

  Future<User> getUser() async {
    if (user == null) {
      final String localUserJson =
          await Preferences.getString(Constants.USER_ENTITY);
      if (localUserJson != null && localUserJson.isNotEmpty) {
        user = User.fromJson(
            jsonDecode(localUserJson) as Map<String, dynamic>);
      }
    }
    return user;
  }
```

```Dart
class AppRepositoryFirestore {
  static final AppRepositoryFirestore _instance =
      AppRepositoryFirestore._internal();

  factory AppRepositoryFirestore() => _instance;

  AppRepositoryFirestore._internal();

  Future<User> getUser() async {
    return GetUser().process();
  }

```

ここまで作成したらBlocからAppRepositoryを使用してデータの取得更新を行い、[Provider](https://pub.dev/packages/provider)で結果を画面に反映する形になります。

```Dart
class MyPageBloc extends ChangeNotifier {
	User user;

	void notify() {
		notifyListeners();
	}

	void getUser() async {
		final userRes = await AppRepository().getStore(uid);
		user = userRes;
		notify();
	}
}
```

以上です。