Implementing efficient cache system [duplicate] - android

My android app gets its data using REST API. I want to have client side caching implemented. Do we have any inbuilt classes for this?
if not, is these any code that i can reuse? I remember coming across such code sometime back. However I cant find it.
If nothing else works, i will write my own. following is basic structure
public class MyCacheManager {
static Map<String, Object> mycache;
public static Object getData(String cacheid) {
return mycache.get(cacheid);
}
public static void putData(String cacheid, Object obj, int time) {
mycache.put(cacheid, obj);
}
}
how do i enable time for cached objects? also - whats the best way to serialize? cache should be intact even if app is closed and reopened later (if time has not expired).
Thanks
Ajay

Now awesome library Volley released on Google I/O 2013 which helps for improve over all problems of calling REST API:
Volley is a library,it is library called Volley from the Android dev team. that makes networking for Android apps easier and most importantly, faster. It manages the processing and caching of network requests and it saves developers valuable time from writing the same network call/cache code again and again. And one more benefit of having less code is less number of bugs and that’s all developers want and aim for.
Example for volley: technotalkative

One of the best ways is to use Matthias Käppler's ignited librarys to make http requests that caches the responses in memory (weak reference) and on file. Its really configurable to do one or the other or both.
The library is located here : https://github.com/mttkay/ignition with examples located here : https://github.com/mttkay/ignition/wiki/Sample-applications
Personally, I love this lib from when it was called Droidfu
Hope this helps you as much as it did me Ajay!

First check the device is connected from the internet or not.
public class Reachability {
private final ConnectivityManager mConnectivityManager;
public Reachability(Context context) {
mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
public boolean isConnected() {
NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnectedOrConnecting();
}}
If device is connected from internet then get the data from API and cache it else get the data from cache.
public class CacheManager {
Cache<String, String> mCache;
private DiskLruCache mDiskLruCache;
private final Context mContext;
public CacheManager(Context context) throws IOException {
mContext = context;
setUp();
mCache = DiskCache.getInstanceUsingDoubleLocking(mDiskLruCache);
}
public void setUp() throws IOException {
File cacheInFiles = mContext.getFilesDir();
int version = BuildConfig.VERSION_CODE;
int KB = 1024;
int MB = 1024 * KB;
int cacheSize = 400 * MB;
mDiskLruCache = DiskLruCache.open(cacheInFiles, version, 1, cacheSize);
}
public Cache<String, String> getCache() {
return mCache;
}
public static class DiskCache implements Cache<String, String> {
private static DiskLruCache mDiskLruCache;
private static DiskCache instance = null;
public static DiskCache getInstanceUsingDoubleLocking(DiskLruCache diskLruCache){
mDiskLruCache = diskLruCache;
if(instance == null){
synchronized (DiskCache.class) {
if(instance == null){
instance = new DiskCache();
}
}
}
return instance;
}
#Override
public synchronized void put(String key, String value) {
try {
if (mDiskLruCache != null) {
DiskLruCache.Editor edit = mDiskLruCache.edit(getMd5Hash(key));
if (edit != null) {
edit.set(0, value);
edit.commit();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public synchronized String get(String key) {
try {
if (mDiskLruCache != null) {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(getMd5Hash(key));
if (snapshot == null) {
// if there is a cache miss simply return null;
return null;
}
return snapshot.getString(0);
}
} catch (IOException e) {
e.printStackTrace();
}
// in case of error in reading return null;
return null;
}
#Override
public String remove(String key) {
// TODO: implement
return null;
}
#Override
public void clear() {
// TODO: implement
}
}
public static String getMd5Hash(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes());
BigInteger number = new BigInteger(1, messageDigest);
String md5 = number.toString(16);
while (md5.length() < 32)
md5 = "0" + md5;
return md5;
} catch (NoSuchAlgorithmException e) {
Log.e("MD5", e.getLocalizedMessage());
return null;
}
}}
Create the CacheInterceptor class to cache the network response and handle the errors
public class CacheInterceptor implements Interceptor{
private final CacheManager mCacheManager;
private final Reachability mReachability;
public CacheInterceptor(CacheManager cacheManager, Reachability reachability) {
mCacheManager = cacheManager;
mReachability = reachability;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String key = request.url().toString();
Response response;
if (mReachability.isConnected()) {
try {
response = chain.proceed(request);
Response newResponse = response.newBuilder().build();
if (response.isSuccessful()) {
if (response.code() == 204) {
return response;
}
// save to cache this success model.
mCacheManager.getCache().put(key, newResponse.body().string());
// now we know that we definitely have a cache hit.
return getCachedResponse(key, request);
}else if (response.code() >= 500) { // accommodate all server errors
// check if there is a cache hit or miss.
if (isCacheHit(key)) {
// if data is in cache, the return the data from cache.
return getCachedResponse(key, request);
}else {
// if it's a miss, we can't do much but return the server state.
return response;
}
}else { // if there is any client side error
// forward the response as it is to the business layers to handle.
return response;
}
} catch (ConnectException | UnknownHostException e) {
// Internet connection exception.
e.printStackTrace();
}
}
// if somehow there is an internet connection error
// check if the data is already cached.
if (isCacheHit(key)) {
return getCachedResponse(key, request);
}else {
// if the data is not in the cache we'll throw an internet connection error.
throw new UnknownHostException();
}
}
private Response getCachedResponse(String url, Request request) {
String cachedData = mCacheManager.getCache().get(url);
return new Response.Builder().code(200)
.body(ResponseBody.create(MediaType.parse("application/json"), cachedData))
.request(request)
.protocol(Protocol.HTTP_1_1)
.build();
}
public boolean isCacheHit(String key) {
return mCacheManager.getCache().get(key) != null;
}}
Now add the this interceptor in OkHttpClient while creating the service using Retrofit.
public final class ServiceManager {
private static ServiceManager mServiceManager;
public static ServiceManager get() {
if (mServiceManager == null) {
mServiceManager = new ServiceManager();
}
return mServiceManager;
}
public <T> T createService(Class<T> clazz, CacheManager cacheManager, Reachability reachability) {
return createService(clazz, HttpUrl.parse(ServiceApiEndpoint.SERVICE_ENDPOINT), cacheManager, reachability);
}
private <T> T createService(Class<T> clazz, HttpUrl parse, CacheManager cacheManager, Reachability reachability) {
Retrofit retrofit = getRetrofit(parse, cacheManager, reachability);
return retrofit.create(clazz);
}
public <T> T createService(Class<T> clazz) {
return createService(clazz, HttpUrl.parse(ServiceApiEndpoint.SERVICE_ENDPOINT));
}
private <T> T createService(Class<T> clazz, HttpUrl parse) {
Retrofit retrofit = getRetrofit(parse);
return retrofit.create(clazz);
}
private <T> T createService(Class<T> clazz, Retrofit retrofit) {
return retrofit.create(clazz);
}
private Retrofit getRetrofit(HttpUrl httpUrl, CacheManager cacheManager, Reachability reachability) {
return new Retrofit.Builder()
.baseUrl(httpUrl)
.client(createClient(cacheManager, reachability))
.addConverterFactory(getConverterFactory())
.build();
}
private OkHttpClient createClient(CacheManager cacheManager, Reachability reachability) {
return new OkHttpClient.Builder().addInterceptor(new CacheInterceptor(cacheManager, reachability)).build();
}
private Retrofit getRetrofit(HttpUrl parse) {
return new Retrofit.Builder()
.baseUrl(parse)
.client(createClient())
.addConverterFactory(getConverterFactory()).build();
}
private Retrofit getPlainRetrofit(HttpUrl httpUrl) {
return new Retrofit.Builder()
.baseUrl(httpUrl)
.client(new OkHttpClient.Builder().build())
.addConverterFactory(getConverterFactory())
.build();
}
private Converter.Factory getConverterFactory() {
return GsonConverterFactory.create();
}
private OkHttpClient createClient() {
return new OkHttpClient.Builder().build();
}}
Cache interface
public interface Cache<K, V> {
void put(K key, V value);
V get(K key);
V remove(K key);
void clear();}

Related

How can I use from GraphQl in android?

I need to a simple example for use GraphQl in android .
How can I use from GraphQl in android (tutorial).
In order to use GraphQL (in general), you need two things:
1. A GraphQL server
There are a few ways how you could go about this. Of course, you could simply go and implement one yourself in any server-side language you like.
Other (faster) approaches are to take advantage of existing tooling and generate a GraphQL API using services like graphql-up or create-graphql-server or even services like Graphcool (disclaimer: I work for them).
2. A GraphQL client library
Though this one isn't strictly necessary and you could also simply interact with the GraphQL server through plain HTTP (sending your queries and mutations in the body of POST requests), it is certainly beneficial to use existing tools that take repetitive work like caching or UI integrations off your shoulders. One of the most popular GraphQL clients right now is Apollo, and they're very actively working on a version for Android as well. However, this hasn't been officially released yet. So, you either have to use their existing development version of chose the former approach using plain HTTP for now.
Here is an example of querying GraphQl from Client. In this example I am using Retrofit 2:
// QueryHelper.java
// This line below is the simple format of Gql query
query = "query{me{name, location, majorOfInterest,profilePhoto{url(size: 400) }}}";
//Post the query using Retrofit2
GqlRetrofitClient.getInstance(getContext()).fetchUserDetails(new GqlQueryRequest(queryUserDetails)).enqueue(new Callback<UserDetails>() {
#Override
public void onResponse(Call<UserDetails> call, Response<UserDetails> response) {
//OnResponse do something();
}
#Override
public void onFailure(Call<UserDetails> call, Throwable t) {
Log.d(TAG, "Failed to fetch User details");
}
});
//GqlClient.java
public class GqlRetrofitClient {
public static final String BASE_URL = BuildConfig.DOMAIN;
private static GqlRetrofitClient sInstance;
private GqlRetrofitService mGqlRetrofitService;
Gson gson = new GsonBuilder().create();
private GqlRetrofitClient(final Context context) {
// Network Interceptor for logging
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addNetworkInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("X-User-Token", "AUTH_TOKEN")
.addHeader("X-User_Email", "Email")
.addHeader("content-type", "application/json")
.build();
return chain.proceed(request);
}
})
.addInterceptor(httpLoggingInterceptor)
.build();
// Retrofit initialization
final Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build();
mGqlRetrofitService = retrofit.create(GqlRetrofitService.class);
}
// Create an instance of GqlRetrofitClient to create retrofit service
public static GqlRetrofitClient getInstance(Context context){
if(sInstance == null){
sInstance = new GqlRetrofitClient(context.getApplicationContext());
}
return sInstance;
}
// Method call to get User details
public Call<UserDetails> fetchUserDetails(GqlQueryRequest queryUserDetails){
return mGqlRetrofitService.getUserDetails(queryUserDetails);
}
}
//GqlRetrofitService.java
public interface GqlRetrofitService{
#POST("/api/graph.json")
Call<UserDetails> getUserDetails(#Body GqlQueryRequest body);
}
In your manifest to add
<uses-permission android:name="android.permission.INTERNET"/>
Your dependencies
// Kotlin Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.4'
//OkHttp
implementation ("com.squareup.okhttp3:okhttp:3.12.12"){
force = true //API 19 support
}
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.12'
//retrofit
implementation "com.squareup.retrofit2:retrofit:2.7.1"
implementation "com.squareup.retrofit2:converter-scalars:$2.7.1"
Also Java 8 compatibility
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
With the service
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST
interface GraphQLService {
#Headers("Content-Type: application/json")
#POST("/")
suspend fun postDynamicQuery(#Body body: String): Response<String>
}
you can create a object
import retrofit2.Retrofit
import retrofit2.converter.scalars.ScalarsConverterFactory
object GraphQLInstance {
private const val BASE_URL: String = "http://192.155.1.55:2000/"
val graphQLService: GraphQLService by lazy {
Retrofit
.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.build().create(GraphQLService::class.java)
}
}
In the activity you can create this method
private fun post(userId: String){
val retrofit = GraphQLInstance.graphQLService
val paramObject = JSONObject()
paramObject.put("query", "query {users(userid:$userId){username}}")
GlobalScope.launch {
try {
val response = retrofit.postDynamicQuery(paramObject.toString())
Log.e("response", response.body().toString())
}catch (e: java.lang.Exception){
e.printStackTrace()
}
}
}
You can check the example in GitHub and my post
Note: if you need a mutation should be to change this line
paramObject.put("query", "query {users(userid:$userId){username}}")
to
paramObject.put("query", "mutation {users(userid:$userId){username}}")
personally I use Retrofit and I took this Link Credits
and changed some things.
This is the code:
In File "GraphQLConverter.java":
public class GraphQLConverter extends Converter.Factory {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
private GraphQueryProcessor graphProcessor;
private final Gson mGson;
private GraphQLConverter(Context context) {
graphProcessor = new GraphQueryProcessor(context);
mGson = new GsonBuilder()
.enableComplexMapKeySerialization()
.setLenient()
.create();
}
public static GraphQLConverter create(Context context) {
return new GraphQLConverter(context);
}
/** Override Converter.Factory Methods **/
#Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return null;
}
#Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
if(type == QueryContainerBuilder.class){
return new GraphRequestConverter(methodAnnotations);
} else {
return null;
}
}
/** RequestConverter Class **/
private class GraphRequestConverter implements Converter<QueryContainerBuilder, RequestBody> {
private Annotation[] mAnnotations;
private GraphRequestConverter(Annotation[] annotations) {
mAnnotations = annotations;
}
#Override
public RequestBody convert(#NonNull QueryContainerBuilder containerBuilder) {
QueryContainerBuilder.QueryContainer queryContainer = containerBuilder
.setQuery(graphProcessor.getQuery(mAnnotations))
.build();
return RequestBody.create(MEDIA_TYPE, mGson.toJson(queryContainer).getBytes());
}
}
}
In File "GraphQuery.java":
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface GraphQuery {
String value() default "";
}
In File "GraphQueryProcessor.java":
class GraphQueryProcessor {
private static final String TAG = GraphQueryProcessor.class.getSimpleName();
// GraphQl Constants
private static final String EXT_GRAPHQL = ".graphql";
private static final String ROOT_FOLDER_GRAPHQL = "graphql";
private final Map<String, String> mGraphQueries;
private Context mContext;
GraphQueryProcessor(Context context) {
mGraphQueries = new WeakHashMap<>();
mContext = context;
populateGraphQueries(ROOT_FOLDER_GRAPHQL);
}
/** Package-Private Methods **/
String getQuery(Annotation[] annotations) {
if(mGraphQueries == null || mGraphQueries.isEmpty()){
populateGraphQueries(ROOT_FOLDER_GRAPHQL);
}
GraphQuery graphQuery = null;
for (Annotation annotation : annotations) {
if (annotation instanceof GraphQuery) {
graphQuery = (GraphQuery) annotation;
break;
}
}
if (graphQuery != null) {
String fileName = String.format("%s%s", graphQuery.value(), EXT_GRAPHQL);
if (mGraphQueries != null && mGraphQueries.containsKey(fileName)) {
return mGraphQueries.get(fileName);
}
}
return null;
}
/** Private Methods **/
private void populateGraphQueries(#NonNull String path) {
try {
String[] paths = mContext.getAssets().list(path);
if (paths != null && paths.length > 0x0) {
for (String item : paths) {
String absolute = path + "/" + item;
if (!item.endsWith(EXT_GRAPHQL)) {
populateGraphQueries(absolute);
} else {
mGraphQueries.put(item, getFileContents(mContext.getAssets().open(absolute)));
}
}
}
} catch (IOException ioE) {
BaseEnvironment.onExceptionLevelLow(TAG, ioE);
}
}
private String getFileContents(InputStream inputStream) {
StringBuilder queryBuffer = new StringBuilder();
try {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
for (String line; (line = bufferedReader.readLine()) != null; )
queryBuffer.append(line);
inputStreamReader.close();
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
return queryBuffer.toString();
}
}
In File "QueryContainerBuilder.java":
public class QueryContainerBuilder {
// Mask Types
private static final byte MASK_REPLACE_QUERY_ARGUMENTS = 0b1; // Invece di inviare il json con le variabili va a inserirle nella query i valori sostituendo i tipi degli argomenti.
private static final byte MASK_REPLACE_EXPLICIT_QUOTES = MASK_REPLACE_QUERY_ARGUMENTS << 0b1; // Alle stringhe non vengono automaticamente messe le virgolette ma devono essere aggiunte nei valori passati per le variabili.
private static final byte MASK_REPLACE_WITH_PLACEHOLDERS = MASK_REPLACE_EXPLICIT_QUOTES << 0b1; // Va a sostituire i placeholders "<key_var_name>" presenti nella query con i valori delle variabili.
private QueryContainer mQueryContainer;
private byte mMask;
public QueryContainerBuilder() {
mQueryContainer = new QueryContainer();
}
/** Setter Methods **/
public QueryContainerBuilder setQuery(String query) {
mQueryContainer.setQuery(query);
return this;
}
public QueryContainerBuilder setReplaceQueryArguments(){
mMask = MASK_REPLACE_QUERY_ARGUMENTS;
return this;
}
public QueryContainerBuilder setReplaceExplicitQuotes(){
mMask = MASK_REPLACE_QUERY_ARGUMENTS | MASK_REPLACE_EXPLICIT_QUOTES;
return this;
}
public QueryContainerBuilder setReplaceWithPlaceholders(){
mMask = MASK_REPLACE_QUERY_ARGUMENTS | MASK_REPLACE_WITH_PLACEHOLDERS;
return this;
}
/** Public Methods **/
public QueryContainerBuilder putVariable(String key, Object value) {
mQueryContainer.putVariable(key, value);
return this;
}
public boolean containsVariable(String key) {
return mQueryContainer.containsVariable(key);
}
/** Builder Methods **/
public QueryContainer build() {
if((mMask & MASK_REPLACE_QUERY_ARGUMENTS) != 0x0){
if((mMask & MASK_REPLACE_WITH_PLACEHOLDERS) != 0x0){
mQueryContainer.replaceVariablesPlaceholdersInQuery();
} else {
mQueryContainer.replaceVariablesInQuery(mQueryContainer.mVariables, 0x0);
}
mQueryContainer.mVariables = null;
}
return mQueryContainer;
}
/** Public Static Classes **/
public class QueryContainer {
#SerializedName("variables")
private LinkedHashMap<String, Object> mVariables;
#SerializedName("query")
private String mQuery;
QueryContainer() {
mVariables = new LinkedHashMap<>();
}
/** Private Methods **/
private void setQuery(String query) {
mQuery = query;
}
private void putVariable(String key, Object value) {
mVariables.put(key, value);
}
private boolean containsVariable(String key) {
return mVariables != null && mVariables.containsKey(key);
}
private void replaceVariablesInQuery(LinkedHashMap<String, Object> map, int index){
if(!TextUtils.isEmpty(mQuery) && map.size() > 0x0){
List<String> keys = new ArrayList<>(map.keySet());
for(String key : keys){
Object value = map.get(key);
if(value instanceof LinkedHashMap){
replaceVariablesInQuery((LinkedHashMap<String, Object>) value, index);
} else {
int i = mQuery.indexOf(key + ":", index) + key.length() + 0x1;
int z;
if(keys.indexOf(key) < keys.size() - 0x1){
z = mQuery.indexOf(",", i);
} else {
z = mQuery.indexOf(")", i);
int x = mQuery.substring(i, z).indexOf('}');
if(x != -0x1){
if(mQuery.substring(i, i + 0x4).contains("{")){
x++;
}
z -= ((z - i) - x);
}
}
String replace;
if((mMask & MASK_REPLACE_EXPLICIT_QUOTES) != 0x0){
replace = String.valueOf(value);
} else {
replace = value instanceof String ?
"\"" + value.toString() + "\"" : String.valueOf(value);
}
String sub = mQuery.substring(i, z)
.replaceAll("[\\\\]?\\[", "\\\\\\[").replaceAll("[\\\\]?\\]", "\\\\\\]")
.replaceAll("[\\\\]?\\{", "\\\\\\{").replaceAll("[\\\\]?\\}", "\\\\\\}");
mQuery = mQuery.replaceFirst(sub.contains("{}") ? sub.replace("{}", "").trim() : sub.trim(), replace);
index = z + 0x1;
}
}
}
}
private void replaceVariablesPlaceholdersInQuery(){
if(!TextUtils.isEmpty(mQuery) && mVariables.size() > 0x0){
for(String key : mVariables.keySet()){
mQuery = mQuery.replaceFirst("\\<" + key + "\\>", mVariables.get(key) != null ? mVariables.get(key).toString() : "null");
}
mVariables = null;
}
}
}
}
Put your queries in a "graphql" directory in the "assets" folder with the ".graphql" extension for your query files. You can change the extension or the folder by changing the "EXT_GRAPHQL" or "ROOT_FOLDER_GRAPHQL" constants in "GraphQueryProcessor". You can use these formats for the queries:
query {
myQuery(param1: <myParam1>) {
....
}
}
If you use this format you need to use "MASK_REPLACE_WITH_PLACEHOLDERS" in your QueryContainerBuilder. Also you need to pass as the HashMap key the name of the placeholder without the "<...>", so in this case "myParam1".
The others format are just common GraphQL queries, like:
query ($p1: String!) {
muQuery(p1: $id) {
...
}
}
With this format you can use normal QueryContainerBuilder behaviour (no mask applyed, so it will pass and generate the "variables" json object.) or the "MASK_REPLACE_QUERY_ARGUMENTS" which will remove the "$id" and place the value.
When you init Retrofit add the "GraphQLConverter". Take care about the "ConvertFactories" order! You can put more ConvertFactory, but they consume the input so if in this case you put "Gson" before "GraphQL" the "GsonConverted" will consume the input data:
new Retrofit.Builder()
.baseUrl(mBaseUrl)
.addConverterFactory(GraphQLConverter.create(context))
.addConverterFactory(GsonConverterFactory.create(gson))
.client(getBaseHttpClient(interceptor))
.build();
In your Retrofit API:
#POST(AppConstants.SERVICE_GQL)
#GraphQuery(AppConstants.MY_GRAPHQL_QUERY_FILENAME)
fun callMyGraphQlQuery(#Body query: QueryContainerBuilder): Call<MyGraphQlResponse>
Call examples:
val query = QueryContainerBuilder()
.putVariable("myParam1", myValue)
.setReplaceWithPlaceholders()
createService(API::class.java).callMyGraphQlQuery(query)
val query = QueryContainerBuilder()
.putVariable("p1", myValue)
.setReplaceQueryArguments()
createService(API::class.java).callMyGraphQlQuery(query)
val query = QueryContainerBuilder()
.putVariable("p1", myValue)
createService(API::class.java).callMyGraphQlQuery(query)
Idk if the "MASK_REPLACE_QUERY_ARGUMENTS" works right, I used it only 2/3 times and then the back-end was changed and wrote better.
I did those cases (masks) to process the queries because I had this 3 case of queries with the back-end I was calling.
You can just add others query processing behavior just by adding another mask and the code in the "QueryContainerBuilder".
If anyone use this code and change it making it better, please write me the changes so I will change the code in my library too.
Thanks you,
have a nice coding and day :D
Bye!

application interceptor com.blueware.agent.android.instrumentation.okhttp3.d#41c1cec0 returned null

application interceptor com.blueware.agent.android.instrumentation.okhttp3.d#41c1cec0 returned null
When I used OkHttp3, this error occurred a NullPointerException many times! especially without a network.
Here are the details:
Here are the resource codes where the error occurred:
class ApplicationInterceptorChain implements Interceptor.Chain {
private final int index;
private final Request request;
private final boolean forWebSocket;
ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
this.index = index;
this.request = request;
this.forWebSocket = forWebSocket;
}
#Override public Connection connection() {
return null;
}
#Override public Request request() {
return request;
}
#Override public Response proceed(Request request) throws IOException {
// If there's another interceptor in the chain, call that.
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
Interceptor interceptor = client.interceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain);
if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
}
return interceptedResponse;
}
// No more interceptors. Do HTTP.
return getResponse(request, forWebSocket);
}
}
I solved the problem. It had a conflict with other SDK.

Retrofit2 Tail Recursion Using RxJava / RxAndroid

I am really trying to get a hang of using Retrofit with RxJava / RxAndroid. I've done this using normal Retrofit2 Callback method in a previous app without the use of Reactive Programming and it worked fine. So, here is it. I need to Tail Recall a function meant to fetch all Local Government from the server. The API uses pagination (I have to construct the URL with ?page=1, perPage=2). I've to do this till I've the whole data. So, below is my Rx code
public static Observable<LgaListResponse> getPages(Context acontext) {
String token = PrefUtils.getToken(acontext);
BehaviorSubject<Integer> pageControl = BehaviorSubject.<Integer>create(1);
Observable<LgaListResponse> ret2 = pageControl.asObservable().concatMap(integer -> {
if (integer > 0) {
Log.e(TAG, "Integer: " + integer);
return ServiceGenerator.createService(ApiService.class, token)
.getLgas(String.valueOf(integer), String.valueOf(21))
.doOnNext(lgaListResponse -> {
if (lgaListResponse.getMeta().getPage() != lgaListResponse.getMeta().getPageCount()) {
pageControl.onNext(initialPage + 1);
} else {
pageControl.onNext(-1);
}
});
} else {
return Observable.<LgaListResponse>empty().doOnCompleted(pageControl::onCompleted);
}
});
return Observable.defer(() -> ret2);
}
And my ServiceGenerator Class
public class ServiceGenerator {
private static final String TAG = "ServiceGen";
private static OkHttpClient.Builder builder = new OkHttpClient.Builder();
private static Retrofit.Builder retrofitBuilder =
new Retrofit.Builder()
.baseUrl(BuildConfig.HOST)
.addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(GsonConverterFactory.create(CustomGsonParser.returnCustomParser()));
public static <S> S createService(Class<S> serviceClass, String token) {
builder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
/*builder.addNetworkInterceptor(new StethoInterceptor());*/
builder.connectTimeout(30000, TimeUnit.SECONDS);
builder.readTimeout(30000, TimeUnit.SECONDS);
if (token != null) {
Interceptor interceptor = chain -> {
Request newRequest = chain.request().newBuilder()
.addHeader("x-mobile", "true")
.addHeader("Authorization", "Bearer " + token).build();
return chain.proceed(newRequest);
};
builder.addInterceptor(interceptor);
}
OkHttpClient client = builder.build();
Retrofit retrofit = retrofitBuilder.client(client).build();
Log.e(TAG, retrofit.baseUrl().toString());
return retrofit.create(serviceClass);
}
public static Retrofit retrofit() {
OkHttpClient client = builder.build();
return retrofitBuilder.client(client).build();
}
public static class CustomGsonParser {
public static Gson returnCustomParser(){
return new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.create();
}
}
}
So, I noticed on the first call, I get a response, but on the second one, the 440Error is thrown. The URL is formed, but the request throws a 400Error. I don't know why it's throwing a 400 everything is working fine if I use POSTMAN to test. And, I tested with my old code too. The Log is too long, so I put it in pastebin LOGS any help thanks. I've written most of this app with RxAndroid / RxJava. Thanks
I suggest you simplify things (and remove recursion). First build up your pages using something like
public static Observable<LgaListResponse> getPages(Context acontext, int initialPage, int perPage) {
String token = PrefUtils.getToken(acontext);
BehaviorSubject<Integer> pagecontrol = BehaviorSubject.<Integer>create(initialPage);
Observable<LgaListResponse> ret2 = pagecontrol.asObservable().concatMap(
new Func1<Integer,Observable<LgaListResponse>>() {
Observable<LgaListResponse> call(Integer pageNumber) {
if (pageNumber > 0) {
return ServiceGenerator.createService(ApiService.class, token)
.getLgas(String.valueOf(aKey), String.valueOf(perPage))
.doOnNext(
new Action1<LgaListResponse>() {
void call(LgaListResponse page) {
if (page.getMeta().getPage() != page.getMeta().getPageCount()) {
pagecontrol.onNext(page.getMeta().getNextPage());
} else {
pagecontrol.onNext(-1);
}
}
}
);
}
else {
return Observable.<LgaListResponse>empty().doOnCompleted(()->pagecontrol.onCompleted());
}
}
}
);
return Observable.defer(
new Func0<Observable<LgaListResponse>() {
Observable<LgaListResponse> call() {
return ret2;
}
}
);
}
then subscribe to the resulting observable. It looks horrible because I've avoided using lambdas but it should work.

Return custom exceptions in case of failure with RxJava + Retrofit

I have a repository class that must return this: Observable<List<SomeObject>,
I do this:
#Override
public Observable<List<SomeObject>> getAllById(Long id) {
if (!AndroidUtils.isNetworkAvailable(mContext))
return Observable.error(new NoNetworkConnectionException());
return mRestService.get(id);
}
This approach works normally, the problem is I want to return custom exceptions
in case of failures, but I don't know the best way to do this with rxjava.
So far, the only solution that works is something like that:
#Override
public Observable<List<SomeObject>> getAllById(Long id) {
if (!AndroidUtils.isNetworkAvailable(mContext))
return Observable.error(new NoNetworkConnectionException());
return Observable.create(subscriber -> {
mRestService.get(id).subscribe(new Observer<List<SomeObject>>() {
#Override
public void onCompleted() {
subscriber.onCompleted();
}
#Override
public void onError(Throwable e) {
if (e instanceof HttpException && ((HttpException) e).code() == 401)
subscriber.onError(new UnathorizedException());
else
subscriber.onError(e);
}
#Override
public void onNext(List<SomeObject> objects) {
subscriber.onNext(objects);
}
});
});
}
I know that is not a good thing to use Observable.create, but I can't figure out
another way to do this.
RestService is this:
public interface RestService {
#GET("objects/{id}")
Observable<List<SomeObject>> get(#Path("id") Long id);
}
If anyone knows a better approach, please tell me.
Thanks!
You can use the operator onErrorResumeNext to map your exception to another one.
mRestService.get(id)
.onErrorResumeNext(e -> {
if (e instanceof HttpException && ((HttpException) e).code() == 401)
return Observable.error(new UnathorizedException());
else
return Observable.error(e);
})
.subscribe();
I did this in a project by adding an interceptor when creating the rest service. This way the errors are checked before the request reaches your rest service.
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.addInterceptor(new ErrorInterceptor());
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(myBaseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(httpClientBuilder.build())
.build();
The ErrorInterceptor class looks like
public class ErrorInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
#Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Response response = chain.proceed(originalRequest);
if (response.code() >= 400) {
throwError(response);
return response;
} else {
return response;
}
}
private void throwError (Response response) throws IOException {
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
if (responseBody.contentLength() != 0) {
String responseJSON = buffer.clone().readString(charset);
Gson gson = new Gson();
Type type = new TypeToken<ErrorResponse>() {}.getType();
ErrorResponse error = null;
try {
error = gson.fromJson(responseJSON, type);
}
catch (Exception e) {
int a = 1;
}
if (error != null && error.hasErrors())
throw ErrorMapper.mapError(error.getFirstError());
}
}
}
And my ErrorResponse class
public class ErrorResponse {
private List<Error> errors;
public boolean hasErrors () {
return errors != null && errors.size() > 0;
}
public Error getFirstError() {
if (errors == null || errors.size() == 0) return null;
return errors.get(0);
}
}
In my ErrorMapper I just check the error message against a set of possible messages from the server and create a new Error containing the message to display on the client.
I'm just checking the first error here, but you should easily be able to adopt it to multiple errors.
You can try following:
RestService restService = Mockito.mock(RestService.class);
Observable<List<Object>> networkExOrEmpty = isNetworkAvailable() ?
Observable.empty() :
Observable.error(new NoNetworkConnectionException());
Observable<List<Object>> resultOrEx = restService
.getAllById(42L)
.materialize()
.map(res -> {
if (res.isOnError()) {
Throwable err = res.getThrowable();
if (err instanceof HttpException && ((HttpException) err).code() == 401) {
return Notification.createOnError(new UnauthrizedException());
} else {
return Notification.createOnError(err);
}
} else {
return res;
}
}).dematerialize();
Observable<List<Object>> result = networkExOrEmpty.concatWith(resultOrEx);
Start with Observable which emits either Error or nothing, depending on network conectivity state, then concatenate it with result from Retrofit service. Observable.materialize() allows one to act on error items: push appropriate exception downstream, and pass non-error notifications as is.

Android prepare waiting mechanism or correct synchronize to service

in my Android project i try to prepare waiting mechanism or correct synchronize to intent service to handle multi concurrency requests user. for example after click on button, my application try to get data on server with Service,
after click on the button, user maybe try to click on that again, for manage this operation i coding below sample to handle multi concurrency requests user, such as waiting to finish request with create an requests path.
but in this code i cont know how to create requests stack, that means they must run by order's requests.
now is my code correct?
Volley Simple Singleton Class:
public class CustomVolleyRequestQueue {
private static CustomVolleyRequestQueue mInstance;
private static Context context;
private RequestQueue mRequestQueue;
private CustomVolleyRequestQueue(Context context) {
this.context = context;
mRequestQueue = getRequestQueue();
}
public static synchronized CustomVolleyRequestQueue getInstance(Context context) {
if (mInstance == null) {
mInstance = new CustomVolleyRequestQueue(context);
}
return mInstance;
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
Cache cache = new DiskBasedCache(context.getCacheDir(), 10 * 1024 * 1024);
Network network = new BasicNetwork(new HurlStack());
mRequestQueue = new RequestQueue(cache, network);
// start the volley request queue
mRequestQueue.start();
}
return mRequestQueue;
}
}
Intent Service:
public class WebService extends IntentService {
public static final String REQUEST_TAG = "SimpleWebServiceBlockingRequest";
private RequestQueue mQueue;
private Object isReceiveMessagesLock;
public WebService() {
super("WebService");
}
public WebService(String name) {
super("WebService");
}
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
Bundle data = intent.getExtras();
if (data != null) {
String url = data.getString("url");
synchronized (isReceiveMessagesLock) {
if (isReceiveMessagesLock != null) {
return;
} else {
try {
isReceiveMessagesLock.wait();
startParsingTask(url); //receiving start
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public void startParsingTask(final String url) {
Thread prepaire_request_thread = new Thread() {
public void run() {
ThreadB request_from_servcer_thread = new ThreadB(getApplicationContext(), url);
JSONObject json_object = null;
try {
json_object = request_from_servcer_thread.execute().get(10, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
e.printStackTrace();
}
if (json_object != null) {
Log.e("OUTPUT: ", json_object.toString());
}
}
};
prepaire_request_thread.start();
}
private class ThreadB extends AsyncTask<Void, Void, JSONObject> {
private Context context;
private String fetch_url = "";
public ThreadB(Context ctx, String url) {
context = ctx;
this.fetch_url = url;
}
#Override
protected JSONObject doInBackground(Void... params) {
final RequestFuture<JSONObject> futureRequest = RequestFuture.newFuture();
mQueue = CustomVolleyRequestQueue.getInstance(context.getApplicationContext())
.getRequestQueue();
final JsonObjectRequest jsonRequest = new JsonObjectRequest(Request.Method
.GET, fetch_url,
new JSONObject(), futureRequest, futureRequest);
jsonRequest.setTag(REQUEST_TAG);
mQueue.add(jsonRequest);
try {
return futureRequest.get(10, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
e.printStackTrace();
}
return null;
}
}
}
POST UPDATED:
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
Bundle data = intent.getExtras();
if (data != null) {
String url = data.getString("url");
startParsingTask(url); //receiving start
}
}
}
IntentService already handles one call per time, you don't need syncronize or anything else.
Volley supports also multiple calls, so you won't have any problem of concurrency at all.
After that, if you are calling APIs, you may want to try retrofit ( http://square.github.io/retrofit/ ) or at least use GSON to deserialize JSON data.

Categories

Resources