.entry .entry-content ul > li > ul { display: none; } .entry-content td { text-align: left; } ã¯ããã« ããã«ã¡ã¯ãæ°èŠäºæ¥éšããã³ããšã³ããããã¯ã® æ± ç° ã§ããæ®æ®µã¯ZOZOãããã®ã¢ããªéçºãæ
åœããŠããŸãã2025幎6æã«ãããã³ã°ã¢ããªã ZOZOããã ãããªãªãŒã¹ããŸãããZOZOãããã«ã¯ã¡ãã»ãŒãžæ©èœãããããã®æ©èœãå®çŸããããã«GraphQLãçšããŠããŸããæ¬èšäºã§ã¯Flutterã¢ããªã§GraphQLãçšãããªã¢ã«ã¿ã€ã ã¡ãã»ãŒãžæ©èœã®éçºã®ç¥èŠãšå·¥å€«ããç¹ãã玹ä»ããŸãã ãªããZOZOãããã¢ããªå
šäœã®ã¢ãŒããã¯ãã£ãæè¡æ§æã«ã€ããŠã¯ãå¥èšäºã ZOZOãããã¢ããªã®ã¢ãŒããã¯ãã£ãšæè¡æ§æ ãã§è©³ãã玹ä»ããŠããŸãã ç®æ¬¡ ã¯ããã« ç®æ¬¡ GraphQLãšã¯ GraphQLã®äž»ãªç¹åŸŽ 1. åäžãšã³ããã€ã³ã 2. å¿
èŠãªããŒã¿ã®ã¿ååŸ 3. 匷åãªåã·ã¹ãã 4. ãªã¢ã«ã¿ã€ã éä¿¡ã®ãµããŒã GraphQLã®3ã€ã®æäœã¿ã€ã ZOZOãããã§ã®GraphQLã®å©çšèæ¯ FlutterÃGraphQLã®å®è£
Flutterãžã®å°å
¥ äž»èŠããã±ãŒãž GraphQL Clientã®èšå® åå®å
šãªã³ãŒãçæ GraphQLã§ã®ããŒã¿ååŸ GraphQLã§ã®éä¿¡åŠç Subscriptionã§ã®ãªã¢ã«ã¿ã€ã åæ éçºã§åŸãç¥èŠãšå·¥å€«ç¹ Optimistic UpdateïŒæ¥œèŠ³çæŽæ°ïŒã«ããUXæ¹å å®è£
ã®æµã ã¢ããªããã¯ã°ã©ãŠã³ãæã®Subscription管ç çºçããåé¡ è§£æ±ºç AWS AppSyncãšã®çµ±åã«ããã課é¡ãšè§£æ±ºç ãŸãšã GraphQLãšã¯ GraphQLã¯ãMeta瀟ãå
¬éããAPIã®ã¯ãšãªèšèªããã³ã©ã³ã¿ã€ã ã§ããREST APIã®èª²é¡ã解決ããããã«éçºãããçŸåšã§ã¯å€ãã®äŒæ¥ã§æ¡çšãããŠããŸãã GraphQLã®äž»ãªç¹åŸŽ 1. åäžãšã³ããã€ã³ã REST APIã§ã¯è€æ°ã®ãšã³ããã€ã³ãïŒ /users ã /messages ãªã©ïŒãååšããŸãããGraphQLã§ã¯ãã¹ãŠã®ãªã¯ãšã¹ããåäžã®ãšã³ããã€ã³ãïŒé垞㯠/graphql ïŒã«éä¿¡ãããŸãã 2. å¿
èŠãªããŒã¿ã®ã¿ååŸ ã¯ã©ã€ã¢ã³ããå¿
èŠãªãã£ãŒã«ããæç€ºçã«æå®ã§ããããããªãŒããŒãã§ããã³ã°ïŒäžèŠãªããŒã¿ã®ååŸïŒãã¢ã³ããŒãã§ããã³ã°ïŒè¿œå ãªã¯ãšã¹ããå¿
èŠïŒãé²ããŸãã # å¿
èŠãªãã£ãŒã«ãã ããæå® query { user(id: "123") { name avatar # ãã®2ã€ã®ãã£ãŒã«ãã®ã¿ååŸ } } 3. 匷åãªåã·ã¹ãã ã¹ããŒãã«ãã£ãŠå³å¯ãªåå®çŸ©ãè¡ãããéçºæã®åå®å
šæ§ãåäžããŸãããŸãã GraphQL Playground ãªã©ã®ããŒã«ã§èªåçã«ããã¥ã¡ã³ããçæãããŸãã 4. ãªã¢ã«ã¿ã€ã éä¿¡ã®ãµããŒã Subscriptionæ©èœã«ããWebSocket 1 çµç±ã§ãªã¢ã«ã¿ã€ã ããŒã¿é
ä¿¡ãå¯èœã§ãããããZOZOãããã®ã¡ãã»ãŒãžæ©èœã§éèŠãªåœ¹å²ãæãããŠããŸãã GraphQLã®3ã€ã®æäœã¿ã€ã GraphQLã«ã¯äž»ã«3ã€ã®æäœã¿ã€ãããããŸãã æäœã¿ã€ã çšé REST APIã§ã®çžåœ Query ããŒã¿ã®ååŸ GET Mutation ããŒã¿ã®äœæã»æŽæ°ã»åé€ POST/PUT/DELETE Subscription ãªã¢ã«ã¿ã€ã ããŒã¿ã®è³Œèª WebSocket/SSE ZOZOãããã§ã®GraphQLã®å©çšèæ¯ ZOZOãããã§ã¯ãŠãŒã¶ãŒå士ããªã¢ã«ã¿ã€ã ã§ã¡ãã»ãŒãžãããåãããæ©èœããããŸãããããã³ã°ã¢ããªã«ãããŠãã¡ãã»ãŒãžæ©èœã¯æãéèŠãªæ©èœã®1ã€ã§ããã以äžã®èŠä»¶ãæºããå¿
èŠããããŸããã ã¡ãã»ãŒãžã®éåä¿¡ããªã¢ã«ã¿ã€ã ã§åæ ããã ãªãã©ã€ã³æã®ã¡ãã»ãŒãžããªã³ã©ã€ã³åŸ©åž°æã«åä¿¡ã§ãã æ¢èªã®ç®¡çãã§ãã åŸæ¥ã®REST APIã§ããããå®çŸããããšãããšã以äžã®èª²é¡ããããŸããã ãªã¢ã«ã¿ã€ã æ§ã®å®è£
ãè€é : ããŒãªã³ã°ã§ã¯é
å»¶ãçºçããWebSocketã®å®è£
ã¯è€é è€æ°åã®APIã³ãŒã« : ã¡ãã»ãŒãžäžèЧããŠãŒã¶ãŒæ
å ±ãæ¢èªç¶æ
ãªã©ãå¥ã
ã«ååŸããå¿
èŠããã ãªãŒããŒãã§ããã³ã° : äžèŠãªããŒã¿ãå«ããŠååŸããŠããŸããéä¿¡éãå¢å åå®å
šæ§ã®ç¢ºä¿ãå°é£ : APIã¬ã¹ãã³ã¹ã®åå®çŸ©ãæåã§ç®¡çããå¿
èŠããã ãããã®èª²é¡ã解決ãããããGraphQLãæ¡çšããŸãããGraphQLã§ã¯ä»¥äžã®æ©èœã«ããå課é¡ã«å¯Ÿå¿ã§ããŸãã Subscription : WebSocketããŒã¹ã®ãªã¢ã«ã¿ã€ã éä¿¡ãæšæºæ©èœãšããŠæäŸãããªã¢ã«ã¿ã€ã ã§ã®ã¡ãã»ãŒãžã®åæ ãå®çŸ åäžãšã³ããã€ã³ã : 1åã®ãªã¯ãšã¹ãã§å¿
èŠãªããŒã¿ïŒã¡ãã»ãŒãžããŠãŒã¶ãŒæ
å ±ãæ¢èªç¶æ
ãªã©ïŒããŸãšããŠååŸå¯èœ æè»ãªã¯ãšãª : ã¯ã©ã€ã¢ã³ãåŽã§å¿
èŠãªãã£ãŒã«ãã®ã¿ãæå®ããŠååŸã§ããéä¿¡éãæé©å 匷åãªåã·ã¹ãã : ã¹ããŒãããåå®å
šãªã³ãŒããèªåçæããéçºæã®åãã§ãã¯ãå®çŸ ã¡ãã»ãŒãžäžèЧç»é¢ ã¡ãã»ãŒãžç»é¢ FlutterÃGraphQLã®å®è£
Flutterãžã®å°å
¥ Flutterã§GraphQLãå©çšããããã«ã以äžã®ããã±ãŒãžãå°å
¥ããŸããã graphql_flutter ã¯GraphQLã¯ãšãªã®å®è¡ãšãŠã£ãžã§ããã®æäŸã graphql_codegen ã¯GraphQLã¹ããŒãããåå®å
šãªDartã³ãŒãã®èªåçæãæ
åœããŸãã äž»èŠããã±ãŒãž dependencies : graphql_flutter : ^5.2.1 dev_dependencies : graphql_codegen : ^2.0.0 GraphQL Clientã®èšå® ãŸããã¢ããªå
šäœã§GraphQL Clientãå©çšã§ããããã«èšå®ããŸããWebSocketã«ããSubscriptionããµããŒããããããHTTPãšWebSocketã®äž¡æ¹ã®ãªã³ã¯ãèšå®ããŠããŸãããŸãã AuthLink ã䜿çšããŠBearerããŒã¯ã³ã«ããèªèšŒã管çãããã¹ãŠã®GraphQLãªã¯ãšã¹ãã«èªèšŒæ
å ±ãèªåçã«ä»äžããŠããŸããWebSocketã®å®è£
ã«é¢ããŠã¯åŸè¿°ã§è©³çްã«èšèŒããŠããŸãã static Future<GraphQLClient> _initializeGraphQLClient(Ref ref) async { final dio = ref.watch(dioProvider); final buildConfig = ref.watch(buildConfigProvider); final authLink = AuthLink( getToken: () async { final token = await _getAccessToken(ref); return 'Bearer $token' ; }, ); final dioLink = Link.from([DioLink(buildConfig.graphQlEndpoint, client: dio)]); final webSocketLink = _initializeWebSocketLink(ref); final link = Link.split( (request) => request.isSubscription, // Subscriptionã®å Žåã¯WebSocketLinkããã以å€ïŒQuery/MutationïŒã¯HTTP Linkãäœ¿çš await webSocketLink, authLink.concat(dioLink), ); final client = GraphQLClient( cache: GraphQLCache(store: InMemoryStore()), link: link, ); return client; } ã¢ããªã®ãšã³ããªãŒãã€ã³ãã§ProviderãšããŠèšå®ããŸãã void main() { runApp( GraphQLProvider( client: _initializeGraphQLClient(), child: MyApp(), ), ); } åå®å
šãªã³ãŒãçæ graphql_codegenãçšããŠãGraphQLã®ã¹ããŒãããèªåçã«DartåãçæããŸãã GraphQLã¹ããŒããã¡ã€ã«ã®é
眮 lib/ âââ graphql/ â âââ schema.graphql # ãµãŒããŒã®ã¹ããŒã â âââ queries/ â âââ messages.graphql # ã¯ãšãªå®çŸ© ã¯ãšãªã®å®çŸ© ïŒmessages.graphqlïŒ query ListMessages($channelId: String!, $limit: Int, $nextToken: String) { listMessages(channelId: $channelId, limit: $limit, nextToken: $nextToken) { items { ...MessageFields } nextToken } } mutation CreateMessage($channelId: String!, $kind: String!, $body: String!) { createMessage(channelId: $channelId, kind: $kind, body: $body) { channelId action message { ...MessageFields } } } subscription OnMessageModified($channelId: String!) { onMessageModified(channelId: $channelId) { action channelId message { ...MessageFields } } } ã³ãŒãçæã®èšå® ïŒbuild.yamlïŒ targets: $default: builders: graphql_codegen: options: schema: lib/graphql/schema.graphql queries_glob: lib/graphql/queries/**.graphql output_directory: lib/graphql/generated ã³ãŒãçæã®å®è¡ flutter pub run build_runner build --delete-conflicting-outputs ããã«ãããåå®å
šãªã¯ãšãªå®è¡çšã®ã¯ã©ã¹ãèªåçæãããŸããèªåçæã«ãã£ãŠã¿ã€ããã¹ã®ãªã¹ã¯ãæžããããã©ãŒããããå¹ãããããšãã§ããŸãããŸããSchemaãé©åã§ãªãã£ãå Žåã¯èªåçæã®éã«ãšã©ãŒãçºçãããããåé¡ãæ©æã«æ€ç¥ã§ããã¡ãªããããããŸãã GraphQLã§ã®ããŒã¿ååŸ ZOZOãããã§ã¯ãããã³ã°ãããçžæã衚瀺ãããã¡ãã»ãŒãžäžèЧç»é¢ãšããããé·ç§»ã§ããã¡ãã»ãŒãžç»é¢ã§Queryã䜿ã£ãŠããŒã¿ãååŸããŠããŸãã 以äžã®ã³ãŒãã¯ãã¡ãã»ãŒãžäžèЧãååŸããéã®å®è£
äŸã§ãã Query$ListMessages$Widget ã¯åè¿°ã®ã³ãŒãçæã«ããäœæãããåå®å
šãªãŠã£ãžã§ããã§ãGraphQLã®Queryãç°¡æœã«å®è¡ã§ããŸãã Query$ListMessages$Widget( options: Options$Query$ListMessages( fetchPolicy: FetchPolicy.networkOnly, variables: Variables$Query$ListMessages( channelId: channelId, ), ), builder: (result, {fetchMore, refetch}) { if (result.data == null && result.isLoading) { return const CommonLoadingView(); } if (result.hasException) { return CommonErrorView( onRetry: () async { await refetch?.call(); }, ); } MessageListWidget(); } ); ãã®ã³ãŒãã§ã¯ã Options$Query$ListMessages ã§ã¯ãšãªãèšå®ãã fetchPolicy ã« networkOnly ãæå®ããŠåžžã«ææ°ã®ããŒã¿ããµãŒããŒããååŸããŸãããŸãã Variables$Query$ListMessages ã§åå®å
šã«GraphQL倿°ïŒ channelId ïŒãæž¡ããŠããŸãã builder å
ã§ã¯ãã¯ãšãªã®å®è¡ç¶æ
ã«å¿ããŠ3çš®é¡ã®UIã衚瀺ããŠããŸãã result.isLoading ãtrueã®å Žåã¯ããŒãã£ã³ã°ç»é¢ã衚瀺ãã result.hasException ãtrueã®å Žåã¯ãšã©ãŒç»é¢ã衚瀺ããŸãããšã©ãŒç»é¢ã§ã¯ refetch ãåŒã³åºãããšã§ãªãã©ã€æ©èœãæäŸããŠããŸããããŒã¿ååŸãæåããå Žåã¯ãã¡ãã»ãŒãžãªã¹ããŠã£ãžã§ããã衚瀺ããŸãã graphql_flutterããã±ãŒãžãæäŸããWidgetããŒã¹ã®APIãå©çšããããšã§ãGraphQLã®ã¯ãšãªå®è¡ãšFlutterã®UIæŽæ°ãèªç¶ã«çµ±åãããŠããŸãããŸãã fetchMore ã refetch ãªã©ã®æ©èœãæšæºã§æäŸããããããããŒãžããŒã·ã§ã³ãããŒã¿ã®åååŸãç°¡åã«å®è£
ã§ããŸãã GraphQLã§ã®éä¿¡åŠç 次ã«Mutationã䜿ã£ãã¡ãã»ãŒãžéä¿¡åŠçã玹ä»ããŸããGraphQLã®Mutationã¯ãããŒã¿ã®äœæã»æŽæ°ã»åé€ãšãã£ãå¯äœçšã䌎ãæäœã«äœ¿çšããŸãã以äžã¯ã¡ãã»ãŒãžéä¿¡æã®å®è£
äŸã§ãã Mutation$CreateMessage$Widget( options: WidgetOptions$Mutation$CreateMessage( onCompleted: (_, _) async { // ã¡ãã»ãŒãžéä¿¡å®äºæã®åŠç }, onError: (error) async { if (error == null ) { return ; } // ãšã©ãŒæã®åŠç logger.error( 'Error creating message: $error' ); }, ), builder: (runMutation, result) { MessageBarWidget( onSubmit: (text) async { runMutation( Variables$Mutation$CreateMessage( channelId: channelId, body: text, ), ); } ); } ); ãã®ã³ãŒãã§ã¯ã Mutation$CreateMessage$Widget ã§ã¡ãã»ãŒãžéä¿¡ãå®è£
ããŠããŸãã onCompleted ã§éä¿¡æåæã onError ã§ãšã©ãŒæã®åŠçãå®çŸ©ããŸãã builder ããæäŸããã runMutation 颿°ãåŒã³åºãããšã§Mutationãå®è¡ããŸãã MessageBarWidget ã®ããã¹ãéä¿¡æã«ã Variables$Mutation$CreateMessage ã䜿ã£ãŠåå®å
šã«å¿
èŠãªãã©ã¡ãŒã¿ïŒ channelId ã body ïŒãæž¡ããŠããŸãããã®å®è£
ã«ããããŠãŒã¶ãŒãã¡ãã»ãŒãžãå
¥åããŠéä¿¡ãã¿ã³ãæŒããšãGraphQL Mutationãå®è¡ãããµãŒããŒã«ã¡ãã»ãŒãžãéä¿¡ãããŸãã Mutationã®å®è¡ã¯éåæã§è¡ãããéä¿¡äžã®ç¶æ
㯠result ãªããžã§ã¯ãããååŸã§ããŸããããã«ãããéä¿¡äžã®ããŒãã£ã³ã°è¡šç€ºããé信倱ææã®ãªãã©ã€æ©èœãªã©ãç°¡åã«å®è£
ã§ããŸãã Subscriptionã§ã®ãªã¢ã«ã¿ã€ã åæ ã¡ãã»ãŒãžäžèЧãžã®ãããã³ã°ã®åæ ããçžæããã¡ãã»ãŒãžã®åä¿¡ããªã¢ã«ã¿ã€ã ã§åæ ããéã«ã¯SubscriptionãçšããŸããGraphQL Subscriptionã¯ãWebSocketã䜿çšããŠãµãŒããŒããã¯ã©ã€ã¢ã³ããžãªã¢ã«ã¿ã€ã ã§ããŒã¿ãããã·ã¥ããä»çµã¿ã§ããåè¿°ããããã«WebSocketLinkã®åå²ã®è¿œå ãå¿
èŠãšãªããŸãã ZOZOãããã§ã¯ãQueryãšSubscriptionãçµã¿åãããŠäœ¿çšããŠããŸããQueryã§åæããŒã¿ãååŸãããã®åŸSubscriptionã§ãªã¢ã«ã¿ã€ã ã®å€æŽãåä¿¡ãããšãã圹å²åæ
ã§ãããã®èšèšã«ã¯ä»¥äžã®çç±ããããŸãã åæããŒã¿ã®ç¢ºå®ãªååŸ : Queryã§ç»é¢è¡šç€ºæã«å¿
èŠãªå
šããŒã¿ãäžåºŠã«ååŸã§ãã ãšã©ãŒãã³ããªã³ã°ã®æç¢ºå : åæããŒã¿ååŸãšãªã¢ã«ã¿ã€ã æŽæ°ã§ãšã©ãŒåŠçãåé¢ã§ãã ããŒãžããŒã·ã§ã³å¯Ÿå¿ : éå»ã®ã¡ãã»ãŒãžååŸãªã©ã«ã¯Queryãé©ããŠãã 以äžã¯ã¡ãã»ãŒãžäžèЧã®å€æŽïŒæ°èŠãããã³ã°ãã¡ãã»ãŒãžåä¿¡ïŒã賌èªããå®è£
äŸã§ãã useEffect(() { WidgetsBinding.instance.addPostFrameCallback((_) { // ã¡ãã»ãŒãžäžèЧãååŸããåŸã«ãSubscriptionã賌èªãã subscription = graphQLClient .subscribe( Options$Subscription$OnChannelModified( variables: Variables$Subscription$OnChannelModified( userId: userId, ), ), ) .listen((event) { // ååŸããããŒã¿ãUIã«åæ ããã }); }); return () async { await subscription?.cancel(); }; }, []); ãã®ã³ãŒãã§ã¯ã useEffect ããã¯ã䜿çšããŠãŠã£ãžã§ããã®ã©ã€ããµã€ã¯ã«ã«åãããSubscriptionã管çããŠããŸãã graphQLClient.subscribe ã¡ãœããã§ Options$Subscription$OnChannelModified ã賌èªãããŠãŒã¶ãŒIDã«é¢é£ãããã£ã³ãã«ã®å€æŽãç£èŠããŸãã listen ã¡ãœããã®ã³ãŒã«ããã¯å
ã§ããµãŒããŒããéä¿¡ãããã€ãã³ããåä¿¡ããUIã«åæ ãããŸããããã«ãããæ°ãããããã³ã°ãæç«ãããããçžæããã¡ãã»ãŒãžãåä¿¡ãããããéã«ããªã¢ã«ã¿ã€ã ã§ã¡ãã»ãŒãžäžèŠ§ãæŽæ°ãããŸãã éèŠãªç¹ãšããŠã useEffect ã®ã¯ãªãŒã³ã¢ãã颿°ã§ subscription?.cancel() ãåŒã³åºãããšã§ããŠã£ãžã§ãããç Žæ£ãããéã«é©åã«Subscriptionãè§£é€ããŠããŸããããã«ãããã¡ã¢ãªãªãŒã¯ãé²ããäžèŠãªWebSocketæ¥ç¶ãç¶æããªãããã«ããŠããŸãã éçºã§åŸãç¥èŠãšå·¥å€«ç¹ Optimistic UpdateïŒæ¥œèŠ³çæŽæ°ïŒã«ããUXæ¹å ã¡ãã»ãŒãžéä¿¡æã®ãŠãŒã¶ãŒäœéšãåäžããããããOptimistic UpdateïŒæ¥œèŠ³çæŽæ°ïŒãå®è£
ããŸãããããã¯ããµãŒããŒããã®å¿çãåŸ
ãããŠãŒã¶ãŒã®æäœãå³åº§ã«UIãžåæ ãããææ³ã§ãããŠãŒã¶ãŒãã¡ãã»ãŒãžãéä¿¡ããéã«ãªã¯ãšã¹ãã®çµæãåŸ
ã£ãŠããUIã«åæ ãããšUIã«åæ ããããŸã§ã®æéãé·ããªã£ãŠããŸãããäžå®ã«æããŠããŸãããšããããŸãã å®è£
ã®æµã å³åº§ã®UIæŽæ° : ãŠãŒã¶ãŒãã¡ãã»ãŒãžéä¿¡ãã¿ã³ãã¿ãããããšãRiverpodã§ç®¡çããŠããã¡ãã»ãŒãžãªã¹ãã«äžæçãªã¡ãã»ãŒãžãªããžã§ã¯ããããŒã«ã«ã§è¿œå ããå³åº§ã«UIãžè¡šç€º ããã¯ã°ã©ãŠã³ãã§ã®éä¿¡ : 䞊è¡ããŠGraphQL Mutationã§ãµãŒããŒãžã¡ãã»ãŒãžãéä¿¡ ããŒã¿ã®åæ : Subscriptionçµç±ã§ãµãŒããŒããæ£åŒãªã¡ãã»ãŒãžããŒã¿ãåä¿¡åŸãäžæçãªã¡ãã»ãŒãžã眮ãæã ãã®å®è£
ã«ããããããã¯ãŒã¯é
å»¶ãšé¢ä¿ãªãããŠãŒã¶ãŒã¯èªåã®éä¿¡ããã¡ãã»ãŒãžãå³åº§ã«è¡šç€ºããããããæäœæãåäžããŸããé信倱ææã¯ããšã©ãŒç¶æ
ã衚瀺ãã倱æããã¡ãã»ãŒãžæãã¡ãã»ãŒãžããŒãžæ»ãåäœã«ããŠããŸãã æ¥œèŠ³çæŽæ°ã«å¯Ÿå¿æžã¿ã®ã¡ãã»ãŒãžéä¿¡ æ¥œèŠ³çæŽæ°ãªãã®ã¡ãã»ãŒãžéä¿¡ ã¢ããªããã¯ã°ã©ãŠã³ãæã®Subscription管ç ã¢ãã€ã«ã¢ããªç¹æã®èª²é¡ãšããŠãã¢ããªãããã¯ã°ã©ãŠã³ãã«ç§»è¡ããéã®WebSocketæ¥ç¶ã®æ±ãããããŸããiOSãAndroidã¯ãããããªãŒæ¶è²»ãæãããããããã¯ã°ã©ãŠã³ãã¢ããªã®ãããã¯ãŒã¯æ¥ç¶ãå¶éããŸããããã«ãããGraphQL Subscriptionã§äœ¿çšããŠããWebSocketæ¥ç¶ãèªåçã«åæãããŠããŸããŸãã çºçããåé¡ ã¢ããªãããã¯ã°ã©ãŠã³ãã«ãããšãWebSocketæ¥ç¶ãåæããã ãã®éã«éä¿¡ãããã¡ãã»ãŒãžã¯ãSubscriptionã§ã¯åä¿¡ã§ããªã ãŠãŒã¶ãŒãããã·ã¥éç¥ã§ã¡ãã»ãŒãžã確èªããŠã¢ããªã«æ»ã£ãŠããUIãæŽæ°ãããŠããªã 解決ç ãã®åé¡ã«å¯ŸããŠã次ã®2ã€ã®ã¢ãããŒãã§å¯Ÿå¿ããŸããã èªå忥ç¶ã®å®è£
: autoReconnect: true ã®èšå®ã«ãããã¢ããªããã©ã¢ã°ã©ãŠã³ããžåŸ©åž°ããéãWebSocketæ¥ç¶ãèªåçã«åéããSubscriptionã®è³Œèªã埩垰 ããŒã¿ã®åååŸ : ã¢ããªã®ã©ã€ããµã€ã¯ã«ã€ãã³ããç£èŠãããã©ã¢ã°ã©ãŠã³ã埩垰æã«ææ°ã®ã¡ãã»ãŒãžãªã¹ããQueryã§åååŸ class MessageListScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { useOnAppLifecycleStateChange((previous, current) { if (current == AppLifecycleState.resumed) { // ãã©ã¢ã°ã©ãŠã³ã埩垰æã«ææ°ããŒã¿ãååŸ refetch?.call(); } }); // 以äžãéåžžã®Widgetå®è£
} } ãã®å®è£
ã«ããããŠãŒã¶ãŒãã¢ããªã«æ»ã£ãéã«ã¯åžžã«ææ°ã®ç¶æ
ã衚瀺ãããããã«ãªããŸããã AWS AppSyncãšã®çµ±åã«ããã課é¡ãšè§£æ±ºç ããã¯ãšã³ãã§ AWS AppSync ãå©çšããŠããå Žåãæšæºã®WebSocketLinkã§ã¯AWS AppSyncç¬èªã®èªèšŒåœ¢åŒã«å¯Ÿå¿ã§ããªããšããèª²é¡ 2 ããããŸããAWS AppSyncã¯WebSocketæ¥ç¶æã«ç¹å¥ãªåœ¢åŒã®èªèšŒããããŒãèŠæ±ãããããã«ã¹ã¿ã ã®WebSocketLinkå®è£
ãå¿
èŠã«ãªããŸããã AWS AppSyncã¯èªèšŒæ
å ±ãBase64ãšã³ã³ãŒãããŠURLãã©ã¡ãŒã¿ãšããŠæž¡ãããªã¯ãšã¹ãããã£ãç¬èªã®åœ¢åŒã§éä¿¡ããŸããããã«å¯Ÿå¿ãããããCustomWebSocketLinkãå®è£
ããŸããã /// [WebSocketLink]ãããŒã¹ã«ãããã«ã¹ã¿ã ã®WebSocketLink class CustomWebSocketLink extends Link { CustomWebSocketLink({ required this .getToken, required this .realTimeEndpoint, required this .host, this .subProtocol = GraphQLProtocol.graphqlWs, }); final String subProtocol; final Future< String ?> Function () getToken; final String realTimeEndpoint; final String host; Future< void > connectOrReconnect() async { final token = await getToken(); final authHeader = { 'Authorization' : token, 'host' : host}; final encodedHeader = base64.encode(utf8.encode(jsonEncode(authHeader))); final url = '$realTimeEndpoint:443/graphql/realtime?header=$encodedHeader&payload=e30=' ; await _socketClient?.dispose(); _socketClient = SocketClient( url, config: SocketClientConfig( serializer: _AppSyncRequest(authHeader: authHeader), inactivityTimeout: const Duration(minutes: 2), queryAndMutationTimeout: const Duration(milliseconds: 5000), ), onMessage: (message) { logger.info( 'GraphQL Subscription message: $message' ); }, ); } } /// AWS AppSyncåºæã®èªèšŒåœ¢åŒã«å¯Ÿå¿ããããã®ãªã¯ãšã¹ãã·ãªã¢ã©ã€ã¶ãŒ /// åè: https://github.com/zino-hofmann/graphql-flutter/issues/682#issuecomment-759078492 class _AppSyncRequest extends RequestSerializer { const _AppSyncRequest({required this .authHeader}); final Map< String , dynamic > authHeader; @override Map< String , dynamic > serializeRequest(Request request) => { 'data' : jsonEncode({ 'query' : printNode(request.operation.document), 'variables' : request.variables, }), 'extensions' : { 'authorization' : authHeader}, }; } SocketClient ã®èšå®ã§ã¯ã inactivityTimeout ã§éã¢ã¯ãã£ãæã®ã¿ã€ã ã¢ãŠããèšå®ããŸãã onMessage ã³ãŒã«ããã¯ã§Subscriptionã®ã¡ãã»ãŒãžåä¿¡ç¶æ³ããã°ç¢ºèªã§ãããããã°ã容æã«ãªããŸãã ãŸãšã æ¬èšäºã§ã¯Flutterã¢ããªã«ãããGraphQLã®å®è£
ã玹ä»ããŸãããGraphQLã®å°å
¥ã«ãã£ãŠãªã¢ã«ã¿ã€ã ã®ã¡ãã»ãŒãžæ©èœã®å®çŸãã§ããŸãããGraphQLã®å©çšãæ€èšããŠããæ¹ãããã°ããã²åèã«ããŠã¿ãŠãã ããã ZOZOã§ã¯ãäžç·ã«ãµãŒãã¹ãäœãäžããŠãããæ¹ãåéäžã§ãããèå³ã®ããæ¹ã¯ã以äžã®ãªã³ã¯ãããã²ãå¿åãã ããã hrmos.co corp.zozo.com WebSocketã¯ãã¯ã©ã€ã¢ã³ããšãµãŒããŒéã§åæ¹åéä¿¡ãå¯èœã«ãããããã³ã«ã§ããHTTPãšç°ãªããäžåºŠæ¥ç¶ã確ç«ãããšããµãŒããŒããã¯ã©ã€ã¢ã³ããžä»»æã®ã¿ã€ãã³ã°ã§ããŒã¿ãéä¿¡ã§ããŸãã ↩ graphql_flutterã®GitHubã«issueãäžãã£ãŠããŸãã https://github.com/zino-hofmann/graphql-flutter/issues/682 ↩