I'm using android lollipop in PAX A920 device (SDK Version 22). I get a warning message like this when build:
NetworkInfo in android.net has been deprecated
WifiConfiguration in android.net has been deprecated
I'm confused because I saw an example of sdk 22 in C:\Users\{YOUR_ACCOUNT}\AppData\Local\Android\Sdk\sources\android-22\com\android\connectivitymanagertestto access wifi using that method, but why does it appear deprecated?
What method is the same to replace the deprecated methods?
public boolean isWifiConnected() {
boolean isWifiConnected = false;
try {
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (networkInfo != null) {
isWifiConnected = networkInfo.getState() == CONNECTED;
}
Log.i(TAG, "wifi adapter is connected? " + isWifiConnected);
} catch (Exception ex) {
ex.printStackTrace();
}
return isWifiConnected;
}
public void removeNetwork() {
List<WifiConfiguration> wifiCfgList = wifiManager.getConfiguredNetworks();
if (wifiCfgList.size() > 0) {
for (WifiConfiguration item : wifiCfgList) {
if (item != null) {
wifiManager.removeNetwork(item.networkId);
wifiManager.saveConfiguration();
}
}
}
}
Thanks for point out.
Deprecated classes were repaced with ConnectivityManager system service and NetworkCallbacks: https://developer.android.com/training/monitoring-device-state/connectivity-status-type
Even though the official code example shows how to get NetworkInfo from ConnectivityManager there is a highlighted note:
Here is a sample code of how you could get the current network state and also receive updates as time goes by. This is a partially stripped-down solution that I would use in production. If you connect it with RxJava or RxKotlin you could create an observable that will hold network state and that will be updated when overridden method of NetworkCallback get called.
Note regarding Java: public class-level variables are made public just for brevity. I'd instead create a few getters for these to access the value behind these variables.
Feel free to ask questions.
Java
class NetworkReachabilityService {
public NetworkType networkType;
public NetworkState networkState = NetworkState.Unavailable;
private ConnectivityManager connectivityManager;
private ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
#Override
public void onAvailable(#NonNull Network network) {
super.onAvailable(network);
updateAvailability(connectivityManager.getNetworkCapabilities(network));
}
#Override
public void onLosing(#NonNull Network network, int maxMsToLive) {
super.onLosing(network, maxMsToLive);
networkState = NetworkState.Losing;
}
#Override
public void onLost(#NonNull Network network) {
super.onLost(network);
networkState = NetworkState.Lost;
}
#Override
public void onUnavailable() {
super.onUnavailable();
networkState = NetworkState.Unavailable;
}
#Override
public void onCapabilitiesChanged(#NonNull Network network, #NonNull NetworkCapabilities networkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities);
updateAvailability(networkCapabilities);
}
};
public NetworkReachabilityService(Context context) {
connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
private void updateAvailability(NetworkCapabilities networkCapabilities) {
if (networkCapabilities == null) {
networkState = NetworkState.Unavailable;
return;
}
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
networkType = NetworkType.CELL;
} else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
networkType = NetworkType.WiFi;
}
networkState = NetworkState.Available;
}
public void resumeListeningNetworkChanges() {
pauseListeningNetworkChanges();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
connectivityManager.registerDefaultNetworkCallback(networkCallback);
} else {
connectivityManager.registerNetworkCallback(
new NetworkRequest.Builder().build(),
networkCallback
);
}
}
public void pauseListeningNetworkChanges() {
try {
connectivityManager.unregisterNetworkCallback(networkCallback);
} catch (IllegalArgumentException exception) {
// Usually happens only once if: "NetworkCallback was not registered"
}
}
private enum NetworkState {
Available, Unavailable, Connecting, Losing, Lost
}
private enum NetworkType {
WiFi, CELL, OTHER
}
}
Kotlin
sealed class NetworkState {
data class Available(val type: NetworkType) : NetworkState()
object Unavailable : NetworkState()
object Connecting : NetworkState()
object Losing : NetworkState()
object Lost : NetworkState()
}
sealed class NetworkType {
object WiFi : NetworkType()
object CELL : NetworkType()
object OTHER : NetworkType()
}
class NetworkReachabilityService private constructor(context: Context) {
private val connectivityManager: ConnectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
// There are more functions to override!
override fun onLost(network: Network) {
super.onLost(network)
networkState = NetworkState.Lost
}
override fun onUnavailable() {
super.onUnavailable()
networkState = NetworkState.Unavailable
}
override fun onLosing(network: Network, maxMsToLive: Int) {
super.onLosing(network, maxMsToLive)
networkState = NetworkState.Losing
}
override fun onAvailable(network: Network) {
super.onAvailable(network)
updateAvailability(connectivityManager.getNetworkCapabilities(network))
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
super.onCapabilitiesChanged(network, networkCapabilities)
updateAvailability(networkCapabilities)
}
}
var networkState: NetworkState = NetworkState.Unavailable
private set
private fun updateAvailability(networkCapabilities: NetworkCapabilities?) {
if (networkCapabilities == null) {
networkState = NetworkState.Unavailable
return
}
var networkType: NetworkType = NetworkType.OTHER
if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
networkType = NetworkType.CELL
}
if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) {
networkType = NetworkType.WiFi
}
networkState = NetworkState.Available(networkType)
}
fun pauseListeningNetworkChanges() {
try {
connectivityManager.unregisterNetworkCallback(networkCallback)
} catch (e: IllegalArgumentException) {
// Usually happens only once if: "NetworkCallback was not registered"
}
}
fun resumeListeningNetworkChanges() {
pauseListeningNetworkChanges()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
connectivityManager.registerDefaultNetworkCallback(networkCallback)
} else {
connectivityManager.registerNetworkCallback(
NetworkRequest.Builder().build(),
networkCallback
)
}
}
}
To start receiving network state updates call resumeListeningNetworkChanges and to stop pauseListeningNetworkChanges respectively.
Update: how to switch between deprecated and new API
Note that even when you use this solution you will anyway have a message that the certain code you use is deprecated! It is completely fine and is not considered as an error as long as you provide an implementation that can switch between new API and old, deprecated API.
Here is an approximate solution. Since the new classes were added in API level 29 we must use Build.VERSION_CODES.Q because it is an integer with the value 29.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Use new API here
} else {
// Use old API here
}
Currently upgrading play framework to 2.7.* I'm getting an error response due to deprecation in the security authenticator class of HTTP.Context class
The application was on Play 2.6.* and auth was working as designed. If I roll back to 2.6.* the authentication works well. Essentially I'm hoping to return the auth token as a String.
#Override
public String getUsername(Http.Request ctx) {
Optional token = getTokenFromHeader(ctx);
if(token.isPresent()){
UserAccount userAccount = UserAccount.find.query().where().eq("authtoken",token.toString()).findOne();
if (userAccount != null){
//ctx.args.put("userAccount", userAccount);
//String resp = Optional.<String>ofNullable(null).orElse(orelesMethod());
String resp = Optional.<String>ofNullable(null).orElse(userAccount.authtoken);
return resp;
}
}
return null;
}
#Override
public Result onUnauthorized(Http.Request ctx) {
Logger.info("onUnauthorized");
ObjectNode result = Json.newObject();
result.put("error","Unauthorized, Please login");
return status(401,result);
}
private Optional getTokenFromHeader(Http.Request ctx) {
return ctx.header("X-AUTH-TOKEN");
}
}
Original Code is as below
public class Secured extends Security.Authenticator{
#Override
public String getUsername(Http.Context ctx) {
String token = getTokenFromHeader(ctx);
if(token != null){
UserAccount userAccount = UserAccount.find.query().where().eq("authtoken",token).findOne();
if (userAccount != null){
ctx.args.put("userAccount", userAccount);
return userAccount.authtoken;
}
}
return null;
}
#Override
public Result onUnauthorized(Http.Context ctx) {
Logger.info("onUnauthorized");
ObjectNode result = Json.newObject();
result.put("error","Unauthorized, Please login");
return status(401,result);
}
private String getTokenFromHeader(Http.Context ctx) {
String[] authTokenHeaderValues = ctx.request().headers().get("X-AUTH-TOKEN");
if ((authTokenHeaderValues != null) && (authTokenHeaderValues.length == 1) && (authTokenHeaderValues[0] != null)) {
return authTokenHeaderValues[0];
}
return null;
}
}
Error response
return type java.lang.String is not compatible with java.util.Optional<java.lang.String>
Play 2.7 has some changes in Security.Authenticator class. Now it has two methods named getUsername.
You override method with Request param, so you should return Optional not String.
Take a look on Authenticator code:
/**
* Handles authentication.
*/
public static class Authenticator extends Results {
#Deprecated
public String getUsername(Context ctx) {
return ctx.session().get("username");
}
// You override this method
public Optional<String> getUsername(Request req) {
return req.session().getOptional("username");
}
...
}
How to start LoginActivity from the interceptor(non-activity class)? I have tried the code (Interceptor) below but not working for me.
Interceptor
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + auth_token_string)
.build();
Response response = chain.proceed(newRequest);
Log.d("MyApp", "Code : "+response.code());
if (response.code() == 401){
Intent intent = new Intent(SplashActivity.getContextOfApplication(), LoginActivity.class);
startActivity(intent);
finish(); //Not working
return response;
}
return chain.proceed(newRequest);
}
}).build();
This is the current solution I'm using, is there any better solution than this? This solution has to keep repeat on every api call.
MainActivity
call.enqueue(new Callback<Token>() {
#Override
public void onResponse(Call<Token> call, Response<Token> response) {
if(response.isSuccessful())
{
//success
}
else
{
Intent intent = new Intent(MainActivity.this.getApplicationContext(), LoginActivity.class);
startActivity(intent);
finish();
}
}
#Override
public void onFailure(Call<Token> call, Throwable t) {
}
});
Personally, I would like to suggest using event bus pattern here. You can use greenrobot implementation or whatever you want, since it's more about an architecture approach rather than concrete implementation.
Create event model
public class UnauthorizedEvent {
private static final UnauthorizedEvent INSTANCE = new UnauthorizedEvent();
public static UnauthorizedEvent instance() {
return INSTANCE;
}
private UnauthorizedEvent() {
}
}
Implement custom Interceptor which disptaches event about unauthorized reqeusts
class UnauthorizedInterceptor implements Interceptor {
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if (response.code() == 401) {
EventBus.getDefault().post(UnauthorizedEvent.instance());
}
return response;
}
}
Create BaseActivity class which handles UnauthorizedEvent
public class BaseActivity extends Activity {
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
#Subscribe
public final void onUnauthorizedEvent(UnauthorizedEvent e) {
handleUnauthorizedEvent();
}
protected void handleUnauthorizedEvent() {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
}
Prevent launching LoginActivity from LoginActivity
public class LoginActivty extends BaseActivity {
#Override
protected void handleUnauthorizedEvent() {
//Don't handle unauthorized event
}
}
Another approach is to not extending BaseActivity here.
Register your interceptor
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new UnauthorizedInterceptor())
.build();
Pros:
Loose coupling between components
Easaly extending the logic by overriding handleUnauthorizedEvent
No need to rewrite code to use new types of callbacks
Reduce human factor about making mistakes (using Callback instead of BaseCallback)
Cons:
EventBus pattern makes debugging more complicated
One more dependency or own implementation which brings new code to the project
Also, please take into account, that this example doesn't cover multithreading issues. It solves your problem of handling unauthorized requests. Thus, if two requests receive 401 than it is possible that 2 instances of LoginActivity is started.
Consider introducing a custom implementation of retrofit2.Callback interface, e.g. BaseCallback:
public abstract class BaseCallback<T> implements Callback<T> {
private final Context context;
public BaseCallback(Context context) {
this.context = context;
}
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.code() == 401) {
// launch login activity using `this.context`
} else {
onSuccess(response.body());
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
}
abstract void onSuccess(T response);
}
Now, from the caller site you should change new Callback<Token> with new BaseCallback<Token>:
call.enqueue(new BaseCallback<Token>(context) {
#Override
void onSuccess(Token response) {
// do something with token
}
});
Although, this approach doesn't fulfil your following statement:
so I don't have to keep repeat the same code over again for each api call
nevertheless, I cannot come up with a better approach.
The simplest way is to inject activity context in Interceptor instance.
If you are using some DI tools, like Dagger2 or Toothpick it will be very simple. I recommend to use toothpick)
https://github.com/stephanenicolas/toothpick
The most code near by will be in kotlin, because it's my boilerplate code. Those thinks, that you are need to solve your problem i will write in Java.
The solution will be like this:
#Qualifier
annotation class BackendUrl
class ActivityModule(activity: BaseActivity): Module() {
init {
bind(Activity::class.java).toInstance(activity)
}
}
class NetworkModule: Module() {
init {
bind(String::class.java).withName(BackendUrl::class.java).toInstance(Constants.URL)
bind(Gson::class.java).toInstance(GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create())
bind(CacheHolder::class.java).toProvider(CacheProvider::class.java).singletonInScope()
bind(OkHttpClient::class.java).toProvider(OkHttpProvider::class.java).instancesInScope()
bind(BackendApi::class.java).toProvider(BackendApiProvider::class.java).instancesInScope()
bind(RedirectInterceptor::class.java).to(RedirectInterceptor::class.java)
}
}
Than you need to create Providers for injection dependency
class BackendApiProvider #Inject constructor(
private val okHttpClient: OkHttpClient,
private val gson: Gson,
#BackendUrl private val serverPath: String
) : Provider<BackendApi> {
override fun get() =
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.baseUrl(serverPath)
.build()
.create(BackendApi::class.java)
}
And your redirect interceptor:
public class RedirectInterceptor implements Interceptor {
private final Context context;
#Inject
public RedirectInterceptor(Activity context) {
this.context = context;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.build();
Response response = chain.proceed(newRequest);
Log.d("MyApp", "Code : "+response.code());
if (response.code() == 401){
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
((Activity) context).finish();
return response;
}
return chain.proceed(newRequest);
}
}
Oh, yes. For Authorization header will be better to create new instance of another interceptor
class HeaderInterceptor(private val token: String?) : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val newRequest = request.newBuilder()
Log.d(TAG, "token: $token")
if (token != null && token.isNotBlank()) {
newRequest.addHeader("Authorization", "Bearer $token")
}
return chain.proceed(newRequest.build())
}
companion object {
private val TAG = HeaderInterceptor::class.java.toString()
}
}
And your OkhttpProvder
class OkHttpProvider #Inject constructor(cacheHolder: CacheHolder, prefs: IPreferences, redirectInterceptor: RedirectInterceptor) : Provider<OkHttpClient> {
private val client: OkHttpClient
init {
val builder = OkHttpClient.Builder()
builder
.addNetworkInterceptor(redirectInterceptor)
.addNetworkInterceptor(HeaderInterceptor(prefs.getAuthToken()))
.readTimeout(30, TimeUnit.SECONDS)
.cache(cacheHolder.okHttpCache)
client = builder.build()
}
override fun get() = client
}
Thats all! Now, you just only need to place you modules in right scopes.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.init_view)
Toothpick.openScopes("activity scope").apply {
installModules(ActivityModule(this#YourActivity))
Toothpick.inject(this#YourActivity, this)
}
Toothpick.openScopes("activity scope", "network scope").apply {
installModules(NetworkModule())
}
// your activity code
}
This is how interceptor worked for handling 401 globally
public class ResponseHeaderInterceptor implements Interceptor {
private final Context context;
public ResponseHeaderInterceptor(Context context) {
this.context = context;
}
#NotNull
#Override
public Response intercept(#NotNull Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if(response.code() == 401){
SharedPreferences pref = context.getSharedPreferences(Constants.PREFERENCES, 0);
String userName = pref.getString("key_user_email", "");
//clear shared preferences
pref.edit().clear().apply();
Bundle params = new Bundle();
params.putString("user", userName);
FirebaseAnalytics.getInstance(context).logEvent(Constants.USER_UNAUTHORIZED_EVENT, params);
Intent intent = new Intent(this.context, IntroActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.context.startActivity(intent);
}
return response;
}
}
adding to okhttp client of retrofit
var okHttpClient: OkHttpClient = OkHttpClient()
.newBuilder()
.addInterceptor(ResponseHeaderInterceptor(MyApplication.getMyApplicationContext()))//Header interceptor for logging network responses
.build()
private var retrofit: Retrofit? = null
val client: Retrofit?
get() {
if (retrofit == null) {
retrofit = Retrofit.Builder()
.client(okHttpClient)
.baseUrl(BuildConfig.SERVER)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
Generalized Solution:
You can solve it by generalizing the error handling. You can use custom CallAdapterFactory to the Retrofit builder. Please refer below classes :
RxErrorHandlingCallAdapterFactory :
public class RxErrorHandlingCallAdapterFactory extends CallAdapter.Factory {
private static Context mContext = null;
private final RxJava2CallAdapterFactory original;
private RxErrorHandlingCallAdapterFactory() {
original = RxJava2CallAdapterFactory.create();
}
public static CallAdapter.Factory create(Context context) {
mContext = context;
return new RxErrorHandlingCallAdapterFactory();
}
#Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
return new RxCallAdapterWrapper(retrofit, original.get(returnType, annotations, retrofit));
}
private static class RxCallAdapterWrapper<R> implements CallAdapter<R, Object> {
private final Retrofit retrofit;
private final CallAdapter<R,
Object> wrapped;
public RxCallAdapterWrapper(Retrofit retrofit, CallAdapter<R, Object> wrapped) {
this.retrofit = retrofit;
this.wrapped = wrapped;
}
#Override
public Type responseType() {
return wrapped.responseType();
}
#Override
public Object adapt(Call<R> call) {
Object result = wrapped.adapt(call);
if (result instanceof Single) {
return ((Single) result).onErrorResumeNext(new Function<Throwable, SingleSource>() {
#Override
public SingleSource apply(#NonNull Throwable throwable) throws Exception {
return Single.error(asRetrofitException(throwable));
}
});
}
if (result instanceof Observable) {
return ((Observable) result).onErrorResumeNext(new Function<Throwable, ObservableSource>() {
#Override
public ObservableSource apply(#NonNull Throwable throwable) throws Exception {
return Observable.error(asRetrofitException(throwable));
}
});
}
if (result instanceof Completable) {
return ((Completable) result).onErrorResumeNext(new Function<Throwable, CompletableSource>() {
#Override
public CompletableSource apply(#NonNull Throwable throwable) throws Exception {
return Completable.error(asRetrofitException(throwable));
}
});
}
return result;
}
private RetrofitException asRetrofitException(Throwable throwable) {
// We had non-200 http error
Log.v("log", "eror");
throwable.printStackTrace();
if (throwable instanceof HttpException) {
HttpException httpException = (HttpException) throwable;
final Response response = httpException.response();
//if ((mContext instanceof Activity)) {
String s = "Something went wrong."; //mContext.getString(R.string.something_went_wrong);
try {
s = new JSONObject(response.errorBody().string()).getString("message");
if (response.code() == 401) { // 401 Unauthorized
Intent intent = new Intent(mContext, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivity(intent);
}
} catch (JSONException | IOException e) {
e.printStackTrace();
}
return RetrofitException.unexpectedError(s, response, retrofit);
//showErrorDialog(mContext, response);
//}
// return RetrofitException.httpError(response.errorBody().toString(), response, retrofit);
}
// A network error happened
if (throwable instanceof IOException) {
return RetrofitException.networkError((IOException) throwable);
}
// We don't know what happened. We need to simply convert to an unknown error
return RetrofitException.unexpectedError(throwable);
}
}
}
RetrofitException :
public class RetrofitException extends RuntimeException {
private final String url;
private final Response response;
private final Kind kind;
private final Retrofit retrofit;
RetrofitException(String message, String url, Response response, Kind kind, Throwable exception, Retrofit retrofit) {
super(message, exception);
this.url = url;
this.response = response;
this.kind = kind;
this.retrofit = retrofit;
}
public static RetrofitException httpError(String url, Response response, Retrofit retrofit) {
String message = response.code() + " " + response.message();
return new RetrofitException(message, url, response, Kind.HTTP, null, retrofit);
}
public static RetrofitException networkError(IOException exception) {
return new RetrofitException(exception.getMessage(), null, null, Kind.NETWORK, exception, null);
}
public static RetrofitException unexpectedError(Throwable exception) {
return new RetrofitException(exception.getMessage(), null, null, Kind.UNEXPECTED, exception, null);
}
public static RetrofitException unexpectedError(String s, Response response, Retrofit retrofit) {
return new RetrofitException(s, null, null, Kind.UNEXPECTED, null, null);
}
/**
* The request URL which produced the error.
*/
public String getUrl() {
return url;
}
/**
* Response object containing status code, headers, body, etc.
*/
public Response getResponse() {
return response;
}
/**
* The event kind which triggered this error.
*/
public Kind getKind() {
return kind;
}
/**
* The Retrofit this request was executed on
*/
public Retrofit getRetrofit() {
return retrofit;
}
/**
* HTTP response body converted to specified {#code type}. {#code null} if there is no
* response.
*
* #throws IOException if unable to convert the body to the specified {#code type}.
*/
public <T> T getErrorBodyAs(Class<T> type) throws IOException {
if (response == null || response.errorBody() == null) {
return null;
}
Converter<ResponseBody, T> converter = retrofit.responseBodyConverter(type, new Annotation[0]);
return converter.convert(response.errorBody());
}
/**
* Identifies the event kind which triggered a {#link RetrofitException}.
*/
public enum Kind {
/**
* An {#link IOException} occurred while communicating to the server.
*/
NETWORK,
/**
* A non-200 HTTP status code was received from the server.
*/
HTTP,
/**
* An internal error occurred while attempting to execute a request. It is best practice to
* re-throw this exception so your application crashes.
*/
UNEXPECTED
}
}
Retrofit Builder :
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create(context))
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(API_URL)
.client(client)
.build();
You can handle 401 in RxErrorHandlingCallAdapterFactory and other errors through Throwable.
I have a retrofit 2 response by which i gets data from server. my code is working fine.
But i want cache this response using SharedPreferences and keep it till that activity is running and after activity is destroyed. i want to delete this response from SharedPreferences.
This is my Code:
public class SampleClass {
private DataInterface mListener;
public SampleClass() {
super();
}
public void getDataForId(final String id) {
ApiInterface apiInterface = APIClient.getApiInterface();
Call<MyResponse> call = apiInterface.getResponse();
call.enqueue(new Callback<MyResponse>() {
#Override
public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
if (response!=null && response.body() != null && mListener != null) {
mListener.responseData(response.body());
}
}
#Override
public void onFailure(Call<MyResponse> call, Throwable t) {
}
});
}
public void setOnDataListener(DataInterface listener) {
mListener = listener;
}
public interface DataInterface {
void responseData( MyResponse myResponse );
}
}
SecondData class file
sampleClass.setOnDataListener(new SampleClass.DataInterface() {
#Override
public void responseData(MyResponse myResponse) {
// i wanna store this response into SharedPreferences for temp and delete after activity is destroyed.
List<Detail> details = myResponse.getDetails();
for (Detail d : details) {
if (d.getId().equals(id)) {
reqDetail = d;
name.setText(reqDetail.getName());
Picasso.with(SecondData.this)
.load(reqDetail.getName())
.placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher)
.into(image);
}
}
}
});
ApiInterface
public interface ApiInterfaceNew {
#GET("/display.php")
Call<MyResponse> getResponse();//imp to include MyResponse as a call
}
Api class
private static final String ROOT_URL = "";
private static Retrofit retrofit1 = null;
private static final String CACHE_CONTROL = "Cache-Control";
public static Retrofit getClient() {
if (retrofit1 == null) {
retrofit1 = new Retrofit.Builder()
.baseUrl(ROOT_URL)
.client(provideOkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit1;
}
public static ApiInterfaceNew getApiInterface() {
return getClient().create(ApiInterfaceNew.class);
}
private static OkHttpClient provideOkHttpClient() {
return new OkHttpClient.Builder()
.addInterceptor(provideHttpLoggingInterceptor())
.addInterceptor(provideOfflineCacheInterceptor())
.addNetworkInterceptor(provideCacheInterceptor())
.cache(provideCache())
.build();
}
private static Cache provideCache() {
Cache cache = null;
try {
cache = new Cache(new File(AppControler.getInstance().getCacheDir(), "http-cache"),
10 * 1024 * 1024); // 10 MB
} catch (Exception e) {
Timber.e("Could not create Cache!");
}
return cache;
}
private static HttpLoggingInterceptor provideHttpLoggingInterceptor() {
HttpLoggingInterceptor httpLoggingInterceptor =
new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
#Override
public void log(String message) {
Timber.e(message);
}
});
httpLoggingInterceptor.setLevel(BuildConfig.DEBUG ? HEADERS : NONE);
return httpLoggingInterceptor;
}
public static Interceptor provideCacheInterceptor() {
return new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
// re-write response header to force use of cache
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(2, TimeUnit.MINUTES)
.build();
return response.newBuilder()
.header(CACHE_CONTROL, cacheControl.toString())
.build();
}
};
}
public static Interceptor provideOfflineCacheInterceptor() {
return new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!AppControler.hasNetwork()) {
CacheControl cacheControl = new CacheControl.Builder()
.maxStale(7, TimeUnit.DAYS)
.build();
request = request.newBuilder()
.cacheControl(cacheControl)
.build();
}
return chain.proceed(request);
}
};
}
AppControler class
public class AppControler extends Application {
private static AppControler instance;
#Override
public void onCreate()
{
super.onCreate();
instance = this;
if (BuildConfig.DEBUG)
{
Timber.plant(new Timber.DebugTree());
}
Timber.i("Creating our Application");
}
public static AppControler getInstance ()
{
return instance;
}
public static boolean hasNetwork ()
{
return instance.checkIfHasNetwork();
}
public boolean checkIfHasNetwork()
{
ConnectivityManager cm = (ConnectivityManager) getSystemService( Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
Add below code in your Retrofit response when a response is success.
First convert JSON to string and store.
Gson gson = new Gson();
String favData = gson.toJson(response.body());
save strings to prefrance.
preferenceManager is my SharedPref class
preferenceManager.setStringPreference(Global.OFFLINE_WORD, favData);
now when you want to get pref data call below method.
public ArrayList<MyResponse> getData(String key) {
String data = getStringPreference(key);
Gson gson = new Gson();
Type type = new TypeToken<ArrayList<MyResponse>>() {
}.getType();
return gson.fromJson(data, type);
}
You can use either ObjectMapper or Gson.
For ObjectMapper you can refer the below code
public static void updateUserInfo(UserInfo userInfo, Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
Crashlytics.log("updating User Info "+(userInfo!=null?userInfo.toString():"UserInfo is null"));
final SharedPreferences.Editor edit = preferences.edit();
ObjectMapper objectMapper = new ObjectMapper();
try {
String value = objectMapper.writeValueAsString(userInfo);
edit.putString("USER_INFO_MODEL", value);
edit.commit();
} catch (JsonProcessingException e) {
Exceptions.propagate(e);
}
}
you can also get the stored response from shared preferences
public static UserInfo getUserInfo(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
String userDetatails = preferences.getString(AppConstants.USER_INFO_MODEL, "");
Crashlytics.log("get UserInfo "+userDetatails);
ObjectMapper mapper = new ObjectMapper();
if (StringUtils.isEmpty(userDetatails)) {
return null;
}
UserInfo userInfo = null;
try {
userInfo = mapper.readValue(userDetatails, UserInfo.class);
} catch (IOException e) {
Exceptions.propagate(e);
}
return userInfo;
}
Somehow line mResponseText = response.body().string(); isn't writing member variable. Instead it appears to be creating and logging it locally.
Any ideas why? The more I look at it the more clueless I'm getting :(
public class Gateway {
private static final String TAG = Gateway.class.getSimpleName();
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
private String mResponseText = "[{'comment' : 'fake' , 'psn_nickname' : 'fake', 'created':'now', 'parent_class':''}]";
public Gateway (String url, String json, final Context context) {
if(isNetworkAvailable(context)) {
//if network is available build request
OkHttpClient client = new OkHttpClient();
// RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
//.post(body)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
//execute call
#Override
public void onFailure(Request request, IOException e) {
// if request failed
Toast.makeText(context, "request failed", Toast.LENGTH_LONG).show();
}
#Override
public void onResponse(Response response) throws IOException {
// if succeeded
if(response.isSuccessful()){
mResponseText = response.body().string();
Log.v(TAG, "SETTING RESPONSE");
// THIS LOGS PROPER JSON LOADED FROM NETWORK
Log.v(TAG, mResponseText);
} else {
//alertUserAboutError(context);
Toast.makeText(context, "Something wrong with response", Toast.LENGTH_LONG).show();
}
}
});
} else {
Toast.makeText(context, "Network is not available", Toast.LENGTH_LONG).show();
}
}
public String getResponse () {
Log.v(TAG, "GETTING RESPONSE");
// THIS LOGS FAKE SAMPLE JSON --- WTF???
Log.v(TAG, mResponseText);
return mResponseText;
}
// check if network is available
private boolean isNetworkAvailable(Context c) {
ConnectivityManager manager = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
boolean isAvailable = false;
if (networkInfo != null && networkInfo.isConnected()) {
isAvailable = true;
}
return isAvailable;
}
/*
private void alertUserAboutError(Context c) {
AlertDialogFragment dialog = new AlertDialogFragment();
dialog.show(c.getFragmentManager(), "error_dialog");
}
*/
}
Here's the code that's using this class
Gateway gateway = new Gateway(mCommentURL, "", this);
String mJsonData = gateway.getResponse();
EDIT Code update - removed extends Activity
You're calling getResponse() too early. The async operation has not completed yet and the value returned is the one you initialize there in the first place, not the one written in the Callback.
Put the code that uses the response in the Callback, or call that code from the callback.