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

FlutterでGraphQLを扱う

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
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 (ΞϓϦ಺ঢ়ଶ)