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!
Related
I am facing a problem,first of all I am new to android, and I am having a Post request, I am sending an object in body :
public class SingleContractRequest {
#SerializedName("userRole")
private String userRole;
#SerializedName("contratId")
private String contratId;
public SingleContractRequest(String userRole, String contractId) {
this.userRole = userRole;
this.contratId = contractId;
}
public String getUserRole() {
return userRole;
}
public void setUserRole(String userRole) {
this.userRole = userRole;
}
public String getContractId() {
return contratId;
}
public void setContractId(String contractId) {
this.contratId = contractId;
}
}
And that is my ContractApi, the method called is the second one :
public interface ContractApi {
#GET("contrats.php")
Single<List<ContractModel>> getContractList();
#POST("contrat.php")
Single<ContractModel> getContract(#Body SingleContractRequest body);
}
And here is my Module :
#Module
public class ApiModule {
public static String BASE_URL = "http://192.168.1.104/newconceptsphp/";
#Provides
public ContractApi provideContractApi(){
Gson gson = new GsonBuilder()
.setLenient()
.create();
return new Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
.create(ContractApi.class);
}
#Provides
public ContractService provideContractService(){
return ContractService.getInstance();
}
}
And to call the api I have a method in my service :
public Single<ContractModel> getContract(SingleContractRequest request) {
return api.getContract(request);
}
So I could do that in one single method but I am creating many layers for f better architecture.
The error I am getting now and I don't know how to solve it :
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException:
Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:224)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
This is the script I am consuming :
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS');
header("Access-Control-Allow-Headers: Content-Type, Content-Range, Content-Disposition, Content-Description");
json_decode(file_get_contents("php://input"), true);
$dsn = "mysql:host=localhost;dbname=leranconcepts";
$user = "root";
$passwd = "";
$pdo = new PDO($dsn, $user, $passwd);
if (isset($_POST["userRole"])){
$userRole = $_POST["userRole"];
} else {
$userRole = null;
}
if (isset($_POST["contratId"])){
$contratId = $_POST["contratId"];
} else {
$contratId = null;
}
// managing products
if ($userRole === "VENDEUR"){
$sth = $pdo->prepare("SELECT * FROM contrat WHERE id=?");
} else if($userRole === "COURTIER"){
$sth = $pdo->prepare("SELECT id, imageUrl, courtier FROM contrat WHERE id=?");
}
$sth->execute([$contratId]);
$result = $sth->fetchAll(PDO::FETCH_OBJ);
echo json_encode($result[0]);
?>
I think this is to understand my problem, how to solve it.
Any help would be much appreciated guys.
I am using spoonacular API for a recipe app project. The problem occurs when trying to making multiple GET requests to the API. The first request is a simple search with a query parameter. The resulting JSON of the first request contains a Recipe ID and I use that ID to make the second GET request , where the problem occurs.
The API responds only when I make the request the first time but after that it responds with error code 500 [Internal Server Error].
I have tested the GET request on Postman but there it works fine every time.
I'm new to working with API's and any help would be immensely appreciated.
This is my Retrofit Service Class
public class ServiceGenerator {
public static final String API_BASE_URL = "https://spoonacular-recipe-food-nutrition-v1.p.rapidapi.com/";
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = builder.build();
public static <S> S createService(Class<S> serviceClass, final String HostName, final String KeyVal)
{
if (!TextUtils.isEmpty(HostName) && !TextUtils.isEmpty(KeyVal))
{
HeadersInterceptor interceptor = new HeadersInterceptor(HostName,KeyVal);
if (!httpClient.interceptors().contains(interceptor))
{
httpClient.addInterceptor(interceptor);
builder.client(httpClient.build());
retrofit = builder.build();
}
}
return retrofit.create(serviceClass);
}
This is the Interceptor I am using to add Headers with the request.
public class HeadersInterceptor implements Interceptor {
private String HostName,KeyVal;
HeadersInterceptor(final String HostName,final String KeyVal) {
this.HostName = HostName;
this.KeyVal = KeyVal;
}
#NotNull
#Override
public Response intercept(#NotNull Chain chain) throws IOException {
Request original = chain.request();
Request.Builder builder = original.newBuilder()
.addHeader("X-RapidAPI-Host",HostName)
.addHeader("X-RapidAPI-Key",KeyVal);
Request request = builder.build();
return chain.proceed(request);
}
}
This is my Fragment which makes a search query and SUCCESSFULLY return results[Receipe ID's]
public class ListSelectedFragments extends Fragment {
private ProgressBar PreviewFragPrg;
private final String TAG = "ListSelectedFragment->";
private PreviewRecipeAdapter adapter;
private RecyclerView SelectedItemRV;
private ArrayList<RecipePreviewHolder> RecipePreviewsList = new ArrayList<>();
public ListSelectedFragments() {
// Required empty public constructor
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
final View view = inflater.inflate(R.layout.fragment_list_selected_fragments, container, false);
SelectedItemRV = view.findViewById(R.id.SelectedItemRV);
TextView DisplayNameTV = view.findViewById(R.id.DisplayNameTV);
PreviewFragPrg = view.findViewById(R.id.PreviewFragPrg);
ImageView BackBtn = view.findViewById(R.id.BackBtn);
BackBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (getFragmentManager() != null) {
getFragmentManager().popBackStackImmediate();
}
}
});
if (getArguments() != null) {
final String QueryTag = getArguments().getString("QueryTag");
final String CuisineName = getArguments().getString("CuisineName");
if(CuisineName!=null){
DisplayNameTV.setText(CuisineName);
}
if(QueryTag!=null){
ProcessQuery(QueryTag);
}
}
return view;
}
private void ProcessQuery(final String QueryStr){
String hostname = getResources().getString(R.string.spoonacular_host_name);
String key = getResources().getString(R.string.spoonacular_apikey_val);
final ServiceGenerator.GetDataService mService =
ServiceGenerator.createService(ServiceGenerator.GetDataService.class, hostname,key);
Call<RecipeInfoModel> call = mService.getRecipes(QueryStr);
call.enqueue(new Callback<RecipeInfoModel>() {
#Override
public void onResponse(#NonNull Call<RecipeInfoModel> call,
#NonNull Response<RecipeInfoModel> response)
{
Log.d(TAG, "Request Response Received");
Log.d(TAG, response.toString());
if (response.body() != null) {
Results[] mRES = response.body().getResults();
SetUpRecipePreviews(mRES);
PreviewFragPrg.setVisibility(View.GONE);
}
}
#Override
public void onFailure(#NonNull Call<RecipeInfoModel> call, #NonNull Throwable t) {
Log.d(TAG, "Request Failed");
Log.d(TAG, call.toString());
Log.d(TAG, "Throwable ->" + t);
PreviewFragPrg.setVisibility(View.GONE);
Toast.makeText(getActivity(),"Could not get required recipes",Toast.LENGTH_SHORT).show();
}
});
Log.d(TAG, "User Inputed Request\n"+call.request().url().toString());
}
private void SetUpRecipePreviews(final Results[] mRES) {
RecipePreviewsList.clear();
adapter = new PreviewRecipeAdapter(getActivity(),RecipePreviewsList);
SelectedItemRV.setLayoutManager(new GridLayoutManager(getActivity(), 2));
SelectedItemRV.setAdapter(adapter);
for (Results mRE : mRES) {
String ImgUrls = mRE.getImage();
RecipePreviewHolder obj = new RecipePreviewHolder(Integer.valueOf(mRE.getId()),
mRE.getTitle(), ImgUrls);
Log.d("GlideLogs->","Rid->"+mRE.getId());
Log.d("GlideLogs->","Img URL->"+ ImgUrls);
Log.d("GlideLogs->","Name->"+mRE.getTitle());
RecipePreviewsList.add(obj);
}
if(RecipePreviewsList.size()>1){
adapter.notifyDataSetChanged();
}
}
This is the Activity I transition to from my Fragment after clicking on a Recipe Card... Sending the Recipe ID in the extras. This function is called immediately after receiving intent extras.
private void RetrieveRecipeInfo(final int recipeID) {
String hostname = getResources().getString(R.string.spoonacular_host_name);
String key = getResources().getString(R.string.spoonacular_apikey_val);
final ServiceGenerator.GetDataService mService =
ServiceGenerator.createService(ServiceGenerator.GetDataService.class, hostname,key);
Call<RecipeDetailedInfo> call = mService.getInformation(185071);
Log.d(TAG , "Your GET Request:\n"+call.request().url().toString());
call.enqueue(new Callback<RecipeDetailedInfo>() {
#Override
public void onResponse(#NonNull Call<RecipeDetailedInfo> call, #NonNull Response<RecipeDetailedInfo> response)
{
Log.d(TAG,"OnResponse() Called\n");
Log.d(TAG,"Response = "+ response);
if(response.body()!=null) {
String obj = response.body().getSourceUrl();
Log.d(TAG,"Getting Recipe Info\n");
Log.d(TAG, String.valueOf(obj));
}
}
#Override
public void onFailure(#NonNull Call<RecipeDetailedInfo> call, #NonNull Throwable t){
}
});
}
Using postman I get the results every time but in my application the API stops responding after the first request. Is there a problem with the way I'm including headers?
So I finally got things working. The problem was with the HeadersInterceptor.java. I was using the Interceptor to add the Headers with the call but I found out a much easier way and it works like a charm.
Simply add #Header with the call to add headers without interceptor in Retrofit.
public interface GetDataService {
#GET("recipes/complexSearch?")
Call<RecipeInfoModel> getRecipes(
#Header("X-RapidAPI-Host") String api,
#Header("X-RapidAPI-Key") String apiKey,
#Query("query") String query_str);
}
I'm trying load posts from blog. I use mosby + retrofit + rxjava.
public class PostRepository implements IPostRepository {
private Api api;
private long last_id = 0;
private Single<List<Post>> postList;
public PostRepository(Api api) {
this.api = api;
}
#Override
public Single<List<Post>> getList() {
this.load();
return postList;
}
private void load() {
Single<List<Post>> tmp;
Log.d(Configuration.DEBUG_TAG, "Loading " + last_id);
tmp = api.getPostList(last_id)
.map(posts -> {
ArrayList<Post> postList = new ArrayList<>();
for (PostResponse post : posts) {
if (last_id == 0 || last_id > post.id) {
last_id = post.id;
}
postList.add(new Post(
post.id,
post.thumb,
post.created_at,
post.title
));
}
return postList;
});
if (postList == null) {
postList = tmp;
} else {
postList.mergeWith(tmp);
}
}
#Override
public Single<Post> getDetail(long id) {
return api.getPost(id)
.map(postResponse -> new Post(
postResponse.id,
postResponse.thumb,
postResponse.created_at,
postResponse.title,
postResponse.body
));
}
}
and api
public interface Api {
#GET("posts")
Single<PostListResponse> getPostList(#Query("last_id") long last_id);
#GET("post/{id}")
Single<PostResponse> getPost(#Path("id") long id);
}
First query to website is ok. https://site/posts?last_id=0
But second run function getList does not work.
I always get the same get query with last_id = 0, but line in console write
D/App: Loading 1416
D/App: 1416
D/OkHttp: --> GET https://site/posts?last_id=0 http/1.1
if i write
tmp = api.getPostList(1000)
then i get true query string https://site/posts?last_id=1000
Update
I rewrite code repository.
public class PostRepository implements IPostRepository {
private Api api;
private long last_id = 0;
private List<Post> postList = new ArrayList<>();
private Observable<List<Post>> o;
public PostRepository(Api api) {
this.api = api;
}
#Override
public Single<List<Post>> getList() {
return load();
}
private Single<List<Post>> load() {
return api.getPostList(last_id)
.map(posts -> {
for (PostResponse post : posts) {
if (last_id == 0 || last_id > post.id) {
last_id = post.id;
}
postList.add(new Post(
post.id,
post.thumb,
post.created_at,
post.title
));
}
return postList;
});
}
#Override
public Single<Post> getDetail(long id) {
return api.getPost(id)
.map(postResponse -> new Post(
postResponse.id,
postResponse.thumb,
postResponse.created_at,
postResponse.title,
postResponse.body
));
}
}
It's work
Your problem lies in this code fragment:
if (postList == null) {
postList = tmp;
} else {
postList.mergeWith(tmp); // here
}
Operators on observables are performing immutable operations, which means that it always returns new stream which is a modified version of the previous one. That means, that when you apply mergeWith operator, the result of this is thrown away as you are not storing it anywhere. The most easy to fix this is to replace the old postList variable with the new stream.
However, this is not optimal way of doing this. You should have a look on Subjects and emit new values within the old stream as your current solution will not affect previous subscribers as they have subscribed to a different stream
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.
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();}