Upgrade to Pro — share decks privately, control downloads, hide ads and more …

FlutterでGraphQLを扱う

Avatar for iganin iganin
November 29, 2021

 FlutterでGraphQLを扱う

Avatar for iganin

iganin

November 29, 2021
Tweet

More Decks by iganin

Other Decks in Technology

Transcript

  1. RESTful APIͷ໰୊ • Under Fetching • ಉҰը໘಺Ͱෳ਺ͷϦΫΤετ͕ൃੜ • ը໘ʹද͍ࣔͨ͠৘ใΑΓ1ϦΫΤετͰऔಘͰ͖Δ৘ใ͕গͳ͍ •

    Over Fetching • ϦΫΤετ಺ʹඞཁͷͳ͍৘ใؚ͕·Εͯ͠·͏ • API͔Βฦ٫͞ΕΔ৘ใͷྔ͕͋Β͔͡Ίܾ·͍ͬͯΔͨΊ
  2. ఆٛͰ͖Δܕ • εΧϥʔܕ • String, Int, Float, Boolean, ID •

    ಠࣗͷΧελϜεΧϥʔܕ΋ఆٛՄೳ • ΦϒδΣΫτܕ • ྻڍܕ(enum) • ഑ྻ
  3. Schema interface Person { name: String ! } type User

    implements Person { id: ID ! name: String ! } type Article { id: ID ! title: String ! } union Content = User |Articl e type Query { user(id: ID!): Use r } type Mutation { changeUserName(input: ChangeUserNameInput ) : ChangeUserNamePayload } Schemaͷαϯϓϧ
  4. Queryͷಛ௃ 1 type User { id: ID ! name: String

    ! address: String ! } type Query { user(id: ID!): Use r } query GetUserA($id: ID!) { user(id: $id) { i d nam e } } query GetUserB($id: ID!) { user(id: $id) { i d nam e addres s } } ఆٛ͞Ε͍ͯΔฦ٫஋͔Β औಘ͢ΔσʔλΛબ୒Ͱ͖ Δ
  5. Queryͷಛ௃ 2 type User {… } type Article {… }

    Query { user(id: ID!): User ! users(limit: Int, nextToken: String): [User!] ! articles(limit: Int, nextToken: String): [Article!] ! } query userPage ( $currentUserId: ID! , $userNextToken: String , $articleNextToken: Strin g ) { user(id: $currentUserId) {… } users(limit: 50, nextToken: $userNextToken) {… } articles(limit: 50, nextToken: $articleNextToken) {… } } ෳ਺ͷQueryΛ1ճͷquery Ͱ·ͱΊ࣮ͯߦ͢Δ͜ͱ͕ Ͱ͖Δ
  6. Mutationͷಛ௃ type User { … } type Mutation { changeUserName

    ( input: ChangeUserNameInpu t ): ChangeUserNamePayloa d } input ChangeUserNameInput { id: ID ! name: String ! } type ChangeUserNamePayload { id: ID ! name: String ! } mutation ChangeUserName($input: ChangeUserNameInput!) { changeUserName(input: $input) { nam e } } Ҿ਺͸inputܕͱͯ͠౉͢͜ ͱ͕ଟ͍
  7. Fragment type User { id: ID ! name: String !

    address: String ! } query SampleQueryA($userId: ID!) { user(id: $id) { i d nam e } articles { … } } query SampleQueryB($userId: ID!) { user(id: $id) { i d nam e } news { … } } ෳ਺ͷQuery, MutationͰಉ ༷ͷσʔλΛऔಘ͢Δ ɹɹɹɹɹ ຖճॻ͘ͷ͸खؒͱͳΔ
  8. Fragment type User { id: ID ! name: String !

    address: String ! } Fragment UserFragment on User { i d nam e } query SampleQueryA($userId: ID!) { user(id: $id) { …UserFragmen t } articles { … } } query SampleQueryB($userId: ID!) { user(id: $id) { …UserFragmen t } news { … } } FragmentͰ൓෮ͯ͠࢖͏ σʔλͷ·ͱ·ΓΛఆٛͰ ͖Δ
  9. GraphQL ϦΫΤετ curl \ -X POST \ -H "Content-Type: application/json"

    \ --data '{ "query": "{ country(code: \"JP\") { code name } } " }' \ https://countries.trevorblades.com/ { "data": { "country": { "code": "JP" , "name":"Japan " } } } REST API ΫϥΠΞϯτͷ POST௨৴Ͱ΋࣮ߦՄೳ Ҿ༻: https://countries.trevorblades.com/
  10. Ωϟογϡ Query SampleQueryA { users { Results { __typenam e

    i d nam e } } } Query SampleQueryB { user(id: "1") { __typenam e I d nam e } } ಉҰͷܕͱIDͷσʔλͰ͋ Ε͹ɺผqueryͷ݁ՌͰ΋ಉ ҰͷCacheͱͯ͠อଘ͞Ε Δ
  11. graphql_ fl utter(graphql) • (͓ͦΒ͘)࠷΋༗໊ͳFlutterͷGraphQLΫϥΠΞϯτ • ΠϯϝϞϦ/ӬଓԽ Ωϟογϡͷαϙʔτ • ൺֱతࣄྫ͕๛෋

    • ίʔυͷࣗಈੜ੒͸ະରԠ • ଞͷίʔυࣗಈੜ੒ϥΠϒϥϦͱ૊Έ߹ΘͤΔ͜ͱ͸Ͱ͖Δ
  12. • ϞϊϨϙͰ؅ཧ͞Ε͍ͯΔ • graphql - GraphQLΫϥΠΞϯτ • graphql_ fl utter

    - ΫϥΠΞϯτ + Query, Mutation Widget • graphql, graphql_ fl utterͲͪΒ΋ sound null saferyରԠࡁΈ • graphql_ fl utter͸ରԠࡁΈͷϥϕϧ͕ͳ͍͕ɺpub outdated Ͱ֬ ೝ͢ΔͱରԠࡁΈͱͳ͍ͬͯΔ graphql_ fl utter(graphql)
  13. graphql_ fl utter(graphql) • ΞΫςΟϒϝϯςφ͕ෆࡏͷঢ়گ • 📣 no active maintainer

    📣 • https://github.com/zino-app/graphql- fl utter/issues/894 • apollo-clientʹͯҾ͖ܧ͗ґཔ͞Ε͕ͨɺҾ͖ܧ͗͞Εͣ • Take over existing fl utter GraphQL client • https://github.com/apollographql/apollo-client/issues/8332
  14. artemis • ίʔυࣗಈੜ੒ػೳ͕ॆ࣮ • Releaseόʔδϣϯ͸null safetyະରԠ • ࠷৽ϦϦʔε 2021/2/18(11/19࣌఺) •

    Pre Release(Beta)͸null safetyରԠ • ࠷৽ϦϦʔε 2021/10/27(11/19࣌఺) • ։ൃ͸BetaϒϥϯνʹͯඇৗʹΞΫςΟϒ
  15. HSBQIRM@ fl VUUFS BSUFNJT GFSSZ ΫϥΠΞϯτػೳ ˓ ˚ ˓ ίʔυࣗಈੜ੒

    ͳ͍ ˓ ˓ /VMM4BGFUZ ˓ ˓ ˞ ˓ ϝϯςφϯε ˚ ˓ ˓ ※ artemisͷnull safetyαϙʔτ͸ Pre Release ͷ Beta൛Ҏ߱Ͱ͋Δ͜ͱʹ஫ҙ
  16. ॳظԽ final httpLink = HttpLink ( 'https://api.github.com/graphql' , ) ;

    final authLink = AuthLink ( getToken: () async => 'Bearer $YOUR_PERSONAL_ACCESS_TOKEN' , ) ; final link = authLink.concat(httpLink) ; final store = await HiveStore.open(path: 'path') ; final GraphQLClient client = GraphQLClient ( cache: GraphQLCache(store: store) , link: link , ); ॳظԽίʔυαϯϓϧ
  17. Operation • Operationͷܕ • Query: QueryOptions • Mutation: MutationOptions •

    Document - ࣮ߦ͢ΔQueryจ • Vars - OperationͷҾ਺ • Policy - Ωϟογϡ΍Τϥʔͷѻ͍
  18. Operation const readRepositories = r'' ' query ReadRepositories($nRepositories: Int!) {

    viewer { repositories(last: $nRepositories) { nodes { __typenam e i d nam e } } } } ''' ; final QueryOptions options = QueryOptions ( document: gql(readRepositories) , variables: <String, dynamic> { 'nRepositories': nRepositories , } , fetchPolicy: FetchPolicy.cacheFirst , ) ; final QueryResult result = await client.query(options); documentΛੜ੒ QueryOptionsੜ੒ Client͔ΒϦΫΤετ
  19. ίʔυࣗಈੜ੒ ͠ͳ͍৔߹ const readRepositories = r'' ' query ReadRepositories($nRepositories: Int!)

    { viewer { repositories(last: $nRepositories) { nodes { __typenam e i d nam e } } } } ''' ; final QueryOptions options = QueryOptions ( document: gql(readRepositories) , variables: <String, dynamic> { 'nRepositories': nRepositories , } , fetchPolicy: FetchPolicy.cacheFirst , ) ; final QueryResult result = await client.query(options) ; String෦෼Ͱิ׬͕ޮ͔ͳ͍ Ҿ਺͕ࣗಈิ׬͞Εͳ͍ ResponseΛύʔε͢ΔܕΛ ࣗ෼Ͱ࡞੒͢Δඞཁ͕͋Δ
  20. ࣗಈੜ੒͞Εͨ ίʔυͷར༻ྫ final getUserQuery = GGetUserReq((builder) => builde r ..vars.id

    = userI d ) ; final queryOptions = QueryOptions ( document: getUserQuery.operation.document , variables: getUserQuery.vars.toJson() , fetchPolicy: FetchPolicy.cacheFirst , ) ; final data = await gqlClient.query(queryOptions) ; final user = GGetUserData.fromJson(data);
  21. Fragment Colocation query TopScreenQuery($searchWord: String!) { items(searchWord: $searchWord) { …HeaderFragmen

    t items { …ItemFragmen t } …FooterFragmen t } } fragment HeaderFragment on Result { searchWor d } Fragment ItemFragment on Item { nam e imageUr l } Fragment FooterFragment on ItemsResult { coun t totalItemCount } Fragment Colocationͷྫ Header, List, Footer ͕͋ΔΑ͏ͳը໘
  22. ࢖༻͢ΔϥΠϒϥϦҊ • Ferry • ίʔυͷࣗಈੜ੒ • ϨεϙϯεσʔλΛࣗಈతʹࣗಈੜ੒ͨ͠Ϋϥε΁ม׵ • ferry_ fl

    utterʹΑΔUIͰͷGraphQLͷ؆қతͳར༻ • ฦ٫஋͕streamʹ౷Ұ͞Ε͍ͯΔͷͰɺReactiveͳ൓ө͕༰қ
  23. DI Provider clientProvider = Provider<Client>((_) { throw Exception() ; })

    ; void main() async { runApp(child: CircularProgressIndicator()) ; final client = await initClient() ; runApp ( ProviderScope ( overrides: [ clientProvider.overrideWithValue(client ) ] , child: App() , ) , ) ; } ӬଓԽΩϟογϡHiveStore ͷॳظԽ͸ඇಉظॲཧ ͜ͷ৔߹ProviderScopeͰ Override͢Δͷ͕͓͢͢Ί
  24. DI class ItemListScreen extends ConsumerWidget { @overrid e Widget build(BuildContext

    context, WidgetRef ref) { final client = ref.watch(clientProvider) ; return Scaffold ( appBar: AppBar ( title: Text(‘all items’) , ) , body: Container() , ) , ) ; } ࢖༻͢Δࡍ͸௨ৗͱಉ༷
  25. Query class SampleChangeNotifier extends ChangeNotifier { SampleChangeNotifier(this._read, this.id) { _load()

    ; } final Reader _read ; final String id ; late final client = _read(clientProvider) ; AsyncValue<GSampleData> _sampleData = AsyncValue.loading() ; AsyncValue<GSampleData> get sampleData => _sampleData ; StreamSubscription? subscription ; Future<void> _load() async { final request =GSampleDataReq ( (builder) => builde r ..vars.id = i d ) ; subscription = client.request(request).listen((event) { if (event.hasErrors) { final error = _parseError() ; _sampleData = AsyncValue.error(error) ; } else { _sampleData = AsyncValue.data(event.data!) ; } notifyListeners() ; }) ; } void reLoad() { _sampleData = AsyncValue.loading() ; final request = GSampleDataReq ( (builder) => builde r ..vars.id = i d ..fetchPolicy = FetchPolicy.CacheFirs t ) ; client.requestController.add(request) ; } ~~~~~~ ~ } ChangeNoti fi erΛ࢖༻ AsnycValueʹΑͬͯσʔλ ࣗମʹ௨৴ঢ়ଶΛ࣋ͨͤΔ
  26. Query UI class ListScreen extends ConsumerWidget { @overrid e Widget

    build(BuildContext context, WidgetRef ref) { final data = ref.watch ( sampleChangeNotifierProvide r ).sampleData ; return Scaffold ( body: data.when ( data: (data) => DataView(data: data) , loading: () => Center ( child: CircularProgressIndicator( ) ) , error: (context, error) => ErrorView() , ) ) ; } }
  27. Mutation class SampleMutationChangeNotifier extends ChangeNotifier { SampleMutationChangeNotifier(this._read) ; final Reader

    _read ; late final client = _read(clientProvider) ; AsyncValue<void> _status = AsyncValue.data(null) ; AsyncValue<void> get status => _status ; final _requestId = "SampleMutation" ; Future<void> doSomething({required String id}) async { final request = GAddStarReq((builder) => builde r ..vars.input.id = i d ..requestId = _requestI d ) ; _status = AsyncValue.loading() ; notifyListeners() ; final result = await client.request(request).first ; if (result.hasErrors) { final error = _parseError() ; _status = AsyncValue.error(error) ; } else { _status = AsyncValue.data(null) ; } notifyListeners() ; } } Mutationͷૢ࡞΋Provider ଆʹهड़͢Δ͜ͱ͕Ͱ͖Δ มߋ͸ࣗಈతʹUIʹ൓ө͞ ΕΔ
  28. ϑΥϧμߏ੒ li b networ k clien t graphql_clien t mutatio

    n sample_mutatio n sample_mutation_provider.dar t graphq l sample_mutation.graphq l scree n sample_scree n sample_screen_query_provider.dar t sample_screen.dar t graphq l screen.graphq l vie w sample_vie w … componen t sample_componen t … network, mutation screen, view provider (ΞϓϦ಺ঢ়ଶ)