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.32k stars 248 forks source link

Flutter AAB file in "release" mode results in Amplify Authentication sign-out on every app closure #1142

Closed rahul-insight closed 2 years ago

rahul-insight commented 2 years ago

Describe the bug We are using AWS Amplify and building our Flutter app on Codemagic by pulling in Amplify env. For Android while building an AAB on Codemagic in "release" mode, the build kicks out some of the Amplify files, resulting in Authentication failing when the user closes the app (sign out happens). This issue doesn't happen on iOS or if we build in "debug" mode or build an AAB on the local machine.

The issue only happens on the actual device. Does anyone know why Amplify is signing out for a release AAB on every app closure?

Adding "--no-shrink" during build or "proguard-rules.pro" to the android/app as mentioned here didn't work: https://github.com/aws-amplify/amplify-flutter/issues/317 https://stackoverflow.com/questions/64565537/aws-amplify-flutters-auth-signup-function-is-not-working-when-deployed-via-andr

To Reproduce Build AAB file from Flutter app using Amplify Authentication (Social sign in) using: flutter build appbundle --release

Platform Amplify Flutter current supports iOS and Android. This issue is reproducible in (check all that apply): [X] Android [] iOS

Output of flutter doctor -v ```[√] Flutter (Channel stable, 2.5.3, on Microsoft Windows [Version 10.0.22000.348], locale en-US) • Flutter version 2.5.3 at C:\Users\rahul\Projects\flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 18116933e7 (6 weeks ago), 2021-10-15 10:46:35 -0700 • Engine revision d3ea636dc5 • Dart version 2.14.4 [!] Android toolchain - develop for Android devices (Android SDK version 30.0.3) • Android SDK at C:\Users\rahul\AppData\Local\Android\sdk X cmdline-tools component is missing Run `path/to/sdkmanager --install "cmdline-tools;latest"` See https://developer.android.com/studio/command-line for more details. X Android license status unknown. Run `flutter doctor --android-licenses` to accept the SDK licenses. See https://flutter.dev/docs/get-started/install/windows#android-setup for more details. [√] Chrome - develop for the web • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe [√] Android Studio (version 4.1) • Android Studio at C:\Program Files\Android\Android Studio • Flutter plugin can be installed from: https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01) [√] VS Code (version 1.62.3) • VS Code at C:\Users\rahul\AppData\Local\Programs\Microsoft VS Code • Flutter extension version 3.28.0 [√] Connected device (3 available) • sdk gphone x86 (mobile) • emulator-5554 • android-x86 • Android 11 (API 30) (emulator) • Chrome (web) • chrome • web-javascript • Google Chrome 96.0.4664.45 • Edge (web) • edge • web-javascript • Microsoft Edge 95.0.1020.53 ! Doctor found issues in 1 category.```
Dependencies (pubspec.lock) ``` Paste the contents of your "pubspec.lock" file here ```

Smartphone (please complete the following information):

Additional context Add any other context about the problem here.

Jordan-Nelson commented 2 years ago

Hello @rahul-insight - What version of amplify-flutter are you using?

rahul-insight commented 2 years ago

Hello @Jordan-Nelson We are using the below packages in our project. We tried upgrading all of them to the latest version 0.2.10 but that didn't make a difference.

amplify_auth_cognito: ^0.2.0 amplify_api: ^0.2.0 amplify_flutter: ^0.2.0 amplify_core: ^0.2.0 amplify_analytics_pinpoint: ^0.2.0

Our Amplify CLI version is 5.1.2

rahul-insight commented 2 years ago

Hello @Jordan-Nelson . Is there any update on this? This is blocking our release. Can you please provide your input.

Here is some more info. This is what we have in our build.gradle

    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled false
            shrinkResources false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

        }
    }
And the proguard-rule.pro contains:
-keep class com.amazonaws.** { *; }
-keep class com.amplifyframework.** { *; }
-keepnames class com.amazonaws.**
-keepnames class com.amazon.**
# Request handlers defined in request.handlers
-keep class com.amazonaws.services.**.*Handler
# The following are referenced but aren't required to run
-dontwarn com.fasterxml.jackson.**
-dontwarn org.apache.commons.logging.**
# Android 6.0 release removes support for the Apache HTTP client
-dontwarn org.apache.http.**
# The SDK has several references of Apache HTTP client
-dontwarn com.amazonaws.http.**
-dontwarn com.amazonaws.metrics.**
-dontwarn com.amazonaws.mobile.**
-dontwarn com.amazonaws.mobileconnectors.**

-keep class org.apache.commons.logging.**               { *; }
-keep class com.amazonaws.services.sqs.QueueUrlHandler  { *; }
-keep class com.amazonaws.javax.xml.transform.sax.*     { public *; }
-keep class com.amazonaws.javax.xml.stream.**           { *; }
-keep class com.amazonaws.services.**.model.*Exception* { *; }
-keep class org.codehaus.**                             { *; }
-keepattributes Signature,*Annotation*
Jordan-Nelson commented 2 years ago

@rahul-insight thanks for the info. I will attempt to reproduce this with the info above.

rahul-insight commented 2 years ago

@Jordan-Nelson While trying to debug this issue, I have a hunch that AmplifyAuthCognitois somehow getting obfuscated while building in "release mode". What I'm observing is on subsequent app openings Amplify.Auth.getCurrentUser() returns Null in release mode, but returns proper value in debug mode.

A little hack that I'm trying just for the purpose of debugging is making await on the function that adds Auth plugin (for that I've to temporarily make main async). Can you please suggest how to prevent AmplifyAuthCognito plugin from getting obfuscated in release build?

void main() **async** {
  WidgetsFlutterBinding.ensureInitialized();

  **await** initAmplify();
  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
      .then((_) {
    runApp(MaterialApp(
      home: App(),
      debugShowCheckedModeBanner: false,
    ));
  });
}
Future<void> initAmplify() async {
  // Add Pinpoint and Cognito Plugins
  final AmplifyAuthCognito authPlugin = AmplifyAuthCognito();
  final AmplifyAPI apiPlugin = AmplifyAPI();
  final AmplifyAnalyticsPinpoint analyticsPinpoint = AmplifyAnalyticsPinpoint();

  Amplify.addPlugins([authPlugin, apiPlugin, analyticsPinpoint]);
  // Once Plugins are added, configure Amplify
  // Note: Amplify can only be configured once.
  try {
    await Amplify.configure(amplifyconfig);
  } on AmplifyAlreadyConfiguredException {
    AppLogger.instance
        .e('Amplify was already configured. Was the app restarted?');
  } catch (e) {
    AppLogger.instance.e(e);
  }
}
fjnoyp commented 2 years ago

Hi @rahul-insight thanks for providing more information.

You mentioned the issue only happens on the actual physical device. But when you deploy your app from Android Studio to a physical device do you observe the same problem? Also you said that if you build an AAB from Android Studio there is no problem as well? So is your code actually working on physical devices in those 2 cases?

Can you provide more details on what is happening when Authentication fails when the user closes the app (sign out happens). What error message are you getting? What exactly is happening here?

You mentioned that the build kicks out some of the Amplify files, how do you know this and what files do you see as missing?

fjnoyp commented 2 years ago

Depending on your answers to the questions above, it might be easier for you to share your repo with us so we can try to reproduce on our side. It would also be helpful to know if this is just due to codemagic or if your issue arises in other deployment cases as well.

offlineprogrammer commented 2 years ago

Hi @rahul-insight

Following up on this if you can get back to us please with the details requested by @fjnoyp above

Regards Mo

rahul-insight commented 2 years ago

@offlineprogrammer To summarize our experience:

  1. The issue happens irrespective of Codemagic on every Android release build
  2. On closing the app on a physical device or emulator, while reopening the app Amplify.Auth.getCurrentUser() returned NULL
  3. We couldn't find any other solution so went with this workaround
void main() **async** {
  WidgetsFlutterBinding.ensureInitialized();

  **await** initAmplify();
  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
      .then((_) {
    runApp(MaterialApp(
      home: App(),
      debugShowCheckedModeBanner: false,
    ));
  });
}
Jordan-Nelson commented 2 years ago

@rahul-insight - Sorry for the lag in response here.

When you were experiencing this issue, did you have initAmplify in the same location, just without the await?

Without the await, there would be nothing preventing your app from calling amplify methods before configuration has finished (unless you have some logic elsewhere to prevent this). I would expect getCurrentUser() to throw an Exception if called before Amplify.configure() had completed, but it is possible it behaves differently in release mode. I would need to test the behavior in release mode. Either way, it would be expected to see issues if getCurrentUser() was called before Amplify.configure() had completed. It is possible you are only seeing the issues in release mode since the calls to configure and getCurrentUser would present a race condition.

Adding the await to ensure amplify is initialized before calling runApp is one way to ensure that no Amplify methods are called prior to configuration. There shouldn't be an issue with this. However, the way we recommend doing this in our docs is a little different. The way we typically recommend to do this is to call Amplify.configure() in a stateful widget and display some sort of a loading indicator in your app until it completes. Below is an example of that.

import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:flutter/material.dart';

import 'amplifyconfiguration.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  /// boolean to keep track of configuration state.
  bool _isConfigured = false;

  @override
  void initState() {
    super.initState();

    /// configure Amplify within initState.
    _configureAmplify();
  }

  void _configureAmplify() async {
    try {
      await Amplify.addPlugin(AmplifyAuthCognito());
      await Amplify.configure(amplifyconfig);
      // set _isConfigured to true after `Amplify.configure` completes.
      setState(() {
        _isConfigured = true;
      });
    } on Exception catch (e) {
      print('Error configuring Amplify: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      /// display a loading indicator until amplify is configured.
      home: _isConfigured
          ? const HomeWidget()
          : const Center(
              child: CircularProgressIndicator(),
            ),
    );
  }
}

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

  @override
  State<HomeWidget> createState() => _HomeWidgetState();
}

class _HomeWidgetState extends State<HomeWidget> {
  @override
  void initState() {
    try {
      /// HomeWidget will not display until Amplify is configured, so calling
      /// Amplify methods will be safe.
      final session = Amplify.Auth.getCurrentUser();
    } on Exception catch (e) {
      /// handle exception (direct user to login for example)
    }

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    /// display your app
  }
}
Jordan-Nelson commented 2 years ago

@rahul-insight - Based on the code sample and workaround that you provided, I think it is likely you are seeing a race condition that is only presenting itself in a release built. As mentioned above, Amplify.configure should either be awaited as you have done, or you should track the completion of this call to prevent other amplify methods from being called before it completes.

Please let me know if you have any questions. Thanks.

Jordan-Nelson commented 2 years ago

@rahul-insight - I am going to close this out since the way that you have worked around this is the suggested pattern. Please open a new issue if you are still experiencing any issues.