Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Amplify v2 missing migration info #2437

Closed
1 task done
lolucosmin opened this issue May 5, 2023 · 13 comments
Closed
1 task done

Amplify v2 missing migration info #2437

lolucosmin opened this issue May 5, 2023 · 13 comments
Assignees
Labels
api closing soon This issue will be closed in 7 days unless further comments are made. question General question

Comments

@lolucosmin
Copy link

lolucosmin commented May 5, 2023

Before opening, please confirm:

Language and Async Model

Java

Amplify Categories

REST API

Gradle script dependencies

implementation 'com.amplifyframework:core:2.6.0'
implementation 'com.amplifyframework:aws-api:2.6.0'
implementation 'com.amplifyframework:aws-auth-cognito:2.6.0'

Environment information

Welcome to Gradle 7.5!

Here are the highlights of this release:
 - Support for Java 18
 - Support for building with Groovy 4
 - Much more responsive continuous builds
 - Improved diagnostics for dependency resolution

For more details see https://docs.gradle.org/7.5/release-notes.html


------------------------------------------------------------
Gradle 7.5
------------------------------------------------------------

Build time:   2022-07-14 12:48:15 UTC
Revision:     c7db7b958189ad2b0c1472b6fe663e6d654a5103

Kotlin:       1.6.21
Groovy:       3.0.10
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          19 (Oracle Corporation 19+36-2238)
OS:           Windows 11 10.0 amd64


Please include any relevant guides or documentation you're referencing

https://docs.amplify.aws/lib/restapi/getting-started/q/platform/android/

Describe the bug

---Current config---
Right now on my project I am using AWSMobileClient and I want to migrate the project to Amplify v2. I am using also API Gatway, so base on this I have a generated class for all my api requests.

First step for me is to call initialize: AWSMobileClient.getInstance().initialize with a specific configuration created by me.
AWSConfiguration awsConfiguration = new AWSConfiguration(getAWSConfiguration());
AWSMobileClient.getInstance().initialize(AppApplication.getInstance().getApplicationContext(), awsConfiguration, callback);

JSONObject getAWSConfiguration()-I use this because base on flavor some parameters will be changed in that json and I don't take the configuration from a file.

Next step on success result I do this thing:
I create a client with my generated class like this:
client= new ApiClientFactory()
.region("region")
.endpoint("endpoint")
.credentialsProvider("provider")
.build(GeneratedClassFromAPIGatway.class);
-region: eu-west-1
-provider: AWSMobileClient.getInstance()

Until now is very clear and all is working fine.

---Start migrating process---

  1. Base on documentation I configured the project and I initialized the Amplify v2.

Amplify.addPlugin(new AWSApiPlugin());
Amplify.addPlugin(new AWSCognitoAuthPlugin());
Amplify.configure(getContext());

---Issues---

  1. How I can pass a dynamic configuration like I did for AWSMobileClient base on flavor and buildtype.
  2. I have my generated class and my ApiClientFactory, how I can use it with Amplify.API?

Reproduction steps (if applicable)

No response

Code Snippet

// Put your code below this line.

Log output

// Put your logs below this line


amplifyconfiguration.json

No response

GraphQL Schema

// Put your schema below this line

Additional information and screenshots

No response

@tylerjroach
Copy link
Member

@lolucosmin

There is a separate Amplify.configure method that allows you to pass in an AmplifyConfiguration.
configure(@NonNull final AmplifyConfiguration configuration, @NonNull Context context)

There are helper methods inside AmplifyConfiguration that allow it to be generated via a generated JSONObject, read from a config file, or constructed via Map<String, CategoryConfiguration> configs.

Please see the Amplify.API documentation for using API. ApiClientFactory is from the AWS Android SDK, which must be removed to not conflict with Amplify v2.

https://docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/android/

@tylerjroach tylerjroach added the api label May 8, 2023
@lolucosmin
Copy link
Author

lolucosmin commented May 8, 2023

@lolucosmin

There is a separate Amplify.configure method that allows you to pass in an AmplifyConfiguration. configure(@NonNull final AmplifyConfiguration configuration, @NonNull Context context)

There are helper methods inside AmplifyConfiguration that allow it to be generated via a generated JSONObject, read from a config file, or constructed via Map<String, CategoryConfiguration> configs.

Please see the Amplify.API documentation for using API. ApiClientFactory is from the AWS Android SDK, which must be removed to not conflict with Amplify v2.

https://docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/android/

Hi @tylerjroach ,

1.Indeed for configuration in the end I found how to use it.

AmplifyConfiguration configuration = AmplifyConfiguration.builder(getConfiguration())
.addPlatform(UserAgent.Platform.ANDROID, BuildConfig.VERSION_NAME)
.devMenuEnabled(BuildConfig.DEBUG)
.build();
Amplify.configure(configuration, application);

2.But my big issue is the 2 problem. My generated class contains at least 110 api requests with this format:

@com.amazonaws.mobileconnectors.apigateway.annotation.Operation(path = "/com_cards/action", method = "PUT")
Response comCardsActionPut(
ComCardsActionInput body,
@com.amazonaws.mobileconnectors.apigateway.annotation.Parameter(name = "app_info", location = "query")
String appInfo
);

So that means I need to transform it into:

Map<String, String> queryParams = new HashMap<>();
queryParams.put("app_info",getAppInfo());
RestOptions options = RestOptions.builder()
.addPath("/com_cards/action")
.addBody(body.getBytes())
.addQueryParameters(queryParams)
.build();

There is a way or a mechanism to make them programmatical from old version to the new version? Or from API Gatway to generate the Android SDK with the format for Amplify V2?

sdk

@eeatonaws eeatonaws added the question General question label May 9, 2023
@tylerjroach
Copy link
Member

At this time, there is not an automated method to create a Codegen SDK for AmplifyGateway using the newer Kotlin SDK. However, you can pass a custom Credentials Provider that wraps Amplify v2 into the existing Android Codegen approach as seen here: #2400 (comment)

@tylerjroach tylerjroach self-assigned this May 11, 2023
@lolucosmin
Copy link
Author

lolucosmin commented May 15, 2023

At this time, there is not an automated method to create a Codegen SDK for AmplifyGateway using the newer Kotlin SDK. However, you can pass a custom Credentials Provider that wraps Amplify v2 into the existing Android Codegen approach as seen here: #2400 (comment)

Hi, thx for that workaround but that has a weird issue, the refresh token is not automated made. I tried a lot of things to force it but after 1h all my Api requests are falling. Only if I destroy the app and start it again will works.

Also I think we need to have this automated method to create a Codegen SDK, for such a large company, I don't think it would be that difficult to make 2 scripts to generate Java and Kotlin code.

Thx.

@tylerjroach
Copy link
Member

@lolucosmin I see another report about requests failing after 1 hr.
aws-amplify/aws-sdk-android#3289

I will try to take a look on my end. It is the responsibility of the custom credentials provider to refresh the token as necessary in the getCredentials method. However, I would have expected fetchAuthSession to refresh tokens as needed.

@tylerjroach
Copy link
Member

@lolucosmin Can you send a code sample of how you are passing your custom credentials provider for API Gateway?

@lolucosmin
Copy link
Author

lolucosmin commented May 18, 2023

@lolucosmin Can you send a code sample of how you are passing your custom credentials provider for API Gateway?

@tylerjroach Hi, so this my code:

MyCredentialsProvider provider = new MyCredentialsProvider();
BFanApiClient client = new ApiClientFactory()
.region(AppConfig.REGION.getName())
.endpoint(AppConfig.SERVER_ENDPOINT)
.credentialsProvider(provider)
.build(BFanApiClient.class);

BFanApiClient.class - generated class from API Gatway.

So after I create a new instance of BFanApiClient I use it with observers to execute my api requests.
All are working fine, but after 1 hour all my api requests are falling. What I saw after few tests,
if I create a new instance of my provider will works but this is not a good idea.

The main idea is to put this provider and client into a singleton class and keep it while the app is running,
and the refresh part should be automatic done by MyCredentialsProvider.

@tylerjroach
Copy link
Member

@lolucosmin

Are you using the MyCredentialsProvider exactly as I had linked? If not, can you provide the code to your provider here.

What you have should work. When a request is built, it will use the provided credentialsProvider.getCredentials() method.

Are you creating requests as necessary, or creating a request, then making the request call after 1 hr. It appears that getCredentials is called as the request is built (https://github.com/aws-amplify/aws-sdk-android/blob/main/aws-android-sdk-apigateway-core/src/main/java/com/amazonaws/mobileconnectors/apigateway/ApiClientHandler.java#L199).

If you could also initialize Amplify by adding our logging plugin first:
Amplify.addPlugin(AndroidLoggingPlugin(LogLevel.VERBOSE)), we will be able to observe what is happening internally at the Auth layer when those requests are made.

@lolucosmin
Copy link
Author

lolucosmin commented May 18, 2023

@lolucosmin

Are you using the MyCredentialsProvider exactly as I had linked? If not, can you provide the code to your provider here.

What you have should work. When a request is built, it will use the provided credentialsProvider.getCredentials() method.

Are you creating requests as necessary, or creating a request, then making the request call after 1 hr. It appears that getCredentials is called as the request is built (https://github.com/aws-amplify/aws-sdk-android/blob/main/aws-android-sdk-apigateway-core/src/main/java/com/amazonaws/mobileconnectors/apigateway/ApiClientHandler.java#L199).

If you could also initialize Amplify by adding our logging plugin first: Amplify.addPlugin(AndroidLoggingPlugin(LogLevel.VERBOSE)), we will be able to observe what is happening internally at the Auth layer when those requests are made.

@tylerjroach I will provide you more details.

App flow:

1.In Application class I initialized the Amplify:

        AmplifyConfiguration configuration = AmplifyConfiguration.builder(getConfiguration())
                .addPlatform(UserAgent.Platform.ANDROID, BuildConfig.VERSION_NAME)
                .devMenuEnabled(BuildConfig.DEBUG)
                .build();

        AWSApiPlugin apiPlugin = AWSApiPlugin.builder().build();
        apiPlugin.configure(getApiConfiguration(), application);

        Amplify.addPlugin(apiPlugin);
        Amplify.addPlugin(new AWSCognitoAuthPlugin());
        Amplify.configure(configuration, application);

2.After continue to initialized ApiClientFactory:

    MyCredentialsProvider provider= new MyCredentialsProvider();
    BFanApiClient client = new ApiClientFactory()
            .region(AppConfig.REGION.getName())
            .endpoint(AppConfig.SERVER_ENDPOINT)
            .credentialsProvider(provider)
            .build(BFanApiClient.class);

3.Then the app continue to main screen, where there I do some get data from server and right now for test I am using this method:

final Handler handler = new Handler(Looper.getMainLooper());
    final Executor executor = Executors.newSingleThreadExecutor();
    executor.execute(() -> {
        Exception err = null;
        Organization organization = null;
        try {
            organization = client.organizationGet(BuildConfig.TEAM_ID, "", AppLocaleHelper.getInstance().getLocaleCode(), BaseObserver.getAppInfo());
        } catch (Exception ex) {
            err = ex;
        }
        Exception finalErr = err;
        Organization finalOrganization = organization;
        handler.post(() -> {
            LogUtils.i("Test:" + finalErr);
            LogUtils.i("Test:" + finalOrganization);
        });
    });

So as you can see from my client which was initialized I call a specific function:
client.organizationGet(BuildConfig.TEAM_ID, "", AppLocaleHelper.getInstance().getLocaleCode(), BaseObserver.getAppInfo());

This function is from my generated class from API Gatway and it looks like this:
/**
* @param orgId
* @param identityId
* @param locale
* @return Organization
*/
@com.amazonaws.mobileconnectors.apigateway.annotation.Operation(path = "/organization", method = "GET")
Organization organizationGet(
@com.amazonaws.mobileconnectors.apigateway.annotation.Parameter(name = "org_id", location = "query")
String orgId,
@com.amazonaws.mobileconnectors.apigateway.annotation.Parameter(name = "identityId", location = "header")
String identityId,
@com.amazonaws.mobileconnectors.apigateway.annotation.Parameter(name = "locale", location = "header")
String locale,
@com.amazonaws.mobileconnectors.apigateway.annotation.Parameter(name = "app_info", location = "query")
String appInfo
);

I keep the application open and for an hour everything is ok, if I re-execute that request 100 times it will work, but after an hour exactly when the cognito session expires it stops working.

Tomorrow I will add also this:
Amplify.addPlugin(AndroidLoggingPlugin(LogLevel.VERBOSE))
and I will tell you exactly what I will get in logs.

This is my MyCredentialsProvider:

public class MyCredentialsProvider implements AWSCredentialsProvider {

@Override
public AWSCredentials getCredentials() {
    // Need to make fetchAuthSessionBlocking
    CountDownLatch latch = new CountDownLatch(1);
    final AWSCredentials[] sdkCredentials = {null};

    Amplify.Auth.fetchAuthSession(new Consumer<AuthSession>() {
        @Override
        public void accept(@NonNull AuthSession authSession) {
            AWSTemporaryCredentials awsTemporaryCredentials = null;

            if (authSession instanceof AWSCognitoAuthSession) {
                AWSCognitoAuthSession awsCognitoAuthSession = (AWSCognitoAuthSession) authSession;
                awsTemporaryCredentials = awsCognitoAuthSession.getAwsCredentialsResult().getValue() instanceof AWSTemporaryCredentials
                        ? (AWSTemporaryCredentials) awsCognitoAuthSession.getAwsCredentialsResult().getValue() : null;
            }

            if (awsTemporaryCredentials != null) {
                sdkCredentials[0] = new BasicSessionCredentials(
                        awsTemporaryCredentials.getAccessKeyId(),
                        awsTemporaryCredentials.getSecretAccessKey(),
                        awsTemporaryCredentials.getSessionToken()
                );
            }

            latch.countDown();
        }
    }, new Consumer<AuthException>() {
        @Override
        public void accept(@NonNull AuthException value) {
            latch.countDown();
        }
    });

    // wait for fetchAuthSession to return
    try {
        latch.await();
    } catch (InterruptedException ex) {
        throw new IllegalStateException("Interrupted while waiting for credentials", ex);
    }

    // return captured credentials or throw
    if (sdkCredentials[0] == null) {
        throw new IllegalStateException("Failed to get credentials");
    }
    return sdkCredentials[0];
}

@Override
public void refresh() {
    Amplify.Auth.fetchAuthSession(
            AuthFetchSessionOptions.builder().forceRefresh(true).build(),
            result -> { /* do nothing on success */ },
            error -> { /* do nothing on failure */ }
    );
}

}

Important:
In parallel I also created this request exactly like in documentation and I run them in parallel after I saw that with ApigClientFactory is falling.

Step 1: - migrate the client.organizationGet to the new format:

public static RestOptions getOrganization(String orgId) {
    Map<String, String> queryParams = getDefaultQueryParams();
    queryParams.put("org_id", orgId);
    return RestOptions.builder()
            .addHeaders(getDefaultHeaders())
            .addPath("/organization")
            .addQueryParameters(queryParams)
            .build();
}

Step 2: - run it
Amplify.API.get("bFanApi", ApiRestOptions.getOrganization(BuildConfig.TEAM_ID), new Consumer() {
@OverRide
public void accept(@nonnull RestResponse value) {
LogUtils.i("Test");
}
}, new Consumer() {
@OverRide
public void accept(@nonnull ApiException value) {
LogUtils.i("Test");
}
});

So after an hour, that with ApiClientFactory is falling and this one with the new structure is working well.
So I am super confused why this thing is happening.

@lolucosmin
Copy link
Author

@tylerjroach did you check my last info?

@tylerjroach
Copy link
Member

@lolucosmin, thank you for the update. Looking over your code this morning, it would still be helpful to see logs posted from Amplify.addPlugin(AndroidLoggingPlugin(LogLevel.VERBOSE)) to see why Amplify does not appear to be refreshing the credentials from the credentials provider after 1 hr.

It may also be helpful to add your own logging in your MyCredentialsProvider. Ex: is it failing because it is failing to fetch credentials or returning expired credentials. Also, what would happen if you attempted to force refresh credentials manually after 1 hr using AuthFetchSessionOptions.builder().forceRefresh(true).build().

@mattcreaser mattcreaser added the closing soon This issue will be closed in 7 days unless further comments are made. label Aug 21, 2023
@gpanshu
Copy link
Contributor

gpanshu commented Aug 31, 2023

Closing due to inactivity. Please feel free to open a new bug if you are still facing issues.

@gpanshu gpanshu closed this as completed Aug 31, 2023
@github-actions
Copy link
Contributor

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api closing soon This issue will be closed in 7 days unless further comments are made. question General question
Projects
None yet
Development

No branches or pull requests

5 participants