GraphQLを使ってアプリを作っている話 / The story of building an app using GraphQL

GraphQLを使ってアプリを作っている話 / The story of building an app using GraphQL

# 【Online】Flutter Meetupで発表した資料です。
https://flutter-jp.connpass.com/event/159013/

# GraphQLの公式ページ
https://graphql.org/

# Flutter chat application on GitHub
https://github.com/kwmt/flutter-inconne

# json_serializable
https://pub.dev/packages/json_serializable

# json_annotation
https://pub.dev/packages/json_annotation

# graphql_fragment_generator
https://pub.dev/packages/graphql_fragment_generator

Ce6acc3536b0e0340b5f0569d3394c9c?s=128

Yasutaka Kawamoto

July 14, 2020
Tweet

Transcript

  1. GraphQLΛ࢖ͬͯ ΞϓϦΛ࡞͍ͬͯΔ࿩ [ONLINE] Flutter Meetup 2020/07/14

  2. • Տຊ ହ޹ • AndroidɺiOSΤϯδχΞ
 @גࣜձࣾtech vein ࣗݾ঺հ • Flutter৮Γ࢝Ίɿ2018೥6݄ࠒ͔Β

    • ݸਓͰνϟοτΞϓϦΛ2018೥຤ʹϦϦʔε
 https://github.com/kwmt/flutter-inconne • ιϑτ΢ΣΞσβΠϯ2019೥4݄߸ʹ
 Flutterೖ໳ͷهࣄΛدߘ • 2019೥3݄ɿFlutter Meetup TokyoͰొஃ FlutterͱͷؔΘΓ
  3. • FlutterͰGraphQLΛ࠾༻ͨ͠ͱ͖ͷ࣮૷ͷงғؾ ΰʔϧ

  4. 1. GraphQLͱ͸ʁ 2. ͲΜͳΞϓϦΛ࡞ͬͯΔ͔ʁ 3. APIϦΫΤετΛͲͷΑ͏ʹ࣮૷͍ͯ͠Δ͔ʁ 4. ޻෉͍ͯ͠Δ͜ͱ 5. ͓ΘΓʹ

    ΞδΣϯμ
  5. • GraphQL͸࠷ߴͩʂ • αʔόʔଆͷ࣮૷ ࿩͞ͳ͍͜ͱ

  6. 1. GraphQLͱ͸ʁ

  7. • Web APIͷن֨ͷ1ͭ • RESTful Web APIʢRESTʣͱൺֱ͞ΕΔ͜ͱ͕ଟ͍ • ΫΤϦݴޠͱεΩʔϚݴޠ͔Βߏ੒ •

    ϨεϙϯεΛ
 ΫϥΠΞϯτ͕ܾΊΔ GraphQLͱ͸ʁ https://graphql.org/
  8. • Web APIͷن֨ͷ1ͭ • RESTful Web APIʢRESTʣͱൺֱ͞ΕΔ͜ͱ͕ଟ͍ • ΫΤϦݴޠͱεΩʔϚݴޠ͔Βߏ੒ •

    ϨεϙϯεΛ
 ΫϥΠΞϯτ͕ܾΊΔ GraphQLͱ͸ʁ https://graphql.org/
  9. ϑΟʔϧυ { hero { name } } ϑΟʔϧυ

  10. ϑΟʔϧυ(ΦϒδΣΫτ) ΦϒδΣΫτ { hero { name friend { name }

    } }
  11. ϑϥάϝϯτ { hero { name friend { name } }

    }
  12. ϑϥάϝϯτ fragment ʙ on Type fragment characterFragment on Character {

    name } { hero { name friend { name } } }
  13. ϑϥάϝϯτ { hero { name friend { ...characterFragment } }

    } fragment characterFragment on Character { name }
  14. ϑϥάϝϯτ { hero { name friend { ...characterFragment } }

    } fragment characterFragment on Character { name }
  15. Opectaion Type ͱ Operation Name query HeroName { hero {

    name } } Operation Type Operation Name
  16. ͦͷଞ https://graphql.org/learn/queries/

  17. 2. ͲΜͳΞϓϦΛ࡞ͬͯΔ͔ʁ

  18. nomemo nomemo͸͓ञʹରͯ͠
 νΣοΫΠϯ͢ΔSNSΞϓϦ

  19. nomemo ༑ୡͱ͓ञΛγΣΞͰ͖Δ ༑ୡͱסഋͰ͖Δ ҿΜ͓ͩञΛه࿥Ͱ͖Δ

  20. nomemoͰGraphQLΛͲͷΑ͏ʹ࢖͍ͬͯΔ͔

  21. nomemoͰGraphQLΛͲͷΑ͏ʹ࢖͍ͬͯΔ͔ ը૾URL ίϝϯτ Ϣʔβʔ໊

  22. nomemoͰGraphQLΛͲͷΑ͏ʹ࢖͍ͬͯΔ͔ ը૾URL ίϝϯτ Ϣʔβʔ໊ user.name image_url comment

  23. nomemoͰGraphQLΛͲͷΑ͏ʹ࢖͍ͬͯΔ͔ ը૾URL ίϝϯτ checkIn Ϣʔβʔ໊ user.name image_url comment

  24. nomemoͰGraphQLΛͲͷΑ͏ʹ࢖͍ͬͯΔ͔ ը૾URL ίϝϯτ checkIn timeline Ϣʔβʔ໊ user.name image_url comment

  25. λΠϜϥΠϯͷΫΤϦ { timeline { user { name } image_url comment

    } } checkIn user.name image_url comment timeline
  26. λΠϜϥΠϯͷϨεϙϯε [ { "user": { "name": “kawamoto" }, "image_url": <ը૾URL>,

    "comment": “͓͍͍͠ʂʂ" }, ]
  27. 3. APIϦΫΤετΛͲͷΑ͏ʹ࣮૷͍ͯ͠Δ͔ʁ

  28. graphqlϓϥάΠϯΛ࢖༻

  29. graphqlϓϥάΠϯΛ࢖༻

  30. GraphQLClientੜ੒ GraphQLClient client() { final HttpLink _httpLink = HttpLink( uri:

    'https://api.github.com/graphql', ); final AuthLink _authLink = AuthLink( getToken: () async => 'Bearer $PERSONAL_ACCESS_TOKEN', ); final Link _link = _authLink.concat(_httpLink); return GraphQLClient( cache: InMemoryCache(), link: _link, ); }
  31. GraphQLClientΛ࢖༻͢ΔSessionΫϥεΛ࡞੒ class Session { Session(this._client); final GraphQLClient _client; }

  32. GraphQLClientͰAPIϦΫΤετ class Session { Session(this._client); final GraphQLClient _client; Future<T> request<T>(Request<T>

    request) async { QueryResult result = await _client.query( QueryOptions( documentNode: gql(request.document), variables: request.variables ?? {}, ) ); return request.parseResponse(result.data); } }
  33. GraphQLClientͰAPIϦΫΤετ class Session { Session(this._client); final GraphQLClient _client; Future<T> request<T>(Request<T>

    request) async { QueryResult result = await _client.query( QueryOptions( documentNode: gql(request.document), variables: request.variables ?? {}, ) ); return request.parseResponse(result.data); } }
  34. GraphQLClientͰAPIϦΫΤετ class Session { Session(this._client); final GraphQLClient _client; Future<T> request<T>(Request<T>

    request) async { QueryResult result = await _client.query( QueryOptions( documentNode: gql(request.document), variables: request.variables ?? {}, ) ); return request.parseResponse(result.data); } }
  35. GraphQLClientͰAPIϦΫΤετ class Session { Session(this._client); final GraphQLClient _client; Future<T> request<T>(Request<T>

    request) async { QueryResult result = await _client.query( QueryOptions( documentNode: gql(request.document), variables: request.variables ?? {}, ) ); return request.parseResponse(result.data); } }
  36. GraphQLClientͰAPIϦΫΤετ class Session { Session(this._client); final GraphQLClient _client; Future<T> request<T>(Request<T>

    request) async { QueryResult result = await _client.query( QueryOptions( documentNode: gql(request.document), variables: request.variables ?? {}, ) ); return request.parseResponse(result.data); } }
  37. RequestΫϥεʹ͍ͭͯ abstract class Request<T> { const Request(); String get document;

    T parseResponse(dynamic data); }
  38. λΠϜϥΠϯAPIΛ࣮૷͢Δ class TimelineRequest extends Request<List<CheckInEntity>> { String get document =>

    “”” query GetTimeline { timeline { user { name } image_url comment } } “””; List<CheckInEntity> parseResponse(dynamic data) {} }
  39. λΠϜϥΠϯAPIΛ࣮૷͢Δ class TimelineRequest extends Request<List<CheckInEntity>>{ String get document => /*লུ*/

    List<CheckInEntity> parseResponse(dynamic data) { } }
  40. λΠϜϥΠϯAPIΛ࣮૷͢Δ part 'CheckInEntity.g.dart'; @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user') final

    UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment; factory CheckInEntity.fromJson(Map<String, dynamic> json) => _$CheckInEntity FromJson(json); }
  41. λΠϜϥΠϯAPIΛ࣮૷͢Δ part 'CheckInEntity.g.dart'; @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user') final

    UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment; factory CheckInEntity.fromJson(Map<String, dynamic> json) => _$CheckInEntityFromJson(json); }
  42. λΠϜϥΠϯAPIΛ࣮૷͢Δ class TimelineRequest extends Request<List<CheckInEntity>>{ String get document => /*লུ*/

    List<CheckInEntity> parseResponse(dynamic data) { List<dynamic> timeline = data[‘timeline’]; return timeline.map((tl) => CheckInEntity.fromJson(tl)).toList(); } }
  43. • GraphQLClientΛฦ͢ client()ؔ਺ • GraphQLClientΛ࢖͏SessionΫϥε • ΫΤϦͱϨεϙϯεΛύʔε͢ΔTimelineRequestΫϥε ࢖͍ํ

  44. • GraphQLClientΛฦ͢ client()ؔ਺ • GraphQLClientΛ࢖͏SessionΫϥε • ΫΤϦͱϨεϙϯεΛύʔε͢ΔTimelineRequestΫϥε ࢖͍ํ _session =

    Session(client());
  45. • GraphQLClientΛฦ͢ client()ؔ਺ • GraphQLClientΛ࢖͏SessionΫϥε • ΫΤϦͱϨεϙϯεΛύʔε͢ΔTimelineRequestΫϥε ࢖͍ํ _session =

    Session(client()); List<CheckInEntity> list = await _session.request(TimelineRequest());
  46. 4. ޻෉͍ͯ͠Δ͜ͱ

  47. ΫΤϦΛϑϥάϝϯτԽ͢Δ class TimelineRequest extends Request<List<CheckInEntity>> { String get document =>

    “”” query GetTimeline { timeline { user { name } image_url comment } } “””; List<CheckInEntity> parseResponse(dynamic data) {} } ࠶ܝ
  48. ΫΤϦΛϑϥάϝϯτԽ͢Δ class TimelineRequest extends Request<List<CheckInEntity>> { String get document =>

    “”” query GetTimeline { timeline { user { name } image_url comment } } “””; List<CheckInEntity> parseResponse(dynamic data) {} } part 'CheckInEntity.g.dart'; @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user') final UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment; }
  49. ΫΤϦΛϑϥάϝϯτԽ͢Δ class TimelineRequest extends Request<List<CheckInEntity>> { String get document =>

    “”” query GetTimeline { timeline { user { name } image_url comment } } “””; List<CheckInEntity> parseResponse(dynamic data) {} } part 'CheckInEntity.g.dart'; @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user') final UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment; }
  50. ΫΤϦΛϑϥάϝϯτԽ͢Δ { hero { name friend { ...characterFragment } }

    } fragment characterFragment on Character { name }
  51. ΫΤϦΛϑϥάϝϯτԽ͢Δ { hero { name friend { ...characterFragment } }

    } fragment characterFragment on Character { name }
  52. ΫΤϦΛϑϥάϝϯτԽ͢Δ { hero { name friend { ...characterFragment } }

    } fragment characterFragment on Character { name }
  53. ΫΤϦΛϑϥάϝϯτԽ͢Δ class CheckInEntity { /**লུ*/ const String checkInEntityFragmentName = “checkInField";

    const String checkInEntityFragment =''' fragment $checkInEntityFragmentName on checkIn { user { name } image_url comment } } '''; }
  54. ΫΤϦΛϑϥάϝϯτԽ͢Δ class CheckInEntity { /**লུ*/ const String checkInEntityFragmentName = “checkInField";

    const String checkInEntityFragment =''' fragment $checkInEntityFragmentName on checkIn { user { name } image_url comment } } '''; }
  55. ΫΤϦΛϑϥάϝϯτԽ͢Δ class TimelineRequest extends Request<List<CheckInEntity>>{ String get document => “””

    query { timeline { user { name } image_url comment } } “””; List<CheckInEntity> parseResponse(dynamic data) {} }
  56. ΫΤϦΛϑϥάϝϯτԽ͢Δ class TimelineRequest extends Request<List<CheckInEntity>>{ String get document => “””

    query { timeline { ...$checkInEntityFragmentName } } $checkInEntityFragment “””; List<CheckInEntity> parseResponse(dynamic data) {} }
  57. ϑΟʔϧυͱΫΤϦΛ ͍ۙ৔ॴʹஔ͘͜ͱ͕Ͱ͖͕ͨɾɾɾ part 'CheckInEntity.g.dart'; @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user')

    final UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment;
 const String checkInEntityFragmentName = “checkInField"; const String checkInEntityFragment =''' fragment $checkInEntityFragmentName on checkIn { user { name } image_url comment } } '''; }
  58. ϑΟʔϧυ௥Ճ͢Δͷ͸໘౗͍͘͞ part 'CheckInEntity.g.dart'; @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user') final

    UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment;
 @JsonKey(name: 'drink_name') final String drinkName;
 const String checkInEntityFragmentName = “checkInField"; const String checkInEntityFragment =''' fragment $checkInEntityFragmentName on checkIn { user { name } image_url comment drink_name } } '''; } ಉ͡
  59. ΫϥεϑΟʔϧυ͔ΒFragmentΛࣗಈੜ੒͢ΔϓϥάΠϯΛ࡞Γ·͠ ͨɻ part 'CheckInEntity.g.dart'; @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user')

    final UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment;
 @JsonKey(name: 'drink_name') final String drinkName;
 const String checkInEntityFragmentName = “checkInField"; const String checkInEntityFragment =''' fragment $checkInEntityFragmentName on checkIn { user { name } image_url comment drink_name } } '''; }
  60. ΫϥεϑΟʔϧυ͔ΒFragmentΛࣗಈੜ੒͢ΔϓϥάΠϯΛ࡞Γ·͠ ͨɻ part 'CheckInEntity.g.dart'; @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user')

    final UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment;
 @JsonKey(name: 'drink_name') final String drinkName;
 const String checkInEntityFragmentName = “checkInField"; const String checkInEntityFragment =''' fragment $checkInEntityFragmentName on checkIn { user { name } image_url comment drink_name } } '''; }
  61. ΫϥεϑΟʔϧυ͔ΒFragmentΛࣗಈੜ੒͢ΔϓϥάΠϯΛ࡞Γ·͠ ͨɻ part 'CheckInEntity.g.dart'; @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user')

    final UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment;
 @JsonKey(name: 'drink_name') final String drinkName;
 const String checkInEntityFragmentName = “checkInField"; const String checkInEntityFragment =''' fragment $checkInEntityFragmentName on checkIn { user { name } image_url comment drink_name } } '''; }
  62. None
  63. graphql_fragment_generatorͷ࢖͍ํ pubspec.yaml dependencies: json_annotation: 3.0.1 graphql_fragment_annotation: ^0.0.1 dev_dependencies: build_runner: ^1.2.8

    graphql_fragment_generator: ^0.0.1
  64. graphql_fragment_generatorͷ࢖͍ํ part 'CheckInEntity.g.dart'; @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user') final

    UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment;
 @JsonKey(name: 'drink_name') final String drinkName; /**লུ*/ }
  65. graphql_fragment_generatorͷ࢖͍ํ part ‘CheckInEntity.g.dart'; part ‘CheckInEntity.graphql.g.dart’; @GraphQLFragment(on: ‘checkIn’) @JsonSerializable() class CheckInEntity

    { @JsonKey(name: 'user') final UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment;
 @JsonKey(name: 'drink_name') final String drinkName; /**লུ*/ }
  66. graphql_fragment_generatorͷ࢖͍ํ part ‘CheckInEntity.g.dart'; part ‘CheckInEntity.graphql.g.dart’; @GraphQLFragment(on: ‘checkIn’) @JsonSerializable() class CheckInEntity

    { @JsonKey(name: 'user') final UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment;
 @JsonKey(name: 'drink_name') final String drinkName; /**লུ*/ } FragmentͷonͷTypeΛࢦఆ
  67. graphql_fragment_generatorͷ࢖͍ํ part ‘CheckInEntity.g.dart'; part ‘CheckInEntity.graphql.g.dart’; @GraphQLFragment(on: ‘checkIn’) @JsonSerializable() class CheckInEntity

    { @JsonKey(name: 'user') final UserEntity user; @JsonKey(name: 'image_url') final String imageUrl; @JsonKey(name: 'comment') final String comment;
 @JsonKey(name: 'drink_name') final String drinkName; /**লུ*/ }
  68. graphql_fragment_generatorͷ࢖͍ํ $ flutter pub get $ flutter pub pub run

    build_runner build
  69. graphql_fragment_generatorͷ࢖͍ํ const String checkInEntityFragmentName = “checkInField"; const String checkInEntityFragment =

    ''' fragment $checkInEntityFragmentName on checkIn { comment image_url user { ...$userEntityFragmentName } drink_name } $userEntityFragment ''';
  70. graphql_fragment_generatorͷ࢖͍ํ const String checkInEntityFragmentName = "checkInField"; const String checkInEntityFragment =

    ''' fragment $checkInEntityFragmentName on checkIn { user { ...$userEntityFragmentName } image_url comment drink_name } $userEntityFragment '''; class CheckInEntity { /**লུ*/ const String checkInEntityFragmentName = “checkInField"; const String checkInEntityFragment =''' fragment $checkInEntityFragmentName on checkIn { user { name } image_url comment drink_name } } '''; } ࣗಈੜ੒ खಈͰఆٛ
  71. graphql_fragment_generatorͷ࢖͍ํ const String checkInEntityFragmentName = "checkInField"; const String checkInEntityFragment =

    ''' fragment $checkInEntityFragmentName on checkIn { user { ...$userEntityFragmentName } image_url comment drink_name } $userEntityFragment '''; class CheckInEntity { /**লུ*/ const String checkInEntityFragmentName = “checkInField"; const String checkInEntityFragment =''' fragment $checkInEntityFragmentName on checkIn { user { name } image_url comment drink_name } } '''; } ࣗಈੜ੒ खಈͰఆٛ
  72. graphql_fragment_generatorͷ࢖͍ํ @GraphQLFragment(on: 'user') @JsonSerializable() class UserEntity { @JsonKey(name: 'name') final

    String name; /**লུ*/ } @GraphQLFragment(on: ‘checkIn’) @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user') final UserEntity user; /**লུ*/ }
  73. graphql_fragment_generatorͷ࢖͍ํ @GraphQLFragment(on: 'user') @JsonSerializable() class UserEntity { @JsonKey(name: 'name') final

    String name; /**লུ*/ } @GraphQLFragment(on: ‘checkIn’) @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user') final UserEntity user; /**লུ*/ } ࢀর
  74. graphql_fragment_generatorͷ࢖͍ํ @GraphQLFragment(on: 'user') @JsonSerializable() class UserEntity { @JsonKey(name: 'name') final

    String name; /**লུ*/ } const String userEntityFragmentName = "userField"; const String userEntityFragment = ''' fragment $userEntityFragmentName on user { name } '''; @GraphQLFragment(on: ‘checkIn’) @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user') final UserEntity user; /**লུ*/ } const String checkInEntityFragmentName = "checkInField"; const String checkInEntityFragment = ''' fragment $checkInEntityFragmentName on checkIn { user { ...$userEntityFragmentName } image_url comment drink_name } $userEntityFragment '''; ࢀর ࣗಈੜ੒ ࣗಈੜ੒
  75. graphql_fragment_generatorͷ࢖͍ํ @GraphQLFragment(on: 'user') @JsonSerializable() class UserEntity { @JsonKey(name: 'name') final

    String name; /**লུ*/ } const String userEntityFragmentName = "userField"; const String userEntityFragment = ''' fragment $userEntityFragmentName on user { name } '''; @GraphQLFragment(on: ‘checkIn’) @JsonSerializable() class CheckInEntity { @JsonKey(name: 'user') final UserEntity user; /**লུ*/ } const String checkInEntityFragmentName = "checkInField"; const String checkInEntityFragment = ''' fragment $checkInEntityFragmentName on checkIn { user { ...$userEntityFragmentName } image_url comment drink_name } $userEntityFragment '''; ࢀর ࣗಈੜ੒ ࢀর ࣗಈੜ੒
  76. • Pros • ΫϥεϑΟʔϧυʹఆ͓͚ٛͯ͠͹… • GraphQLͷΫΤϦ͕աෆ଍ͳ͘ϦΫΤετՄೳ • λΠϓϛεͳͲͷϦεΫܰݮ graphql_fragment_generatorͷProsɾCons •

    Cons • json_annotaionϓϥάΠϯʹґଘ͍ͯ͠Δ
  77. • ʮศརͩͬͨʂʯ • ʮཉ͍͠৘ใ͕૿͑ͨͱ͖ʹɺϑΟʔϧυʹ΋௥Ճͯ͠ɺ ΫΤϦʹ΋௥Ճ͠ͳ͔͋Μ͔Β໘౗͍͘͞ͳ͊ͱࢥͬͯͨ Βɺbuild͚ͨͩ͠Ͱಈ͍ͯ͘ΕͨͷͰศརͩͬͨʯ graphql_fragment_generatorͷࣾ಺ධ൑

  78. ☺ େઈࢍʂʂ

  79. 5. ͓ΘΓʹ

  80. • GraphQLʹ͍ͭͯ؆୯ʹ঺հ • GraphQLΛ࢖͏ͷʹgraphqlϓϥάΠϯ͕͋Γɺ
 ͦΕΛNomemoͰͲͷΑ͏ʹ࢖͍ͬͯΔ͔Λ঺հ • graphql_fragment_generatorΛ঺հ ͓ΘΓʹ

  81. ☺ ྑ͖Flutter x GraphQLϥΠϑΛʂ

  82. Thank you! @yasi_kawamoto @kwmt