aws-amplify / amplify-flutter

A declarative library with an easy-to-use interface for building Flutter applications on AWS.
https://docs.amplify.aws
Apache License 2.0
1.31k stars 247 forks source link

FlutterJNI was detached from native C++ #1504

Closed crawford-jake closed 2 years ago

crawford-jake commented 2 years ago

Description

When I configure Amplify using a custom FunctionAuthProvider, the method channel fails to call getLatestAuthToken when the app state changes. This does not occur for api authorizations that do not use the FunctionAuthProvider class. Seemingly because the method channel call in FlutterAuthProvider.kt is avoided.

When the app reopens from a state change Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++ is thrown on all API requests. I have confirmed that the method channel call to retrieve the custom token fails.

Our QA team has reported the same issue coming from their iOS devices as well, however it is easiest to reproduce in Android.

Categories

Steps to Reproduce

Steps to Reproduce

  1. Create a new flutter project on the stable channel
  2. Install amplify_flutter 0.4.3 and amplify_api 0.4.3
  3. Configure Amplify with FunctionAuthProvider and setup working Amplify Query
  4. Break Amplify

1. Create a new flutter project on the stable channel

Run the following commands in the terminal flutter channel stable flutter create amplify_bug cd amplify_bug flutter pub cache clean flutter pub get

Setup iOS for Amplify Modify the ios/Podfile and replace the second line with: platform :ios, '11.0' cd ios && pod install

Setup Android for Amplify Modify the android/app/build.gradle to the minSdkVersion 21

defaultConfig {
    minSdkVersion 21
}

Confirm the project runs on the iOS simulator and Android emulator flutter pub get flutter run

2. Install amplify_flutter 0.4.3 and amplify_api 0.4.3

Open pubspec.yaml and add the following dependencies

amplify_flutter: 0.4.3
amplify_api: 0.4.3

Then confirm that these packages run flutter pub get flutter run

3. Configure Amplify with FunctionAuthProvider and setup working Amplify Query

Create a new .dart file in the lib directory, and copy the following. (To see this render your will need to set this as the home widget in main.dart)

 import 'package:amplify_api/amplify_api.dart';
 import 'package:amplify_flutter/amplify_flutter.dart';

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

  bool _amplifyConfigured = false;

  _configureWithCustomAuth() async {
    await Amplify.addPlugin(
      AmplifyAPI(
        authProviders: [
          CustomAuthProvider()
        ]
      )
    );
    try {
      await Amplify.configure(amplifyconfig_customAuth);
      setState(() {
        _amplifyConfigured = true;
      });
    } on AmplifyAlreadyConfiguredException {
      print("Amplify was already configured. Looks like app restarted on android.");
    }
  }

  @override
  void initState() {
    _configureWithCustomAuth();

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: _amplifyConfigured ? ExampleAmplifyCall() : CircularProgressIndicator(),
    );
  }
}

You will find three errors: amplifyconfig_customAuth, ExampleAmplifyCall, and CustomAuthProvider To resolve amplifyconfig_customAuth, set it to the following (Please note you will need to provide your own API Key, endpoint, and friendly name):

 final String amplifyconfig_customAuth = '''{
    "UserAgent": "aws-amplify-cli/2.0",
    "Version": "1.0",
    "api": {
      "plugins": {
        "awsAPIPlugin": {
          "<<YOUR-API-FRIENDLY-NAME>>": {
            "endpointType": "GraphQL",
            "endpoint": "<<https://........>>",
            "region": "us-east-1",
            "authorizationType": "AWS_LAMBDA",
            "apiKey": "<<da2-XXXXXXXXXXXXXXXXX>>"
          }
        }
      }
    }
}''';

This configuration has been set referencing the following Amplify/AWS Lambda documentation

To resolve CustomAuthProvider, add the following:

import 'package:amplify_api/amplify_api.dart';

class CustomAuthProvider extends FunctionAuthProvider {
  CustomAuthProvider();

  @override
  Future<String?> getLatestAuthToken() async {
    return "Bearer <<YOUR-TOKEN>>";
  }
}

Note: This will need to be a valid Bearer token, intended to be used for endpoint you stated in amplifyconfig_customAuth

To resolve ExampleAmplifyCall, add the following to a new dart file and import where necessary:

class ExampleAmplifyCall extends StatefulWidget {
  const ExampleAmplifyCall({ Key? key }) : super(key: key);

  @override
  State<ExampleAmplifyCall> createState() => _ExampleAmplifyCallState();
}

class _ExampleAmplifyCallState extends State<ExampleAmplifyCall> {

  run() async {
    try {
      var query = Amplify.API.query(
        request: GraphQLRequest(document: myQuery, variables: {"key": "value"})
      );
      var response = await query.response;
      print("errors: ${response.errors}");
      var data = jsonDecode(response.data);
      print(data);
      print("SUCCESS");
    } catch(e){
      print("problem running query");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: TextButton(
        child: Text("RUN QUERY"),
        onPressed: (){
          run();
        },
      ),
    );
  }
}

Note: Any graphQL request that works with your specified endpoint will do.

You should now be able to run the flutter app, and tap "RUN QUERY" to see Amplify data print in the console (Assuming your amplify configuration, token, and query is correct).

4. Break Amplify

Run the flutter app on an Android Emulator. After confirming the query completes successfully, click the Android back button to return to the Android home screen.

Wait on the phone home screen for several seconds, then click on the "Recently used apps" button, and select the flutter app. When Amplify has been configured, you will be able to click on "RUN QUERY" again. This time however, you will see the following error: Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: com.amazonaws.amplify/api. Response ID: 11

I have found that the error being thrown is from the FlutterAuthProvider.kt file in the android api folder. When you are debugging, you will find that when the app restarts that getLatestAuthToken in the dart file we created is not reached because of a disconnect in the method channel.

If you would like to quickly see this error disappear, change the authorizationType in the amplify configuration to "authorizationType": "AWS_LAMBDA" and initialize Amplify with the following:

await Amplify.addPlugin(
  AmplifyAPI()
);
await Amplify.configure(amplifyconfig_apiKey);

By simply changing our code to an API key, we resolved the JNI Issue

Screenshots

https://user-images.githubusercontent.com/82613137/161838322-8ab8b40e-df2b-4bea-8b19-4f177ded3177.mov

Platforms

Environment

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.10.4, on macOS 12.3 21E230 darwin-x64, locale en-US)
[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2020.3)
[✓] VS Code (version 1.66.0)
[✓] Connected device (2 available)
[✓] HTTP Host Availability

• No issues found!

Dependencies

Dart SDK 2.16.2
Flutter SDK 2.10.4
amplify_bug 1.0.0+1

dependencies:
- amplify_api 0.4.3 [amplify_api_plugin_interface amplify_core collection flutter meta plugin_platform_interface]
- amplify_flutter 0.4.3 [amplify_analytics_plugin_interface amplify_api_plugin_interface amplify_auth_plugin_interface amplify_core amplify_datastore_plugin_interface amplify_storage_plugin_interface collection flutter json_annotation meta plugin_platform_interface]
- cupertino_icons 1.0.4
- flutter 0.0.0 [characters collection material_color_utilities meta typed_data vector_math sky_engine]

transitive dependencies:
- amplify_analytics_plugin_interface 0.4.3 [amplify_core flutter meta]
- amplify_api_plugin_interface 0.4.3 [amplify_core collection flutter json_annotation meta]
- amplify_auth_plugin_interface 0.4.3 [flutter meta amplify_core]
- amplify_core 0.4.3 [flutter plugin_platform_interface collection date_time_format meta uuid]
- amplify_datastore_plugin_interface 0.4.3 [flutter meta collection amplify_core]
- amplify_storage_plugin_interface 0.4.3 [flutter meta amplify_core]
- characters 1.2.0
- collection 1.15.0
- crypto 3.0.1 [collection typed_data]
- date_time_format 2.0.1
- json_annotation 4.4.0 [meta]
- material_color_utilities 0.1.3
- meta 1.7.0
- plugin_platform_interface 2.1.2 [meta]
- sky_engine 0.0.99
- typed_data 1.3.0 [collection]
- uuid 3.0.6 [crypto]
- vector_math 2.1.1

Device

Pixel 4 Emulator API 30

OS

Android

CLI Version

7.6.26 (The configuration in the example is manually set from the Flutter Amplify API Docs)

Additional Context

No response

haverchuck commented 2 years ago

@crawford-jake Thanks for submitting this - we are taking a look.

dnys1 commented 2 years ago

I can reproduce on Flutter stable and master. Related Flutter issue: https://github.com/flutter/flutter/issues/28651

haverchuck commented 2 years ago

@crawford-jake We are in the midst of reviewing/testing a fix.

crawford-jake commented 2 years ago

@haverchuck Thank you!

haverchuck commented 2 years ago

@crawford-jake Can you please give version 0.4.4 a try?

crawford-jake commented 2 years ago

@haverchuck It's working great on Amplify 0.4.4 with flutter stable 2.10.4. I want to give our QA team a chance to test this in our app. Following that i'll close the issue. Thank you for the quick fix! You guys rock :)

crawford-jake commented 2 years ago

@haverchuck It's working great, thanks so much again! I just had a quick question about the 2 second timeout in FlutterAuthProvider.swift/kt. When we launch the app we setup some streams and queries. However on a cache miss or the token expires, the server takes longer than two seconds to deliver the request. So for example if the token is delivered in 2.1 seconds Amplify will throw and doesn't retry. Would a retry be something we should handle client side, or something you could easily patch?

haverchuck commented 2 years ago

@crawford-jake We can bump that timeout threshhold for now, until we decide on a way to make it configurable. Would that be helpful?

crawford-jake commented 2 years ago

@haverchuck Yep, that would be great.

haverchuck commented 2 years ago

@crawford-jake Ok, we will make this change soon. I'll keep this issue open to track it.

crawford-jake commented 2 years ago

@haverchuck We found another issue similar to above. Using the same steps as above, change the query in the ExampleAmplifyCall to a stream. Then on iOS open the app in release mode on a physical device, verify the stream established successfully, move the app to the background (don't close, just open another app), and after some time return to the example app.

This gives us the following error in XCODE: Error in subscription stream: ApiException(message: Subscription item event failed with error, recoverySuggestion: , underlyingException: The operation couldn’t be completed. (AppSyncRealTimeClient.ConnectionProviderError error 3.))

After this occurs, streams no longer update. I will provide more detailed steps if necessary.

Thanks!

haverchuck commented 2 years ago

@crawford-jake We've updated the timeout you called out previously in version 0.4.5.

crawford-jake commented 2 years ago

@haverchuck Thank you!

HuiSF commented 2 years ago

This issue has been fix since version 0.4.4.

iamMrGaurav commented 1 month ago

https://medium.com/@gauravpaudel2013/tried-to-send-a-platform-message-to-flutter-but-flutterjni-was-detached-from-native-c-well-well-c7d9ccf0df96