What service are you using?
Cognito / Federated Identities / AWSMobileClient
Is the issue limited to Simulators / Actual Devices?
Feature request
Can your problem be resolved if you bump to a higher version of SDK?
Hopefully! Feature request
Is this problem related to specific Android/OS version?
N/A
Can you give us steps to reproduce with a minimal, complete, and verifiable example? Please include any specific network conditions that might be required to reproduce the problem.
My feature request is to remove the requirement that the context in AWSMobileClient.getInstance().initialize(Context context, AWSStartupHandler awsStartupHandler) be an Activity.
Currently, the AWSMobileClient.getInstance().initialize(Context context, AWSStartupHandler awsStartupHandler).execute() requires that context be an Activity. This requirement is necessary to execute this.resumeSession((Activity) context, startupAuthResultHandler); in AWSMobileClient->fetchCognitoIdentity . resumeSession calls IdentityManager->resumeSession, which refreshes the user identity and is loosely coupled with the UI by restarting the callingActivity after the user is refreshed.
I am hoping to instantiate AWSMobileClient from a BroadcastReceiver (the application is not already running in the background), like so:
public class UserPresentBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context receiverContext, Intent intent) {
String SOME_INTENT = Intent.ACTION_USER_PRESENT
if(intent.getAction().equals(SOME_INTENT)){
//Either receiverContext or receiverContext.getApplicationContext()
AWSMobileClient.getInstance().initialize(receiverContext.getApplicationContext(), new AWSStartupHandler() {
@Override
public void onComplete(AWSStartupResult awsStartupResult) {
//Access AWS resources as an authorized user, assuming that this device has cached credentials.
}
}).execute();
}
}
}
The coupling with the UI for instantiating AWSMobileClient prohibits (as far as I can tell) the use of non-activity contexts from using the conveniences of AWSMobileClient. Allowing AWSMobileClient to be instantiated on an Application context would be useful for accessing AWS resources from background services, where the user is persistently logged in on the device.
Thanks, @kvasukib (for the stackoverflow comment, as well). Could I please append a request for an isInitialized() flag, as well? I don't believe that capability exists already.
Thank you @ajhool for the suggestion! By isInitialized() flag, do you mean to check if the AWSMobileClient already created the credentials provider and fetched the identity id and not to repeat it again? Do you have any other requirements in isInitialized() or in general in AWSMobileClient?
Yes, by isInitialized() flag, I mean to check if AWSMobileClient already created the credentials provider and fetched the identity id and not to repeat it again. However, I was assuming that initialization should only occur once during the Application lifecycle. Maybe it should occur more often, in which case the isInitialized() flag is probably unnecessary.
I think we have covered my current needs for AWSMobileClient and I will just restate them, here:
BroadcastReceiver->onReceive(Context context, Intent intent). context is a but can call context.getApplicationContext()public boolean isInitialized() flag that returns true if initialize has already been called. Certainly, this would be necessary if it is harmful to call initialize twice during an application lifecycle, but I don't think calling initialize twice is harmful. It might serve as a small optimization.Thank you @ajhool for the suggestion. We will update his thread when we add these features to AWSMobileClient. Meanwhile, you are welcome to submit a pull request or update this issue with more suggestions!
@kvasukib There is fairly heavily coupling between the Activity and the underlying implementation of AWSMobileClient (including IdentityManager, SignInManager, and several other classes). There are two apparent reasons to use an Activity instead of a Context or Application:
callingActivity.runOnUiThread(...) to execute the callback on the UI thread. Downstream coupling might be an easier place to start
Current:
IdentityManager->resumeSession(Activity callingActivity...)
SignInManager.SignInProviderResultAdapter wraps the SignInProviderResultHandler in ThreadUtils.runOnUiThread calls.
Potential Alternative (in addition to the current resumeSession function):
IdentityManager->resumeSessionInBackground(Context callingContext...)
SignInManager.SignInProviderResultAdapter wraps the SignInProviderResultHandler in ThreadUtils.runOnUiThread calls iff the activity is not null, otherwise it simply calls the Handler calls
Simple use-case and ideal flow for resuming the session in the background:
I would be happy to work on a PR but this feature will touch a lot of existing classes, so I would appreciate some guidance
(PS. is there already an easy way to resume a session in the background -- that won't interfere with the AWSMobileClient signup/login UI flow -- that I'm missing?)
Thank you @ajhool for the suggestion and the detailed analysis. The resumeSession already executes the task in the background thread. Ideally an overloaded version of resumeSession that takes in a context would be ideal. We will update this thread once we have a fix for this.
Is there already an easy way to resume a session in the background -- that won't interfere with the AWSMobileClient signup/login UI flow -- that I'm missing?
As I said, resumeSession already executes the task in the background through the ExecutorService. Are you looking to use resumeSession separately without having to configure the sign-in flows?
If so, you could do the following:
IdentityManager idm = new IdentityManager(getApplicationContext(), new AWSConfiguration(getApplicationContext());
IdentityManager.setDefaultIdentityManager(idm);
IdentityManager.getDefaultIdentityManager().resumeSession(YourActivity.this, resumeSessionCallback); // resumeSessionCallback would be an object of a class that implements StartupAuthResultHandler interface.
To clarify, by "Is there already an easy way to resume a session in the background" I mean when the app, itself, is running as a background process (eg. the user is taking a selfie in snapchat and MyApp is responding to an Intent in the background -- in my case, using a BroadcastReceiver -- but it could also be a Service).
So, in your code suggestion, there would be no YourActivity.this to access. The YourActivity.this object is passed from resumeSession -> signInManager.refreshCredentialsWithProvider -> new SignInProviderResultAdapter(activity, signInProviderResultHandler), where the result adapter uses the activity object to execute the result handler on the activity's UI thread.
I don't believe you can spoof an Activity class from an app that is running in the background and that attempting to call runOnUIThread when an app is running in the background will throw an error. I can double check this assumption with a test app.
I have just ran into this same issue, one hack (and I use hack in the most derogatory way) to fix the issue is to launch an empty activity and immediately close the activity, then wait for the activity to launch and obtain a reference to it, then use that reference as the context you need.
There are quite a few reasons you do not want to use this hack:
I have found a work around for the final problem, but it is still not a good solution.
Is there a reason the IdentityManager requires being run on the ui thread?
Thanks for the tip @NullandKale . I'm finding some untested success after subclassing IdentityManager into BackgroundIdentityManager. The only modification in the subclass is to use a bgResumeSession(final Context callingContext, final StartupAuthResultHandler startupAuthResultHandler, final long minimumDelay) function, which is almost identical to the resumeSession method, but comments out a few things and passes a null activity into the SignInManager call. I put this together last night so there is still room for improvement (it doesn't handle unauthenticated users)
Interestingly, the actual call the runOnTheUIThread in SignInManager does not use the Activity's runOnUIThread function call, it uses ThreadUtils.runOnUIThread call. So, as far as I can tell, the Activity can safely be null in this use-case. I have not run the tests or tested the usage, but it does allow me to update background resources.
public class BGIdentityManager extends IdentityManager {
static String LOG_TAG = "BGIdentityManager";
public BGIdentityManager(Context context, AWSConfiguration awsConfiguration) {
super(context, awsConfiguration);
}
/**
* This should be called from your app's splash activity upon start-up. If the user was previously
* signed in, this will attempt to refresh their identity using the previously sign-ed in provider.
* If the user was not previously signed in or their identity could not be refreshed with the
* previously signed in provider and sign-in is optional, it will attempt to obtain an unauthenticated (guest)
* identity.
*
* @param startupAuthResultHandler a handler for returning results.
* @param minimumDelay the minimum delay to wait before returning the sign-in result.
*/
public void bgResumeSession(final Context callingContext,
final StartupAuthResultHandler startupAuthResultHandler,
final long minimumDelay) {
Log.d(LOG_TAG, "Resume session called.");
// Originally, resumeSession uses an executorService. Can add that here, too.
Log.d(LOG_TAG, "Starting up authentication...");
final SignInManager signInManager = SignInManager.getInstance(
callingContext.getApplicationContext());
if (signInManager == null) {
throw new IllegalStateException("You cannot pass null for identityManager.");
}
final SignInProvider provider = signInManager.getPreviouslySignedInProvider();
// if the user was already previously signed-in to a provider.
if (provider != null) {
Log.d(LOG_TAG, "Refreshing credentials with identity provider " + provider.getDisplayName());
// asynchronously handle refreshing credentials and call our handler.
signInManager.refreshCredentialsWithProvider(null,
provider, new SignInProviderResultHandler() {
@Override
public void onSuccess(final IdentityProvider provider) {
// The sign-in manager is no longer needed once signed in.
SignInManager.dispose();
Log.d(LOG_TAG, "Successfully got credentials from identity provider '"
+ provider.getDisplayName());
startupAuthResultHandler.onComplete(new StartupAuthResult(BGIdentityManager.this, null));
}
@Override
public void onCancel(final IdentityProvider provider) {
// Should never happen.
//Log.wtf(LOG_TAG, "Cancel can't happen when handling a previously signed-in user.");
}
@Override
public void onError(final IdentityProvider provider, final Exception ex) {
Log.e(LOG_TAG,
String.format("Cognito credentials refresh with %s provider failed. Error: %s",
provider.getDisplayName(), ex.getMessage()), ex);
if (ex instanceof AuthException) {
//handleUnauthenticated(callingActivity, startupAuthResultHandler,
// (AuthException) ex);
} else {
//handleUnauthenticated(callingActivity, startupAuthResultHandler,
// new AuthException(provider, ex));
}
}
});
} else {
//handleUnauthenticated(callingActivity, startupAuthResultHandler, null);
}
}
}
and I instantiate inside my BroadcastReceiver via:
if(intent.getAction().equals(Intent.ACTION_USER_PRESENT)){
BGIdentityManager idm = new BGIdentityManager(context.getApplicationContext(), new AWSConfiguration(context.getApplicationContext()));
IdentityManager.setDefaultIdentityManager(idm);
IdentityManager.getDefaultIdentityManager().addSignInProvider(CognitoUserPoolsSignInProvider.class);
IdentityManager.getDefaultIdentityManager().addSignInProvider(FacebookSignInProvider.class);
((BGIdentityManager) IdentityManager.getDefaultIdentityManager()).bgResumeSession(context, new StartupAuthResultHandler() {
@Override
public void onComplete(StartupAuthResult authResults) {
Log.d("UserPresentBroadcast", "Successfully resumed session.");
//ACCESS AWS DYNAMODB RESOURCES HERE.
// AWSProvider.getInstance().getUserStatusProvider().postUpdateStatus();
Toast.makeText(context, "Successfully resumed session.", Toast.LENGTH_LONG).show();
}
}, 0);
}
Hi @ajhool and @NullandKale, Thank you for looking into this issue. Ideally IdentityManager should be having a resumeSession and login methods and flow which takes in context and doesn't rely on an activity. We couldn't modify the methods as it has the risk of breaking the customers who use it with activity. However we are considering the option to see if we can have a overloaded method that accepts context as a parameter and either duplicates or converges into the workflow.
Also thank you for pointing out the fact that SignInManager uses the ThreadUtils to run on the UI thread. We are looking to avoid the tight coupling between the activity and making the callback for all cases {Fb, Google and UserPools} Providers.
@kvasukib
Google has an interesting approach to this problem that utilizes a ContentProvider to initialize the Firebase client without any code. ContentProviders are declared in the manifest and initialized before the application (priority order) and provides a Context, access to ActivityLifeCycleCallbacks. Manifest merging would also allow this sdk to initialize the AWSMobileClient while maintaining the current awsconfiguration.json flow.
https://firebase.googleblog.com/2016/12/how-does-firebase-initialize-on-android.html
is there any timeline for this fix?
this AWSMobileClient causes memory leaks. is there a way to avoid them. thank you.
Hi @ajhool @NullandKale @mjsachin @pedrovarela86 ,
We have launched a new AWSMobileClient in v2.8.+ , which does not require an Activity context.
A full details available in the Release notes and Documentation
Hi,
We are closing this issue because there has been no activity. Please feel free to open a new issue if the problem persists. We ask this because closed issues are not actively monitored.
Thanks
Most helpful comment
is there any timeline for this fix?