ferry icon indicating copy to clipboard operation
ferry copied to clipboard

Conditional fragment spreading in a fragment

Open Pylyr opened this issue 2 years ago • 10 comments

So i have a feed which can have 2 types of videos, a position and an update.

Here are how they defined using an interface VideoInterface. An update only has these fields. A position has all of these fields plus an extra field position

interface VideoInterface {
  id: ID!
  thumbnail: String!
  video: String!
  likeCount: Int!
  company: CompanyType!
}

type PositionVideoViewType implements VideoInterface {
  id: ID!
  thumbnail: String!
  video: String!
  likeCount: Int!
  company: CompanyType!
  position: PositionType!
}

Since these are used in more than one place in the app and I do not actually need all the fields from the company, I put them in a fragment.

fragment VideoViewFields on VideoInterface {
  id
  thumbnail
  video
  likeCount
  company {
    id
    name
    username
  }
}

I then query the videos from the server

query GetExploreVideos {
  getExploreVideos{
    ...VideoViewFields
    ... on PositionVideoViewType {
      position {
        title
      }
    }
  }
}

Everything works

However, as I said these videos are queried very often, so I get decide to put it in a fragment.

fragment VideoViewFragment on VideoInterface {
  ...VideoViewFields
  ... on PositionVideoViewType {
    position {
      title
      ...PositionDescriptionFragment
    }
  }
}

And therefore the request becomes

query GetExploreVideos {
  getExploreVideos{
    ...VideoViewFragment
  }
}

However, his is where things break. The graphiQl still processes everything correct, but ferry has some trouble. Specifically it only queries the fields in ...VideoViewFields and completely ignores ...on PositionVideoViewType, even if G__typenname is PositionVideoViewType.

Using .when() also doesn't work, even though it correctly identifies that it is a PositionVideoViewType, it fails at casting it, because the data for these fields is simply not saved.

So for now I cannot use VideoViewFragment, which is very sad, because not only do I have to do a lot of repetitions in the .graphql queries, but the code-gened types are not compatible.

Pylyr avatar Oct 17 '23 15:10 Pylyr

Would be great if you could produce a minimal reproductible example so I can take a look.

knaeckeKami avatar Oct 17 '23 15:10 knaeckeKami

schema.graphql

schema {
  query: Queries
}

interface VideoInterface {
  id: ID!
  thumbnail: String!
  video: String!
  likeCount: Int!
  company: ComanyType!
}

type CompanyType {
    id: String
    name: String
    username: String
    other: String
}

type PositionType {
  id: ID!
  title: String
  startDate: String
  duration: Int
  }


type PositionVideoViewType implements VideoInterface {
  id: ID!
  thumbnail: String!
  video: String!
  likeCount: Int!
  company: CompanyType!
  position: PositionType!
}


type Queries {
  getExploreVideos: [VideoInterface!]!
}

fragments.graphql


fragment VideoViewFields on VideoInterface {
  id
  thumbnail
  video
  likeCount
  company {
    id
    name
    username
  }
}


fragment VideoViewFragment on VideoInterface {
  ...VideoViewFields
  ... on PositionVideoViewType {
    position {
      title
    }
  }
}

get_explore_videos.graphql

# import "fragments.graphql"

query GetExploreVideos {
  getExploreVideos{
    ...VideoViewFragment
  }
}

Test

typedef VideoView = GVideoViewFragment;
Future<List<VideoView>> fetchExploreVideos() async {
    final response = await client
        .request(GGetExploreVideosReq(
            (b) => b..fetchPolicy = FetchPolicy.NetworkOnly))
        .first;

    return response.data!.getExploreVideos.toList();

final videos = await fetchExploreVideos();
 for (var video in response.data!.getExploreVideos.toList()) {
      if (video.G__typename == 'PositionVideoViewType') {
        final castedPosition = video as PositionVideoView; 
**//  Unhandled Exception: type '_$GGetExploreVideosData_getExploreVideos' is not a subtype of type 'GVideoViewFragment__asPositionVideoViewType' in type cast**
      }
    }

I think these 2 screenshots from the debugger communicate the issue well: This is what happens if you fragment the request, i.e you do

query GetExploreVideos {
  getExploreVideos{
    ...VideoViewFragment
  }
}
bad

And here is what happens if you do not fragment it and do

query GetExploreVideos {
  getExploreVideos{
    ...VideoViewFields
    ... on PositionVideoViewType {
      position {
        title
      }
    }
  }
}
good

Pylyr avatar Oct 17 '23 16:10 Pylyr

Thanks, I can reproduce the issue.

knaeckeKami avatar Oct 20 '23 17:10 knaeckeKami

I have the same issue.

rexmihaela avatar Nov 02 '23 21:11 rexmihaela

Any news on this ?

Masadow avatar Mar 06 '24 08:03 Masadow

I've got exactly the same problem. @Masadow @rexmihaela @Pylyr - did you ever figure out how to solve it?

yonahforst avatar May 06 '25 13:05 yonahforst

is still still reproducible with the latest version of ferry_generator?

knaeckeKami avatar May 06 '25 22:05 knaeckeKami

@knaeckeKami - here's the version from my pubspec.lock

  ferry_generator:
    dependency: "direct dev"
    description:
      name: ferry_generator
      sha256: "6637ea751297f56c8971691f63b1191d14bf649dacfdcaa08a594915eba47bda"
      url: "https://pub.dev"
    source: hosted
    version: "0.12.0"

yonahforst avatar May 08 '25 14:05 yonahforst

so, does it happen on 0.14.0?

knaeckeKami avatar May 08 '25 15:05 knaeckeKami

Hi @knaeckeKami , no it doesn't happen on 0.14.0 👏👏 But I run into a different error with the same setup.

fragment validationError on ValidationError {
    ... on ValidationError {
        __typename
        message
    }
}

query getUserProfile($userId: ExternalRefInput!) {
	PolicyApplication {
		getUserProfile(userId: $userId) {
  	 	        ...validationError
			... on PolicyApplicationQueryGetUserProfileSuccess {
				data {
					...person
				}
			}
		}
	}
}

ValidationError is in separate fragment because I use it in many places.

The generated file contains this:

abstract class GgetUserProfileData_PolicyApplication_getUserProfile__asPolicyApplicationQueryGetUserProfileSuccess
    implements
        Built<
            GgetUserProfileData_PolicyApplication_getUserProfile__asPolicyApplicationQueryGetUserProfileSuccess,
            GgetUserProfileData_PolicyApplication_getUserProfile__asPolicyApplicationQueryGetUserProfileSuccessBuilder>,
        _i3.GvalidationError,
        _i3.GvalidationError__asPolicyApplicationQueryGetUserProfileSuccess,
        GgetUserProfileData_PolicyApplication_getUserProfile {
  GgetUserProfileData_PolicyApplication_getUserProfile__asPolicyApplicationQueryGetUserProfileSuccess._();

_i3.GvalidationError__asPolicyApplicationQueryGetUserProfileSuccess, makes no sense and the linter shows this error:

Image

yonahforst avatar May 08 '25 19:05 yonahforst