Why can I not access this method from another class? - java

I have an Android app where I'm trying to make a GET request to my API using Retrofit2. I have copied some code online that allows me to add the access token to the body of the request as this is how my API is setup. Following the code online, the author is able to call the api variable from a different class but I am unable to.
Here is the class the api variable is defined:
cCustomerService.java
package com.example.dentdevils.helper.retrofit.oauth2.client;
import android.content.Context;
import android.content.SharedPreferences;
import com.example.dentdevils.helper.retrofit.oauth2.OauthConstant;
import com.example.dentdevils.helper.retrofit.oauth2.service.CustomerService;
import java.io.IOException;
import okhttp3.FormBody;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.Buffer;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.GET;
import static com.example.dentdevils.MainActivity.access;
import static com.example.dentdevils.MainActivity.mypreference;
public class cCustomerService {
public final CustomerService api;
private Context mContext;
SharedPreferences sharedPreferences;
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder requestBuilder = request.newBuilder();
mContext.getSharedPreferences(mypreference, Context.MODE_PRIVATE);
RequestBody formBody = new FormBody.Builder()
.add("access_token", sharedPreferences.getString(access, ""))
.build();
String postBodyToString = bodyToString(request.body());
postBodyToString += ((postBodyToString.length() > 0) ? "&" : "") + bodyToString(formBody);
request = requestBuilder.post(RequestBody.create(MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"), postBodyToString))
.build();
return chain.proceed(request);
}
}).build();
public static String bodyToString(final RequestBody requestBody) {
try {
final RequestBody copy = requestBody;
final Buffer buffer = new Buffer();
if (copy != null) {
copy.writeTo(buffer);
} else {
return "";
}
return buffer.readUtf8();
} catch (IOException e) {
return "didn't work";
}
}
public cCustomerService() {
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(OauthConstant.DATA_SERVER_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
api = retrofit.create(CustomerService.class);
}
}
As you can see I set the api variables value at the bottom.
Now I want to access it in the following class:
ViewCustomers.Java
package com.example.dentdevils;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import com.example.dentdevils.helper.retrofit.oauth2.OauthConstant;
import com.example.dentdevils.helper.retrofit.oauth2.client.cCustomerService;
import com.example.dentdevils.helper.retrofit.oauth2.response.CustomerResponse;
import com.example.dentdevils.helper.retrofit.oauth2.service.CustomerService;
import java.io.IOException;
import java.util.List;
import okhttp3.FormBody;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import static com.example.dentdevils.MainActivity.access;
import static com.example.dentdevils.MainActivity.mypreference;
import static com.example.dentdevils.helper.retrofit.oauth2.client.cCustomerService.bodyToString;
public class ViewCustomers extends AppCompatActivity {
private Context mContext;
SharedPreferences sharedPreferences;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_customers);
CustomerService customerService = api.create(CustomerService.class);
Call<List<CustomerResponse>> callArray = customerService.getCustomers();
callArray.enqueue(new Callback<List<CustomerResponse>>() {
#RequiresApi(api = Build.VERSION_CODES.N)
#Override
public void onResponse(Call<List<CustomerResponse>> call, retrofit2.Response<List<CustomerResponse>> response) {
if (response.isSuccessful())
{
List<CustomerResponse> customers = response.body();
customers.forEach(customer -> System.out.println(customer.getName()));
} else {
Log.e("TAG", "Failed!");
}
}
#Override
public void onFailure(Call<List<CustomerResponse>> call, Throwable t) {
Log.e("TAG", t.getLocalizedMessage());
}
});
}
}
As you can see I reference the api variable on this line CustomerService customerService = api.create(CustomerService.class); but I get an error saying it cannot resolve symbol 'api'. Like I said the author of the post has his setup exactly like mine so why isn't mine working? I added the relevant imports and I still can't access it. Any help would be appreciated.

Related

Unit testing - RestTemplate mock keeps retuning null

So Im writing unit test a class that does bankId authentication and the unit test looks like this.
package se.kt.client;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.HttpEntity;
import se.kt.common.vo.PublicApplicationForm;
import se.kt.models.BankIdAuthRequest;
import se.kt.models.BankIdAuthResponse;
import se.kt.models.BankIdCollectResponse;
import static org.mockito.ArgumentMatchers.any;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class BankIdClientTest {
private final RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
#InjectMocks
private BankIdClient bankIdClient;
#BeforeEach
public void setUp() {
}
#Test
public void testBankIdAuthentication_success() throws InterruptedException {
PublicApplicationForm form = new PublicApplicationForm();
form.setSsn("123456-7890");
form.setIp_address("123.123.123.123");
BankIdAuthRequest authRequest = bankIdClient.authRequestFromApplicationForm(form, "123");
BankIdAuthResponse authResponse = new BankIdAuthResponse();
authResponse.setOrderRef("123456");
BankIdCollectResponse collectResponse = new BankIdCollectResponse();
collectResponse.setStatus("completed");
Mockito.when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), any()))
.thenReturn(ResponseEntity.ok(authResponse));
Mockito.when(restTemplate.getForEntity(anyString(), any()))
.thenReturn(ResponseEntity.ok(collectResponse));
assertTrue(bankIdClient.bankIdAuthentication(authRequest));
}
}
And the class Im testing looks like this:
package se.kt.client;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import se.kt.common.domain.AbstractApplicationForm;
import se.kt.common.vo.PublicApplicationForm;
import se.kt.models.BankIdAuthRequest;
import se.kt.models.BankIdAuthResponse;
import se.kt.models.BankIdCollectResponse;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import java.util.Objects;
#Component
public class BankIdClient {
private static final Logger log = LoggerFactory.getLogger(BankIdClient.class);
private final RestTemplate customRestTemplate;
private static final String CONTENT_TYPE = "Content-Type";
#Value("${BankId.AuthUrl}")
private String bankIdAuthUrl;
#Value("${BankId.CollectUrl}")
private String bankIdCollectUrl;
#Value("${BankId.SecretKey}")
private String bankIdSecretKey;
public BankIdClient(RestTemplate customRestTemplate) {
this.customRestTemplate = customRestTemplate;
}
public BankIdAuthRequest authRequestFromApplicationForm(PublicApplicationForm form, String jobId) {
BankIdAuthRequest bankIdAuthRequest = new BankIdAuthRequest();
bankIdAuthRequest.setPno(form.getSsn());
bankIdAuthRequest.setIpAddress(form.getIp_address());
bankIdAuthRequest.setRefID(jobId);
bankIdAuthRequest.setSecretKey(bankIdSecretKey);
bankIdAuthRequest.setAvsikt("Kt application");
return bankIdAuthRequest;
}
public boolean bankIdAuthentication(BankIdAuthRequest bankIdAuthRequest) throws InterruptedException {
//Setup header and body for request.
HttpHeaders headers = new HttpHeaders();
headers.add(CONTENT_TYPE, MediaType.APPLICATION_JSON.toString());
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
try {
String bankIdAuthFormJson = ow.writeValueAsString(bankIdAuthRequest);
HttpEntity<String> httpEntity = new HttpEntity<>(bankIdAuthFormJson, headers);
ResponseEntity<BankIdAuthResponse> authResponse = customRestTemplate.postForEntity(bankIdAuthUrl, httpEntity, BankIdAuthResponse.class);
bankIdCollectUrl += Objects.requireNonNull(authResponse.getBody()).getOrderRef();
ResponseEntity<BankIdCollectResponse> collectResponse;
do {
collectResponse = customRestTemplate.getForEntity(bankIdCollectUrl, BankIdCollectResponse.class);
Thread.sleep(1500);
if (Objects.requireNonNull(collectResponse.getBody()).getStatus().equals("completed"))
return true;
if (Objects.requireNonNull(collectResponse.getBody()).getStatus().equals("failed"))
return false;
} while (Objects.requireNonNull(collectResponse.getBody()).getStatus().equals("progress"));
} catch (JsonProcessingException e) {
log.info(e.getMessage());
} catch (NullPointerException e) {
log.info(e.toString());
log.info("BankId API not responding correctly. Check server connection");
}
return false;
}
public void cancelBankIdAuthentication(#Value("${BankId.CancelUrl}") String bankIdCancelUrl) {
customRestTemplate.postForEntity(bankIdCancelUrl, null, String.class);
}
}
Now for some reson this line:
ResponseEntity<BankIdAuthResponse> authResponse = customRestTemplate.postForEntity(bankIdAuthUrl, httpEntity, BankIdAuthResponse.class);
keeps producing the result authResponse = null indicating that
Mockito.when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), any()))
.thenReturn(ResponseEntity.ok(authResponse));
is not working. Now I have been playing around with this for over 3h and i still get the same result. What could i be doing wrong?
#InjectMocks only works with #Mock-annotated fields, not with fields which get assigned a manually created mock. Therefore you need to change:
private final RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
#InjectMocks
private BankIdClient bankIdClient;
to
#Mock
private RestTemplate restTemplate;
#InjectMocks
private BankIdClient bankIdClient;
I also recommend reading Why is my class not calling my mocked methods in unit test? which provides additional insights and points out some common mistakes when using Mockito or mocks in general.

assert output response sling servlet filter

I am trying to assert output of the below transformed response from sling servlet filter. I am not able to get hold of output response and not able to assert.
import com.day.cq.wcm.api.Page;
import com.safeway.app.rxwa.pharmacy.utils.HttpServletResponseCopier;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestPathInfo;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.engine.EngineConstants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* This filter rewrites link for the Content Fragment list model exporter
*/
#Component(service = Filter.class,
immediate = true,
configurationPolicy = ConfigurationPolicy.REQUIRE,
property = {
EngineConstants.SLING_FILTER_SCOPE + "=" + EngineConstants.FILTER_SCOPE_REQUEST,
EngineConstants.SLING_FILTER_METHODS + "=GET",
EngineConstants.SLING_FILTER_PATTERN + "=/content/experience-fragments/rxwa/.*",
EngineConstants.SLING_FILTER_SELECTORS + "=model",
EngineConstants.SLING_FILTER_EXTENSIONS + "=json"})
#Designate(ocd = ContentJsonLinkRewriterFilter.Config.class)
public class ContentJsonLinkRewriterFilter implements Filter {
private static final Logger logger =
LoggerFactory.getLogger(ContentJsonLinkRewriterFilter.class);
private Config config;
private List<String> resourceTypes;
#Reference
private ResourceResolverFactory resolverFactory;
#Activate
public void activate(Config config) {
this.config = config;
this.resourceTypes = Arrays.asList(config.resourceTypes());
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
// no-op
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
String resultString = "{}";
// add author check
if (!(response instanceof SlingHttpServletResponse) || !(request instanceof SlingHttpServletRequest)) {
throw new IllegalStateException("Filter not properly registered as Sling Servlet Filter");
}
if (!config.enabled()) {
logger.debug("Filter disabled");
filterChain.doFilter(request, response);
return;
}
SlingHttpServletRequest slingHttpServletRequest = (SlingHttpServletRequest) request;
Resource currentResource = slingHttpServletRequest.getResource();
String currentResourceType = currentResource.getResourceType();
if(currentResource.isResourceType("cq:Page")) {
Page page = slingHttpServletRequest.getResource().adaptTo(Page.class);
currentResourceType = page.getContentResource().getResourceType();
}
String finalProcessResourceType = currentResourceType;
if (resourceTypes.stream().noneMatch(resourceType -> StringUtils.startsWith(finalProcessResourceType, resourceType))) {
logger.debug("Current resource path {} is not configured to be evaluated", currentResourceType);
filterChain.doFilter(request, response);
return;
}
// Wrap Response Class before servlet gets called
HttpServletResponseCopier responseCopier = new HttpServletResponseCopier((SlingHttpServletResponse) response);
filterChain.doFilter(request, responseCopier);
// externalize links after the servlet finishes
responseCopier.flushBuffer();
//read original response
byte[] responseBytes = responseCopier.getCopy();
String responseString = new String(responseBytes, responseCopier.getCharacterEncoding());
if (StringUtils.isNotEmpty(responseString)) {
//replace all links
resultString = responseString.replaceAll(StringUtils.substring(slingHttpServletRequest.getRequestURI(),0, slingHttpServletRequest.getRequestURI().lastIndexOf("/")), "");
}
responseCopier.resetBuffer();
responseCopier.getOutputStream().write(resultString.getBytes());
responseCopier.setContentType("application/json");
responseCopier.setCharacterEncoding("utf-8");
}
#Override
public void destroy() {
// no-op
}
#ObjectClassDefinition(name = "Content Fragment List Link Rewriter Filter",
description = "Configuration for filter to extend Content Fragment List functionality to rewrite links")
public #interface Config {
#AttributeDefinition(name = "Enabled",
description = "If this filter should not be active, rather try to delete this config. " + "Only in cases " +
"where this cannot be easily accomplished uncheck this option to disable the filter.") boolean enabled() default false;
#AttributeDefinition(name = "Resource Types",
description = "Resource Types to be evaluated by the filter.") String[] resourceTypes();
}
}
Unit test I tried
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import com.safeway.app.rxwa.pharmacy.utils.HttpServletResponseCopier;
import io.wcm.testing.mock.aem.junit.AemContext;
import org.apache.commons.io.IOUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.testing.mock.osgi.MapUtil;
import org.apache.sling.testing.mock.sling.loader.ContentLoader;
import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.wcm.api.components.ComponentContext;
import com.day.cq.wcm.api.designer.Design;
import com.day.cq.wcm.api.designer.Designer;
import com.google.common.collect.Sets;
import org.mockito.junit.MockitoJUnitRunner;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
#RunWith(MockitoJUnitRunner.class)
public class ContentJsonLinkRewriterFilterTest {
protected static final String CONTENT_ROOT = "/content/experience-fragments/rxwa";
public Map<String, Object> props = new HashMap<>();
#Rule
public AemContext context = new AemContext();
#Mock
FilterChain chain;
public ContentJsonLinkRewriterFilter filter;
#Before
public void setUp() {
context.load().json("/test-page-content.json", "/content/experience-
fragments/rxwa");
props.clear();
props.put("enabled", "true");
props.put("resourceTypes", new String[] {"rxwa-
pharmacy/components/structure/xfpage"});
ContentJsonLinkRewriterFilter aei = context.registerInjectActivateService(new
ContentJsonLinkRewriterFilter(), props);
filter = Mockito.spy(aei);
}
#Test
public void doFilterCall() throws ServletException, IOException {
context.requestPathInfo().setResourcePath("/content/experience-fragments/rxwa/templated-
page");
context.requestPathInfo().setSelectorString("model");
context.requestPathInfo().setExtension("json");
context.request().setMethod("GET");
context.response().setCharacterEncoding("UTF-8");
context.currentPage("/content/experience-fragments/rxwa/templated-page");
Mockito.doAnswer(invocation -> {
HttpServletResponseWrapper httpServletResponseWrapper =
(HttpServletResponseWrapper) invocation.getArguments()[1];
String testHtmlContent = IOUtils.toString(
ContentLoader.class.getResourceAsStream("/test-page-content.json"),
StandardCharsets.UTF_8
);
httpServletResponseWrapper.getWriter()
.write(testHtmlContent);
httpServletResponseWrapper.getWriter().flush();
return null;
}).when(chain).doFilter(Mockito.any(), Mockito.any());
filter.doFilter(context.request(), context.response(), chain);
Mockito.verify(chain).doFilter(Mockito.any(), Mockito.any());
String testHtmlContent2 = IOUtils.toString(
ContentLoader.class.getResourceAsStream("/test-page-content.json"),
StandardCharsets.UTF_8
);
}
}

Android MVVM architecture and observing changes on data from an API

I'm new to the Android MVVM architecture. I have an API running locally with data ("deals") in it. I'd like to simply make a request to the API and display that data in a text field. Currently the data does not show up when the fragment is first loaded, but if I go to another activity and then back to the fragment it loads.
There are 3 classes of importance here.
DashboardViewModel.java:
package com.example.android_client.ui.dashboard;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.example.android_client.models.Deal;
import com.example.android_client.repository.Repository;
import java.util.List;
public class DashboardViewModel extends ViewModel {
private MutableLiveData<String> mText;
private Repository repository;
private MutableLiveData<List<Deal>> deals = null;
public void init() {
if(this.deals == null) {
this.repository = Repository.getInstance();
this.deals = this.repository.getDeals();
}
}
public DashboardViewModel() {
this.mText = new MutableLiveData<>();
}
public LiveData<List<Deal>> getDeals() {
return this.deals;
}
}
DashboardFragment.java:
package com.example.android_client.ui.dashboard;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import com.example.android_client.R;
import com.example.android_client.models.Deal;
import java.util.List;
public class DashboardFragment extends Fragment {
private DashboardViewModel dashboardViewModel;
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_dashboard, container, false);
final TextView textView = root.findViewById(R.id.text_dashboard);
dashboardViewModel = ViewModelProviders.of(this).get(DashboardViewModel.class);
dashboardViewModel.init();
dashboardViewModel.getDeals().observe(this, new Observer<List<Deal>>() {
#Override
public void onChanged(List<Deal> deals) {
if (deals != null && !deals.isEmpty()) {
System.out.println(deals.get(0).toString());
textView.setText(deals.get(0).toString());
}
}
});
return root;
}
}
and Repository.java:
package com.example.android_client.repository;
import androidx.lifecycle.MutableLiveData;
import com.example.android_client.models.Deal;
import com.google.gson.Gson;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class Repository {
private static Repository instance;
private ArrayList<Deal> dealsList = new ArrayList<>();
private final OkHttpClient client = new OkHttpClient();
public static Repository getInstance() {
if(instance == null) {
instance = new Repository();
}
return instance;
}
private Repository() {}
public MutableLiveData<List<Deal>> getDeals() {
setDeals();
MutableLiveData<List<Deal>> deals = new MutableLiveData<>();
deals.setValue(dealsList);
return deals;
}
private void setDeals() {
Request request = new Request.Builder()
.url("http://10.0.2.2:8000/api/deals?<params here>")
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(#NotNull Call call, #NotNull IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(#NotNull Call call, #NotNull Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
String jsonDeals = responseBody.string(); // can only call string() once or you'll get an IllegalStateException
Deal[] deals = new Gson().fromJson(jsonDeals, Deal[].class);
dealsList = new ArrayList<>(Arrays.asList(deals));
}
}
});
}
}
When stepping through the code in the Repository class I can see that setDeals() is called when I load the fragment, and the request in the callback is queued. The first time getDeals() returns, it returns a list of 0 deals (within the MutableLiveData object).
onResponse in the callback doesn't run until the fragment is already loaded. When debugging I can see that the data is in the objects (all the Gson stuff works fine), but onChanged doesn't get called again (which sets the text view).
Am I not observing changes on the deals properly?
Your code is not working due to a new live data instance be created whenever getDeals() is called and the api response value be informed to other live data instance. You must set api response value to same instance of MutableLiveData returned by getDeals()
I'm not saying that it is the best architectural solution, but if you create a mutable live data as a class attribute and return it whenever getDeals() is called. Probably, it's going to work.
Also, a good practice is return a LiveData and not a MutableLiveData to not allowing a external component modify the internal value.
Please, take a look at the piece of code below.
OBS: Maybe, there is some syntax error, because I have not compiled it
import com.example.android_client.models.Deal;
import com.google.gson.Gson;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class Repository {
private static Repository instance;
private ArrayList<Deal> dealsList = new ArrayList<>();
private final OkHttpClient client = new OkHttpClient();
private MutableLiveData<List<Deal>> _deals = new MutableLiveData<>();
private LiveData<List<Deal>> deals = _deals
public static Repository getInstance() {
if(instance == null) {
instance = new Repository();
}
return instance;
}
private Repository() {}
public LiveData<List<Deal>> getDeals() {
setDeals();
return deals;
}
private void setDeals() {
Request request = new Request.Builder()
.url("http://10.0.2.2:8000/api/deals?<params here>")
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(#NotNull Call call, #NotNull IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(#NotNull Call call, #NotNull Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
String jsonDeals = responseBody.string(); // can only call string() once or you'll get an IllegalStateException
Deal[] deals = new Gson().fromJson(jsonDeals, Deal[].class);
dealsList = new ArrayList<>(Arrays.asList(deals));
_deals.setValue(dealsList);
}
}
});
}
}
When
I think this would help. Try postValue on MutableLiveData in onResponse of network call. Please change your repository class like below:
package com.example.android_client.repository;
import androidx.lifecycle.MutableLiveData;
import com.example.android_client.models.Deal;
import com.google.gson.Gson;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class Repository {
private static Repository instance;
private ArrayList<Deal> dealsList = new ArrayList<>();
private final OkHttpClient client = new OkHttpClient();
MutableLiveData<List<Deal>> deals = new MutableLiveData<>();
public static Repository getInstance() {
if(instance == null) {
instance = new Repository();
}
return instance;
}
private Repository() {}
private MutableLiveData<List<Deal>> getDeals() {
Request request = new Request.Builder()
.url("http://10.0.2.2:8000/api/deals?<params here>")
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(#NotNull Call call, #NotNull IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(#NotNull Call call, #NotNull Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
String jsonDeals = responseBody.string(); // can only call string() once or you'll get an IllegalStateException
Deal[] deals = new Gson().fromJson(jsonDeals, Deal[].class);
dealsList = new ArrayList<>(Arrays.asList(deals));
deals.postValue(dealsList);
}
}
});
return deals;
}
}
in your repository class in function get deals. you are initializing live data. requesting url in background thread and posting value on live data which is not received from server yet.
to solve this create livedata instance in constructor of repository and postvalue on livedata in onResponse callback.
//sorry for bad writting, posted from mobile.

#Autowired get null value

In Login class #Autowired is not null but when I try to use postman call Logout class #Autowired get null. I copy code from Login to Logout class just change variable name all code is same pattern I don't know what happening
package xxx.api.login
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
import xxx.api.domain.logout.LoginRequest;
import xxx.api.domain.logout.LoginResponse;
import xxx.converter.FieldValidation;
import xxx.dto.log.MessageLog;
import xxx.logging.LogFactory;
#Component
public class LoginClient {
#Autowired
FieldValidation fieldValidation;
public LoginResponse login(LoginRequest loginRequest) throws Exception {
ObjectMapper mapper = new ObjectMapper();
final String url = "http://localhost:xxx/xxx/login";
LoginResponse responseObject = null;
try {
String requestData = mapper.writeValueAsString(loginRequest);
responseObject = login(url, requestData);
fieldValidation.validateResponse(responseObject, "login");
// 'fieldValidation' not null
This is my Login class. Return result as I expect.
package xxx.api.logout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
import xxx.api.domain.logout.LogoutRequest;
import xxx.api.domain.logout.LogoutResponse;
import xxx.converter.FieldValidation;
import xxx.dto.log.MessageLog;
import xxx.logging.LogFactory;
#Component
public class LogoutClient {
#Autowired
FieldValidation fieldValidation;
public LogoutResponse logout(LogoutRequest logoutRequest) throws Exception {
ObjectMapper mapper = new ObjectMapper();
final String url = "http://localhost:xxx/xxx/logout";
LogoutResponse responseObject = null;
try {
String requestData = mapper.writeValueAsString(logoutRequest);
responseObject = logout(url, requestData);
fieldValidation.validateResponse(responseObject, "logout");
// 'fieldValidation' is null then throws nullPointerException
This is my Logout class
package xxx.api.logout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xxx.api.domain.logout.LogoutRequest;
import xxx.api.domain.logout.LogoutResponse;
import xxx.dto.log.MessageLog;
import xxx.exception.common.ErrorException;
import xxx.api.logout.LogoutClient;
import xxx.converter.FieldValidation;
#RestController
#RequestMapping(path = "/xxx")
public class LogoutServer {
#Autowired
FieldValidation fieldValidation;
#Autowired
WSLogFactory wsLog;
#PostMapping(value = "/logout", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<?> logout(#RequestHeader(value = "requestId") String requestId,
#RequestBody(required = true) LogoutRequest logoutRequest) throws ErrorException, Exception {
writeLogRequest(logoutRequest, logoutRequest.getClientIP(), "Request", "/xxx/logout");
fieldValidation.validateRequest(logoutRequest, "logout");
try {
LogoutClient logoutClient = new LogoutClient();
LogoutResponse response = logoutClient.logout(logoutRequest);
return new ResponseEntity<LogoutResponse>(response, HttpStatus.OK);
} catch (Exception e) {
throw e;
}
}
This is Logout Controller
#org.springframework.context.annotation.Configuration
#PropertySource("file:${layout.properties_file}")
public class FieldValidation {
// do somethings
}
This is my FieldValidation class.
{
"timestamp": 1550490230074,
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.NullPointerException",
"message": "No message available",
"path": "/umm/logout"
}
This is return when I call my Logout class.
This is my project structure
xxx.api.login <<< LoginClient.java
xxx.api.logout <<< LogoutClient.java
xxx.converter <<< FieldValidation.java
This is my main program
package xxx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
I try to replace #Component to #Configuration but still doesn't works.
I don't need 'fieldValidation' in Logout class or another class be a null value.
package xxx.converter;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.Year;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.springframework.context.annotation.Configuration;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.validator.routines.BigDecimalValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import com.fasterxml.jackson.databind.ObjectMapper;
import xxx.exception.common.ErrorException;
#Configuration
#PropertySource("file:${layout.properties_file}")
public class FieldValidation {
public void validateObject(Object objResponse, String config) throws Exception {
try {
List<Configuration> confs = objectMapper.readValue(config,
objectMapper.getTypeFactory().constructCollectionType(List.class, Configuration.class));
for (int i = 0; i < confs.size(); i++) {
Configuration configuration = confs.get(i);
Object objValue = getValue(configuration.getFieldName(), objResponse);
Validation validation = new Validation();
BeanUtils.copyProperties(validation, configuration);
isValid(objValue, validation);
if (configuration.getType().equalsIgnoreCase("object")) {
List<Validation> validations = configuration.getValidation();
Class<?> act = Class.forName(configuration.getClassName());
Object objectConfig = act.cast(objValue);
validateObject(objectConfig, validations);
}
}
} catch (Exception e) {
throw e;
}
}
This is full import of FieldValidation class and replace #Configuration and I get this error.
configuration.getFieldName() << error The method is undefined for type Configuration
configuration.getType() << error The method is undefined for type Configuration
configuration.getValidation() << error The method is undefined for type Configuration
configuration.getClassName() << error The method is undefined for type Configuration

Java : Android : Retrofit 2 response code is 200, response.body is null

I am trying to build an Android App that implements a Collaborative Filtering Algorithm using Retrofit 2,Realm and The Movie Database API.
When making my Retrofit callback, onResponse returns a successful status code (200), but from logging I get that my response.body().getResults returns null. I'm in this pickle now and I can't get it to work.My ApiService seem to be working fine and I make other retrofit callbacks to get directors,movies by title,movies by release date.Anyways, here is some code snippets that might be helpful.
APIService.java
package com.yannis.thesis.movierecommendationapp.api;
import com.yannis.thesis.movierecommendationapp.models.DirectorResponse;
import com.yannis.thesis.movierecommendationapp.models.GenreResponse;
import com.yannis.thesis.movierecommendationapp.models.Movie;
import com.yannis.thesis.movierecommendationapp.models.MovieResponse;
import com.yannis.thesis.movierecommendationapp.models.PrimaryMovieInfo;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.Query;
public interface APIService {
#POST("/list")
Call<Movie> loadMovie();
#GET("movie/top_rated")
Call<MovieResponse> getTopRatedMovies(#Query("api_key") String apiKey);
#GET("movie/popular")
Call<MovieResponse> getPopularMovies(#Query("api_key") String apiKey);
#GET("movie/{id}")
Call<MovieResponse> getMovieDetails(#Path("id") int id, #Query("api_key") String apiKey);
#GET("search/movie")
Call<MovieResponse> getMovieByTitle(#Query("query") String title, #Query("api_key") String apiKey);
}
MovieResponse.java
package com.yannis.thesis.movierecommendationapp.models;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class MovieResponse {
#SerializedName("page")
private Integer page;
#SerializedName("results")
private List<Movie> results;
#SerializedName("total_results")
private Integer totalResults;
#SerializedName("total_pages")
private Integer totalPages;
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public List<Movie> getResults() {
return results;
}
public void setResults(List<Movie> results) {
this.results = results;
}
public Integer getTotalResults() {
return totalResults;
}
public void setTotalResults(Integer totalResults) {
this.totalResults = totalResults;
}
public Integer getTotalPages() {
return totalPages;
}
public void setTotalPages(Integer totalPages) {
this.totalPages = totalPages;
}
}
MovieRecommendationApp.java
package com.yannis.thesis.movierecommendationapp;
import android.app.Application;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import com.luseen.logger.LogType;
import com.luseen.logger.Logger;
import com.weiwangcn.betterspinner.library.BetterSpinner;
import com.yannis.thesis.movierecommendationapp.activities.BaseActivity;
import com.yannis.thesis.movierecommendationapp.activities.MainActivity;
import com.yannis.thesis.movierecommendationapp.api.APIService;
import com.yannis.thesis.movierecommendationapp.models.DirectorResponse;
import com.yannis.thesis.movierecommendationapp.models.DirectorResult;
import com.yannis.thesis.movierecommendationapp.models.Genre;
import com.yannis.thesis.movierecommendationapp.models.GenreResponse;
import com.yannis.thesis.movierecommendationapp.models.MainPagerEnum;
import com.yannis.thesis.movierecommendationapp.models.Movie;
import com.yannis.thesis.movierecommendationapp.models.MovieResponse;
import com.yannis.thesis.movierecommendationapp.models.MovieRecommendedForUser;
import com.yannis.thesis.movierecommendationapp.models.Recommendation;
import com.yannis.thesis.movierecommendationapp.models.User;
import com.yannis.thesis.movierecommendationapp.models.UserRatesMovie;
import com.yannis.thesis.movierecommendationapp.MovieRecommendationApp;
import com.yannis.thesis.movierecommendationapp.R;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmQuery;
import io.realm.RealmResults;
import io.realm.Sort;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class MovieRecommendationApp extends Application {
private static MovieRecommendationApp instance;
public BaseActivity lastActivity;
String API_BASE_URL = "http://api.themoviedb.org/3/";
private final static String API_KEY = "******************";
private static Retrofit retrofitinstance;
private String loggedInUserId;
private Realm realm;
final Double SIMILARITY_PILLOW = 0.5;
final Double PREDICTION_PILLOW = 3.0;
private APIService client;
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(
GsonConverterFactory.create()
);
Retrofit retrofit =
builder
.client(
httpClient.build()
)
.build();
retrofit2.Call<MovieResponse> call;
#Override
public void onCreate() {
super.onCreate();
instance = this;
Realm.init(this);
RealmConfiguration config = new RealmConfiguration.Builder()
.name("myrealmDB.realm")
.deleteRealmIfMigrationNeeded()
.build();
Realm.setDefaultConfiguration(config);
new Logger.Builder()
.isLoggable(BuildConfig.DEBUG)
.logType(LogType.WARN)
.tag("Iamerror")
.build();
realm = Realm.getDefaultInstance();
MovieRecommendationAlgorithm();
}
public static MovieRecommendationApp getInstance() {
return instance;
}
public static Retrofit getRetrofitInstance() {
return retrofitinstance;
}
public static String getApiKey() {
return API_KEY;
}
public void MovieRecommendationAlgorithm() {
prediction("activeUserId","637",neighbours);
}
public void prediction(String activeUserId, String notYetRatedMovieId,ArrayList<String> neightbours) {
Double activeAVG = avgRating(activeUserId);
Double A = 0.0;
Double B = 0.0;
for (int i = 0; i < neightbours.size(); i++) {
avgRating(neightbours.get(i));
A = A + similarity(activeUserId, neightbours.get(i)) * (getUser_i_MovieRating(neightbours.get(i), notYetRatedMovieId) - avgRating(neightbours.get(i)));
B = B + similarity(activeUserId, neightbours.get(i));
}
final Double prediction = activeAVG + A / B;
if (prediction < PREDICTION_PILLOW) {
return;
}
int movieId = Integer.parseInt(notYetRatedMovieId);
client = retrofit.create(APIService.class);
call = client.getMovieDetails(movieId, MovieRecommendationApp.getApiKey());
call.enqueue(new retrofit2.Callback<MovieResponse>() {
#Override
public void onResponse(retrofit2.Call<MovieResponse> call, retrofit2.Response<MovieResponse> response) {
int statusCode = response.code();
if (response.isSuccessful() == false) {
Logger.w("unsuccessful w status", String.valueOf(statusCode));
} else {
//problem is in this spot
Logger.w( " reponse body is " + response.body().getResults());
}
// Logger.e("Number of movies received: " + movies.size());
}
#Override
public void onFailure(retrofit2.Call<MovieResponse> call, Throwable t) {
}
});
}
}
I double checked the API call using Postman - calling https://api.themoviedb.org/3/movie/637?api_key=*********&language=en-US
got me the desired JSON Body.
Thank you for you time and help.
I don't know if this will be helpfull, but here: if (response.isSuccessful() == false)
It's a quite strange compare a boolean method isSuccessful() with false, the method already return a boolean.
After searching at the movie database api forum , I found this [https://www.themoviedb.org/talk/5667650ec3a36836970002bc][1]
in which it is stated that "The only way we currently support is via query parameters." and there is a big possibility for my problem to be related with the fact that I am using a #Path parameter and not a #Query parameter.
So now the million dollar question is how to convert the
#GET("movie/{id}")
Call<MovieResponse> getMovieDetails(#Path("id") int id, #Query("api_key") String apiKey);
to a Call using the #Query annotation.

Categories