aws-amplify / aws-amplify/amplify-android
Cannot Use Amplify-Generated Android Model Files for GraphQL API Requests
- Dominant language
- Java
- Stars
- 287
- Forks
- 132
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 45
Description
### Before opening, please confirm:
- [X] I have [searched for duplicate or closed issues](https://github.com/aws-amplify/amplify-android/issues?q=is%3Aissue+) and [discussions](https://github.com/aws-amplify/amplify-android/discussions).
### Language and Async Model
Java, Kotlin
### Amplify Categories
GraphQL API
### Gradle script dependencies
```groovy
implementation 'com.amplifyframework:core:2.14.6'
implementation 'com.amplifyframework:aws-api:2.14.5'
```
### Environment information
```
# Put output below this line
```
### Please include any relevant guides or documentation you're referencing
https://docs.amplify.aws/android/build-a-backend/graphqlapi/advanced-workflows
### Describe the bug
During the development of our Android application, I utilized Amplify's codegen tool to generate model files intended for interfacing with our GraphQL API. These generated files encompass classes that either extend the `Model` class or are simple Java model classes.
According to the provided documentation, these generated model files should seamlessly facilitate requests to our GraphQL API. However, upon attempting to utilize these files for making requests, we encountered unexpected results. Instead of successful interactions with the API, parsing issues within Amplify arose, leading to the generation of errors.
Notably, the response is returned as a string.
It's worth noting that the files extending the `Model` class seem to work fine, whereas the "simple Java files" do not return expected results.
Our objective is to leverage these generated model files for Amplify requests, streamlining our process and ensuring smoother migrations of future changes. Currently, we are resorting to using custom files for parsing responses, which adds complexity to our workflow.
I am seeking clarification on the root cause of these parsing failures. I have diligently followed the steps outlined in the AWS documentation. Any insights or assistance in resolving this issue would be greatly appreciated.
### Reproduction steps (if applicable)
_No response_
### Code Snippet
```kotlin
fun testGetBatchTeam() : GraphQLRequest{
val document = "query BatchGetTeam(" +
" \$orgID: ID!\n" +
" \$roleSKs: [ID]\n" +
" \$deptSKs: [ID]\n" +
" \$locationSKs: [ID]\n" +
" \$memberSKs: [ID]) {\n" +
" BatchGetTeam(" +
" orgID: \$orgID\n" +
" roleSKs: \$roleSKs\n" +
" deptSKs: \$deptSKs\n" +
" locationSKs: \$locationSKs\n" +
" memberSKs: \$memberSKs) {\n" +
" roles {\n" +
" id\n" +
" orgID\n" +
" sortKey\n" +
" itemType\n" +
" createdAt\n" +
" createdBy\n" +
" updatedAt\n" +
" updatedBy\n" +
" roleName\n" +
" canManageOrgAccount\n" +
" canManageOrgInfo\n" +
" canManageSubscriptions\n" +
" canManageTeamStructure\n" +
" canManageMembers\n" +
" canRemoveMembers\n" +
" canManageChannels\n" +
" canManagePosts\n" +
" canCreateTeamStructure\n" +
" canCreateMembers\n" +
" canCreateChannels\n" +
" canCreatePublicChannels\n" +
" canCreateRoleBasedChannels\n" +
" canCreatePosts\n" +
" deletedAt\n" +
" deletedBy\n" +
" }\n" +
" depts {\n" +
" id\n" +
" orgID\n" +
" sortKey\n" +
" itemType\n" +
" createdAt\n" +
" createdBy\n" +
" updatedAt\n" +
" updatedBy\n" +
" deptName\n" +
" deletedAt\n" +
" deletedBy\n" +
" }\n" +
" locations {\n" +
" id\n" +
" orgID\n" +
" sortKey\n" +
" itemType\n" +
" createdAt\n" +
" createdBy\n" +
" updatedAt\n" +
" updatedBy\n" +
" locationName\n" +
" deletedAt\n" +
" deletedBy\n" +
" }\n" +
" members {\n" +
" id\n" +
" orgID\n" +
" sortKey\n" +
" itemType\n" +
" createdAt\n" +
" createdBy\n" +
" updatedAt\n" +
" updatedBy\n" +
" userID\n" +
" chatUserID\n" +
" chatSystemChannel\n" +
" fullName\n" +
" profilePhoto {\n" +
" cfImageHash\n" +
" cfImageID\n" +
" }\n" +
" employeeID\n" +
" assignedDepts\n" +
" assignedLocations\n" +
" assignedRoles\n" +
" deactivatedAt\n" +
" deactivatedBy\n" +
" deletedAt\n" +
" deletedBy\n" +
" phoneNumber\n" +
" }\n" +
" unprocessedMembers {\n" +
" orgID\n" +
" sortKey\n" +
" }\n" +
" unprocessedLocations {\n" +
" orgID\n" +
" sortKey\n" +
" }\n" +
" unprocessedDepts {\n" +
" orgID\n" +
" sortKey\n" +
" }\n" +
" unprocessedRoles {\n" +
" orgID\n" +
" sortKey\n" +
" }\n" +
" }\n" +
"}"
return SimpleGraphQLRequest(
document,
mapOf(
"orgID" to "04a731e8-0fed-401d-80d5-c5c7244b7fc8",
"roleSKs" to listOf("OWNER"),
"memberSKs" to listOf("TEAM#MEMBER#2024-02-26T12:10:33.648Z")
),
BatchGetTeamOutput::class.java,
GsonVariablesSerializer()
)
}
```
```java
package co.heyinnovation.desklessworkers.data.models.amplify.generated.model;
import androidx.core.util.ObjectsCompat;
import java.util.Objects;
import java.util.List;
/** This is an auto generated class representing the BatchGetTeamOutput type in your schema. */
public final class BatchGetTeamOutput {
private final List roles;
private final List depts;
private final List locations;
private final List members;
private final List unprocessedMembers;
private final List unprocessedLocations;
private final List unprocessedDepts;
private final List unprocessedRoles;
public List getRoles() {
return roles;
}
public List getDepts() {
return depts;
}
public List getLocations() {
return locations;
}
public List getMembers() {
return members;
}
public List getUnprocessedMembers() {
return unprocessedMembers;
}
public List getUnprocessedLocations() {
return unprocessedLocations;
}
public List getUnprocessedDepts() {
return unprocessedDepts;
}
public List getUnprocessedRoles() {
return unprocessedRoles;
}
private BatchGetTeamOutput(List roles, List depts, List locations, List members, List unprocessedMembers, List unprocessedLocations, List unprocessedDepts, List unprocessedRoles) {
this.roles = roles;
this.depts = depts;
this.locations = locations;
this.members = members;
this.unprocessedMembers = unprocessedMembers;
this.unprocessedLocations = unprocessedLocations;
this.unprocessedDepts = unprocessedDepts;
this.unprocessedRoles = unprocessedRoles;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if(obj == null || getClass() != obj.getClass()) {
return false;
} else {
BatchGetTeamOutput batchGetTeamOutput = (BatchGetTeamOutput) obj;
return ObjectsCompat.equals(getRoles(), batchGetTeamOutput.getRoles()) &&
ObjectsCompat.equals(getDepts(), batchGetTeamOutput.getDepts()) &&
ObjectsCompat.equals(getLocations(), batchGetTeamOutput.getLocations()) &&
ObjectsCompat.equals(getMembers(), batchGetTeamOutput.getMembers()) &&
ObjectsCompat.equals(getUnprocessedMembers(), batchGetTeamOutput.getUnprocessedMembers()) &&
ObjectsCompat.equals(getUnprocessedLocations(), batchGetTeamOutput.getUnprocessedLocations()) &&
ObjectsCompat.equals(getUnprocessedDepts(), batchGetTeamOutput.getUnprocessedDepts()) &&
ObjectsCompat.equals(getUnprocessedRoles(), batchGetTeamOutput.getUnprocessedRoles());
}
}
@Override
public int hashCode() {
return new StringBuilder()
.append(getRoles())
.append(getDepts())
.append(getLocations())
.append(getMembers())
.append(getUnprocessedMembers())
.append(getUnprocessedLocations())
.append(getUnprocessedDepts())
.append(getUnprocessedRoles())
.toString()
.hashCode();
}
public static BuildStep builder() {
return new Builder();
}
public CopyOfBuilder copyOfBuilder() {
return new CopyOfBuilder(roles,
depts,
locations,
members,
unprocessedMembers,
unprocessedLocations,
unprocessedDepts,
unprocessedRoles);
}
public interface BuildStep {
BatchGetTeamOutput build();
BuildStep roles(List roles);
BuildStep depts(List depts);
BuildStep locations(List locations);
BuildStep members(List members);
BuildStep unprocessedMembers(List unprocessedMembers);
BuildStep unprocessedLocations(List unprocessedLocations);
BuildStep unprocessedDepts(List unprocessedDepts);
BuildStep unprocessedRoles(List unprocessedRoles);
}
public static class Builder implements BuildStep {
private List roles;
private List depts;
private List locations;
private List members;
private List unprocessedMembers;
private List unprocessedLocations;
private List unprocessedDepts;
private List unprocessedRoles;
public Builder() {
}
private Builder(List roles, List depts, List locations, List members, List unprocessedMembers, List unprocessedLocations, List unprocessedDepts, List unprocessedRoles) {
this.roles = roles;
this.depts = depts;
this.locations = locations;
this.members = members;
this.unprocessedMembers = unprocessedMembers;
this.unprocessedLocations = unprocessedLocations;
this.unprocessedDepts = unprocessedDepts;
this.unprocessedRoles = unprocessedRoles;
}
@Override
public BatchGetTeamOutput build() {
return new BatchGetTeamOutput(
roles,
depts,
locations,
members,
unprocessedMembers,
unprocessedLocations,
unprocessedDepts,
unprocessedRoles);
}
@Override
public BuildStep roles(List roles) {
this.roles = roles;
return this;
}
@Override
public BuildStep depts(List depts) {
this.depts = depts;
return this;
}
@Override
public BuildStep locations(List locations) {
this.locations = locations;
return this;
}
@Override
public BuildStep members(List members) {
this.members = members;
return this;
}
@Override
public BuildStep unprocessedMembers(List unprocessedMembers) {
this.unprocessedMembers = unprocessedMembers;
return this;
}
@Override
public BuildStep unprocessedLocations(List unprocessedLocations) {
this.unprocessedLocations = unprocessedLocations;
return this;
}
@Override
public BuildStep unprocessedDepts(List unprocessedDepts) {
this.unprocessedDepts = unprocessedDepts;
return this;
}
@Override
public BuildStep unprocessedRoles(List unprocessedRoles) {
this.unprocessedRoles = unprocessedRoles;
return this;
}
}
public final class CopyOfBuilder extends Builder {
private CopyOfBuilder(List roles, List depts, List locations, List members, List unprocessedMembers, List unprocessedLocations, List unprocessedDepts, List unprocessedRoles) {
super(roles, depts, locations, members, unprocessedMembers, unprocessedLocations, unprocessedDepts, unprocessedRoles);
}
@Override
public CopyOfBuilder roles(List roles) {
return (CopyOfBuilder) super.roles(roles);
}
@Override
public CopyOfBuilder depts(List depts) {
return (CopyOfBuilder) super.depts(depts);
}
@Override
public CopyOfBuilder locations(List locations) {
return (CopyOfBuilder) super.locations(locations);
}
@Override
public CopyOfBuilder members(List members) {
return (CopyOfBuilder) super.members(members);
}
@Override
public CopyOfBuilder unprocessedMembers(List unprocessedMembers) {
return (CopyOfBuilder) super.unprocessedMembers(unprocessedMembers);
}
@Override
public CopyOfBuilder unprocessedLocations(List unprocessedLocations) {
return (CopyOfBuilder) super.unprocessedLocations(unprocessedLocations);
}
@Override
public CopyOfBuilder unprocessedDepts(List unprocessedDepts) {
return (CopyOfBuilder) super.unprocessedDepts(unprocessedDepts);
}
@Override
public CopyOfBuilder unprocessedRoles(List unprocessedRoles) {
return (CopyOfBuilder) super.unprocessedRoles(unprocessedRoles);
}
}
}
```
### Log output
```
//Response is -----
GraphQLResponse{data='co.heyinnovation.desklessworkers.data.models.amplify.generated.model.BatchGetTeamOutput@2b6d2a38', errors='[]'}
```
### amplifyconfiguration.json
_No response_
### GraphQL Schema
_No response_
### Additional information and screenshots
_No response_
Contributor guide
Research direction
Start with the referenced GraphQL advanced-workflows documentation and the Kotlin request using GraphQLRequest, SimpleGraphQLRequest, and GsonVariablesSerializer. Compare how the generated classes extending Model differ from the simple Java model classes, then reproduce the parsing failure with BatchGetTeamOutput; done means identifying a specific parsing problem and confirming the expected response handling.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, java, kotlin
- Domain
- api
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100