I'm working on an app that includes taking pictures and sending them to the backend. Nothing crazy, and everything seems to be good. Since we need to send information back to the server, I need implemented something such that the app verifies the connection token's validity every time the App's state goes from Paused or Exited to Resumed.
That being said, a friend of mine was testing the app and apparently taking pictures from his Samsung S20 FE (android 12) can crash the app. It happens at seemingly random times, so I've plugged his phone into Android Studio and go this error message:
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Error During Communication: Error occured while Communication with Server with StatusCode: 404
That's really weird because we don't try to send the pictures until a button is pressed and this happens right as the picture is taken. So I thought it might be my code that checks for lifecycle changes, since it does call the backend to check on the auth token, but it's surrounded with a .then and .onError which should catch the network error before it's propagated to the app.
It also bugs me that I've never been able to reproduce it with my OnePlus 6 (Android 11) or using a simulator. I'll be joining some code below to illustrate my functionalities. Any idea why I'm having this problem and how I can fix it?
Thanks in advance !
App lifecycle function
#override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
print("State changed to: $state");
switch(state){
case AppLifecycleState.resumed:
final LoginManager loginManager = locator<LoginManager>();
final userToken = await loginManager.getUserTokenFromStorage();
if (userToken != null) {
final Future<LoginResponse?> loginResponse = locator<LoginManager>().verifyToken(refreshToken);
loginResponse.then((value) {
if(value == null) {
_showTimeoutDialog(navigatorKey.currentContext!);
}
}).onError((error, stackTrace) {
_showTimeoutDialog(navigatorKey.currentContext!);
});
}
break;
case AppLifecycleState.inactive:
// TODO: Handle this case.
break;
case AppLifecycleState.paused:
// TODO: Handle this case.
break;
case AppLifecycleState.detached:
// TODO: Handle this case.
break;
}
}
Note that showTimeoutDialog shows a dialog and properly disconnects the user, no information is sent whatsoever.
Taking a picture function
Future<File?> _getPhotoFromCamera(bool isSelfie) async {
return ImagePicker()
.pickImage(
source: ImageSource.camera,
preferredCameraDevice:
isSelfie ? CameraDevice.front : CameraDevice.rear,
)
.then((value) => value != null ? File(value.path) : null);
}
Uses the flutter image_picker package
Related
I am using Revenuecat (purchases_flutter: ^3.7.0) plugin in my Flutter Android app. However I noticed, even if the (test) subscription has been ended the code shows it is still active.
I am using the following method to determine whether the subscription is active or not.
static isUserSubscribed() async {
bool _isSubscribed = false;
await Purchases.setDebugLogsEnabled(false);
await Purchases.setup(revenueCatId);
if (userId != null) {
await Purchases.setup(revenueCatId, appUserId: userId);
PurchaserInfo purchaserInfo = await Purchases.getPurchaserInfo();
if (purchaserInfo.entitlements.all["Ads Free"] != null &&
purchaserInfo.entitlements.all["Ads Free"].isActive != null &&
purchaserInfo.entitlements.all["Ads Free"].isActive) {
// it comes here even though the subscription has been expired
_isSubscribed = true;
}
return _isSubscribed;
}
I noticed, when I do test purchase and even after the subscription has been ended the above method returns true always. I even restarted the app and logged out and even clear the memory too.
If I go to revenucat.com and remove the user by searching the email address then it start returning false. My test subscription was expired in the morning around 9:00 AM but in the evening also it showing active in the app.
Any help will be appreciated
Im using connectivity package to track users connection changes. The idea is to pop up a warning page for connection loss when the ConnectivityResult is none (aka wifi and mobile is disconnected). But instead i get these results :
If the wifi is connected and you disconnect it, 50% of the time the warning pops up.
If you are on mobile and turn it off, the connectivity returns that user is still on ConnectivityResult.mobile not ConnectivityResult.none.
Tried to make a doublecheck with pinging google, but even that doesnt work as smooth as i expected it to be.
My code :
I have created seperated file with functions :
void trackNetworkStatus(BuildContext ctx) {
//check in case state is just opened
pingGoogle(ctx);
//add network listener
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
print("Network changed to $result");
//if user lost connection
if (result == ConnectivityResult.none) {
openNoInternetScreen(ctx);
}else{
//if user has connection, doublecheck
//mobile network is tricky on android
pingGoogle(ctx);
}
});
}
Future<void> pingGoogle(BuildContext ctx) async {
try {
//ping internet page
final result = await InternetAddress.lookup('google.com');
//if ping is successful
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print('connected');
}else{
print('not connected');
openNoInternetScreen(ctx);
}
} on SocketException catch (_) {
print('not connected');
openNoInternetScreen(ctx);
}
}
void openNoInternetScreen(BuildContext ctx) {
Navigator.push(
ctx,
MaterialPageRoute(builder: (context) => noInternetPage()),
);
}
because i am calling them on every apps init like this :
#override
void initState() {
super.initState();
//listen to netwoek changes
trackNetworkStatus(context);
}
which leads to problem that sometimes warning page pops up twice because, as i believe, the previous listener has not been stopped, but i can figure out how to fix it. The question is why connectivity package returns false callback on mobile. Ive tested on virtual Android API 23 and Samsung S9+, both share same results.
I gave my app to couple early testers and everything turned out just fine for them, even if some of them own Samsung devices too. Looks like there is no problem at all with the connectivity package, some of Samsung phones just act weird as they shouldnt. Looks like there will always be a black sheep in the whole community, sadly thats me and im the developer with this buggy device.
Havent found the fix for my device, but looks like the package is safe to go for the most of the devices in market.
The phone model, that is acting strange : SM-G965F.
I have implemented Sinch APIs in my app, but i am not able to " log out " or switch between 2 users on the same device. the "logged out" user keep receiving incomming call( but not able to answer ) even I use unregisterManagePush() method. I made a query on Sinch's support page. But the support team told me to read the docs again. But I did exactly what the docs says.this is my code:
if (MyFirebaseMessaging.sinchClient!=null) {
MyFirebaseMessaging.sinchClient.stopListeningOnActiveConnection();
MyFirebaseMessaging.sinchClient.unregisterManagedPush();
MyFirebaseMessaging.sinchClient.terminateGracefully();
MyFirebaseMessaging.sinchClient=null; }
UPDATE 1: CODE I HAVE TRIED
if (MyFirebaseMessaging.sinchClient!=null) {
MyFirebaseMessaging.sinchClient.stopListeningOnActiveConnection();
MyFirebaseMessaging.sinchClient.unregisterManagedPush();
MyFirebaseMessaging.sinchClient.terminateGracefully();
}
if (MyFirebaseMessaging.sinchClient!=null) {
MyFirebaseMessaging.sinchClient.stopListeningOnActiveConnection();
MyFirebaseMessaging.sinchClient.unregisterManagedPush();
MyFirebaseMessaging.sinchClient.terminate();
MyFirebaseMessaging.sinchClient=null; }
if (MyFirebaseMessaging.sinchClient!=null) {
MyFirebaseMessaging.sinchClient.stopListeningOnActiveConnection();
MyFirebaseMessaging.sinchClient.unregisterManagedPush();
MyFirebaseMessaging.sinchClient.terminate();
}
=> They are all dose not work at all. Sinch keep call the previous user who logged out.
After some back and forth I finally got this to work but I had to use version 0.2.0 because I followed the google guide presented in the Readme.
Anyway, Im struggling with handling what will happen when the oAuth token times out. Then it needs to open the browser again to log in or is there a background process available for this as it automatically redirects back to the app because the server remembers the user so there is no need for a new username/password input?
Im getting a refresh token like this :
if(mAuthService == null){
mAuthService = new AuthorizationService(context);
}
mAuthState.performActionWithFreshTokens(mAuthService, new AuthState.AuthStateAction() {
#Override public void execute(
String accessToken,
String idToken,
AuthorizationException ex) {
if (ex != null) {
return;
}
// Getting the access token...
}
});
Thats working fine but after the user is idle for some time it wont work. How to handle this properly?
Solution for my problem was this:
I changed to using offline_access for the token in the scope. Depending on the site/service you're login into if they accept it or not. For me it was accepted and will keep the user logged in for a long time and removes the need to re-login.
I have an app in which user authentificates in Office365 with AzureAD library for Android.
It works well, users can authentificate and work with the app. Unfortunately, after a while they start hitthing AuthenticationException with ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED as an error code.
I checked the source code of AzurelAD. The only place, which is throughing this issue is acquireTokenAfterValidation() method:
private AuthenticationResult acquireTokenAfterValidation(CallbackHandler callbackHandle,
final IWindowComponent activity, final boolean useDialog,
final AuthenticationRequest request) {
Logger.v(TAG, "Token request started");
// BROKER flow intercepts here
// cache and refresh call happens through the authenticator service
if (mBrokerProxy.canSwitchToBroker()
&& mBrokerProxy.verifyUser(request.getLoginHint(),
request.getUserId())) {
.......
Logger.v(TAG, "Token is not returned from backgroud call");
if (!request.isSilent() && callbackHandle.callback != null && activity != null) {
....
} else {
// User does not want to launch activity
String msg = "Prompt is not allowed and failed to get token:";
Logger.e(TAG, msg, "", ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED);
callbackHandle.onError(new AuthenticationException(
ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED, msg));
}
// It will start activity if callback is provided. Return null here.
return null;
} else {
return localFlow(callbackHandle, activity, useDialog, request);
}
}
My source code:
authenticator.getAccessTokenSilentSync(getMailService());
public class Authenticator {
..............
public String getAccessTokenSilentSync(ServiceInfo serviceInfo) {
throwIfNotInitialized();
return getAuthenticationResultSilentSync(serviceInfo).getAccessToken();
}
private AuthenticationResult getAuthenticationResultSilentSync(ServiceInfo serviceInfo) {
try {
return authenticationContext.acquireTokenSilentSync(
serviceInfo.ServiceResourceId,
Client.ID,
userIdentity.getAdUserId());
} catch (AuthenticationException ex) {
// HERE THE EXCEPTION IS HANDLED.
}
}
..............
}
Stacktrace I'm getting:
<package name>.data_access.error_handler.AuthenticationExceptionWithServiceInfo: Refresh token is failed and prompt is not allowed
at com.microsoft.aad.adal.AuthenticationContext.localFlow(AuthenticationContext.java:1294)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenAfterValidation(AuthenticationContext.java:1229)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenLocalCall(AuthenticationContext.java:1123)
at com.microsoft.aad.adal.AuthenticationContext.refreshToken(AuthenticationContext.java:1609)
at com.microsoft.aad.adal.AuthenticationContext.localFlow(AuthenticationContext.java:1261)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenAfterValidation(AuthenticationContext.java:1229)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenLocalCall(AuthenticationContext.java:1123)
at com.microsoft.aad.adal.AuthenticationContext.refreshToken(AuthenticationContext.java:1609)
at com.microsoft.aad.adal.AuthenticationContext.localFlow(AuthenticationContext.java:1261)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenAfterValidation(AuthenticationContext.java:1229)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenLocalCall(AuthenticationContext.java:1123)
at com.microsoft.aad.adal.AuthenticationContext.access$600(AuthenticationContext.java:58)
at com.microsoft.aad.adal.AuthenticationContext$4.call(AuthenticationContext.java:1072)
at com.microsoft.aad.adal.AuthenticationContext$4.call(AuthenticationContext.java:1067)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
Version of AzureAD library I'm using: 1.1.7 (to prevent blaming too old version - I've checked the changelist since from 1.1.7 to 1.1.11 and haven't found anything related to question)
Problem: Right now, I'm treating this error, as a signal to through the user to the login screen. In my opinion, it leads to a poor experience for the user. The fact that it happens very often and affects many users make it even worse.
Question: Is there anything I can do different to avoid this AuthenticationException or workaround it somehow (i.e. avoid user enters credentials once again).
Have you verified that AuthenticationContext.acquireTokenSilentSync() is truly the method that you wish to invoke?
The docs indicate that this method will explicitly not show a prompt. From the docs:
This is sync function. It will first look at the cache and automatically checks for the token expiration. Additionally, if no suitable access token is found in the cache, but refresh token is available, the function will use the refresh token automatically. This method will not show UI for the user. If prompt is needed, the method will return an exception.
The refresh token you are issued should last two weeks per this AAD book. After the refresh token expires users are expected to reauthenticate. Can you inspect net traffic with Fiddler or Charles and inspect the expiry of the tokens? If you can verify that the tokens are failing to refresh before their expiry it may indicate a bug in the AD library.
To clarify the difference in methods on AuthenticationContext - there are two categories of methods: "silent" methods (which will not present a dialog to user in the event that they need to reauthenticate), and non-silent. Non-silent methods will, in the event of requiring reauthentication (or consent) from the user, start a new Activity containing the AAD login. At that point the authentication flow is restarted.
Additionally, if you make changes to your application's registration in Azure such as adding new permission scopes your users will be required to re-grant consent for the application to continue to handle their data.
This is because you need to refresh your token and implement this in your code so the user won't be prompt to login every time the access token is expired. please check out how to implement refresh token here:
https://msdn.microsoft.com/en-us/library/azure/dn645538.aspx
Hope this helps.