For my project I'm using Retrofit for my API calls. For authentication I'm using JWT tokens. For storage and refresh flow of that token I'm using Google's AccountManager.
The ApiService class is an singleton which I build as follows:
private ApiService(AccountManager accountManager) {
mAccountManager = accountManager;
build();
}
public static synchronized ApiService getInstance(AccountManager accountManager) {
if (mInstance == null) {
mInstance = new ApiService(accountManager);
}
return mInstance;
}
In this class I'm building the OkHttp3 client and the Retrofit service.
Okhttp3 has a nice method when the server responds with a challenge so I set this up like this:
OkHttpClient client = new OkHttpClient.Builder()
.authenticator(new ApiAuthenticator(mAccountManager))
As you can see I'm setting a custom implementation of the Okhttp3 Authenticator which looks like this:
/**
* 2017 App-vise.
* Created by daangeurts on 31/07/2017.
*/
public class ApiAuthenticator implements Authenticator {
private static final String TAG = "ApiAuthenticator";
private AccountManager accountManager;
ApiAuthenticator(AccountManager accountManager) {
this.accountManager = accountManager;
}
private static int responseCount(Response response) {
int result = 1;
while ((response = response.priorResponse()) != null) {
result++;
}
return result;
}
/**
* Returns a request that includes a credential to satisfy an authentication challenge in {@code
* response}. Returns null if the challenge cannot be satisfied.
*
* @param route
* @param response
*/
@Nullable
@Override
public Request authenticate(@NonNull Route route, @NonNull Response response) throws IOException {
if (response.request().url().encodedPath().startsWith("login_check") || response.request().url().encodedPath().startsWith("token"))
return null;
if (responseCount(response) >= 2) {
// If both the original call and the call with refreshed token failed,
// it will probably keep failing, so don't try again.
return null;
}
for (Challenge challenge : response.challenges()) {
if (challenge.scheme().equals("Bearer")) {
Account[] accounts = accountManager.getAccountsByType(AuthConstants.ACCOUNT_TYPE);
if (accounts.length != 0) {
String oldToken = accountManager.peekAuthToken(accounts[0], AuthConstants.AUTHTOKEN_TYPE_FULL_ACCESS);
if (oldToken != null) {
accountManager.invalidateAuthToken(AuthConstants.ACCOUNT_TYPE, oldToken);
}
try {
String token = accountManager.blockingGetAuthToken(accounts[0], AuthConstants.AUTHTOKEN_TYPE_FULL_ACCESS, false);
if (token == null) {
accountManager.removeAccount(accounts[0], null, null);
}
if (token != null) {
Request.Builder builder = response.request().newBuilder();
return builder.header("Authorization", "Bearer " + token).build();
}
} catch (OperationCanceledException | AuthenticatorException e) {
e.printStackTrace();
Log.d(TAG, e.getLocalizedMessage());
}
}
}
}
return null;
}
}
My repository for some function looks like this:
public class ApiCheckinRepository implements CheckinRepository {
private AccountManager accountManager;
public ApiCheckinRepository(AccountManager accountManager) {
this.accountManager = accountManager;
}
@Override
public Observable<Response<Message>> checkin(QrCode qrCode) {
return ApiService.getInstance(accountManager).checkin(qrCode);
}
}
But now I'm getting memory leaks caused by the accountManager dependency and I don't see the solution anymore...
Hope it will help you for resolve your problem :-
public class AccountLeakHandler {
public static AccountManagerFuture<Bundle> safeGetAuthToken(AccountManager accountManager,
Account account,
String authTokenType,
Bundle options,
Activity activity,
AccountManagerCallback<Bundle> callback,
Handler handler) {
return accountManager.getAuthToken(
account,
authTokenType,
options,
null,
new ResolveLeakAccountManagerCallback(callback, activity),
handler);
}
public static class ResolveLeakAccountManagerCallback implements AccountManagerCallback<Bundle> {
private AccountManagerCallback<Bundle> originalCallback = null;
private Activity originalActivity = null;
public ResolveLeakAccountManagerCallback(AccountManagerCallback<Bundle> callback, Activity activity) {
originalCallback = callback;
originalActivity = activity;
}
@Override
public void run(AccountManagerFuture<Bundle> future) {
Bundle bundle = null;
try {
bundle = future.getResult();
} catch (Exception e) {
// error happen
}
if (bundle == null) {
callAndClear(future);
} else {
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
if (intent != null) {
if (originalActivity != null) {
try {
//make FutureTask callback again
new RInstance(FutureTask.class, future).setValue("state", 0);
} catch (Exception e) {}
originalActivity.startActivity(intent);
} else {
callAndClear(future);
}
} else {
callAndClear(future);
}
}
}
private void callAndClear(AccountManagerFuture<Bundle> future) {
if (originalCallback != null) {
originalCallback.run(future);
originalCallback = null;
}
originalActivity = null;
}
}
}
for more info visit below link:-