While i was learning Dagger2 I made a naive service class that provides data assynchronously (in this case jokes from a funny api) but I encountered a problem and I kind of stuck with it. I'm using retrofit2 for requesting data from network.
But I can't figure out how to pull out the joke object retrieved from network (via response.body()), from anonymous internal class, into joke instance variable of the external class. I'm getting NullPointerException:
public class ChuckNorrisJokeService {
private Joke joke;
public String getJoke() {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.chucknorris.io")
.build();
JokeService jokeService = retrofit.create(JokeService.class);
Call<Joke> call = jokeService.provideJoke();
call.enqueue(new Callback<Joke>() {
#Override
public void onResponse(Call<Joke> call, Response<Joke> response) {
joke = response.body();
}
#Override
public void onFailure(Call<Joke> call, Throwable t) {
System.out.println(t.getMessage());
}
});
return joke.getContent();
}
}
The Joke class is a simple POJO:
public class Joke {
#SerializedName("value")
private String content;
public String getContent() {
return content;
}
}
P.S. When calling synchronously the result is successful. How can I achieve the same functionality asynchronously?
P.S.S. I read this but it doesn't work for me and is so dirty.
The stacktrace is this:
Exception in thread "main" java.lang.NullPointerException
at com.alic.ChuckNorrisJokeService.getJoke(ChuckNorrisJokeService.java:41)
at com.alic.Application.run(Application.java:11)
at com.alic.Main.main(Main.java:6)
The Application and Main classes are very simple:
public class Application {
private ChuckNorrisJokeService chuckNorrisJokeService;
public Application() {
this.chuckNorrisJokeService = new ChuckNorrisJokeService();
}
public void run() {
System.out.println(chuckNorrisJokeService.getJoke());
}
}
and the Main class:
public class Main {
public static void main(String[] args) {
Application app = new Application();
app.run();
}
}
Related
I have an example ,I want to understand some parts,In this exampl ,It was working fine ,but when I changed part
from:
call list<model>> method();
to:
call <model> method();
It caused an error ,What's the reason for that?
What is the difference between the two cases?
// MainActivity :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GetData service = RetrofitClient.getRetrofitInstance().create(GetData.class);
Call<RetroUsers> call = service.getAllUsers();
call.enqueue(new Callback<RetroUsers>() {
#Override
public void onResponse(Call<RetroUsers> call, Response<RetroUsers> response) {
Log.i("print", "" + response.body().getUser());
}
#Override
public void onFailure(Call<RetroUsers> call, Throwable t) {
Log.i("print", "Dont" + t.getMessage());
}
});
}
///Error message :
I/print: Dontjava.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $
// interface GetData:
public interface GetData {
#GET("/users")
Call<RetroUsers>getAllUsers();
/*
#GET("/users")
Call<List<RetroUsers>> getAllUsers();
*/
}
// RetrofitClient :
public class RetrofitClient {
private static Retrofit retrofit;
private static final String BASE_URL = "https://jsonplaceholder.typicode.com";
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
// model class :
public class RetroUsers {
#SerializedName("name")
private String name;
public RetroUsers(String name) {
this.name = name;
}
public String getUser() {
return name;
}
public void setUser(String name) {
this.name = name;
}
}
This: Call<RetroUsers> getAllUsers();
will cause you an error because the getAllUsers() will return more than one records of RetroUsers. Due to which it requires you to provide it a type as List so that its datatype is set to List and it can then handle multiple records.
Go through basics of Core Java to better understand this.
In one case you tell the deserializer that you expect a single Model, in the other case you tell it to expect a list of models, aka a List<Model>. Depending on what data you actually get you need to use one or the oter.
Of course you can "hide" the List<...> within your model by not using List<Model> but:
public class MoreThanOneModel {
public List<Model> entries;
...
}
But that does not change the underlying reasoning.
i try this for just 1 time create retrofit but i have error
i want call my retrofit class and give endPoint of url and body class , and get body from server clearly
ApiClient
public class ApiClient {
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(App.SERVER)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
ApiService
public interface ApiService {
#POST("{urlEndPoint}")
<C, T> Call<C> request(#Body T body, #Path("urlEndPoint") String urlEndPoint);
}
Retrofit Object
public class Request<C,T> {
private C c = null;
public C rest(T body, String urlEndPoint) {
ApiService apiService = ApiClient.getClient().create(ApiService.class);
Call<C> call = apiService.request(body, urlEndPoint);
call.enqueue(new Callback<C>() {
#Override
public void onResponse(Call<C> call, Response<C> response) {
if (response.isSuccessful())
c = response.body();
else
Toaster.shorter(App.context.getString(R.string.serverError));
#Override
public void onFailure(Call<C> call, Throwable t) {
Toaster.shorter(App.context.getString(R.string.connectionError));
}
});
return c;
}
}
calling method:
private void requestForCode() {
Request request = new Request();
int i = (int) request.rest(App.car, "/Rest/ReturnActivationCode");
if (i == 0)
Toaster.longer(App.context.getString(R.string.validateYourNumber));
else
Toaster.shorter(App.context.getString(R.string.serverError));
}
error:
12-05 12:18:04.119 773-907/? E/ConnectivityService: RemoteException caught trying to send a callback msg for NetworkRequest [ id=535, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
12-05 12:18:09.575 10359-10359/com.rayanandisheh.peysepar E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.rayanandisheh.peysepar, PID: 10359
java.lang.IllegalArgumentException: Method return type must not include a type variable or wildcard: retrofit2.Call<C>
for method ApiService.request
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:755)
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:746)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:229)
at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:165)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:170)
at retrofit2.Retrofit$1.invoke(Retrofit.java:147)
at java.lang.reflect.Proxy.invoke(Proxy.java:393)
at $Proxy0.request(Unknown Source)
retrofit don't support generic objects???
It seems that you're trying to minimize your boilerplate by having a generic function to be called, but there's a better way to do this.
First, you're encapsulating the retrofit setup with your:
#POST("{urlEndPoint}")
<C, T> Call<C> request(#Body T body, #Path("urlEndPoint") String urlEndPoint);
And then you're calling it with the function you created:
request.rest(App.object1, "endpoint");
But actually, this will just make things complicated and the code is very tightly coupled. You will still need to call the same method on every different APIs (request.rest(App.object2, "endpoint2"), request.rest(App.object3, "endpoint3")). This also limits the capability of retrofit (such as multiple params, customize headers, etc). What you can do is just follow the setup of retrofit:
#POST("yourendpoint")
Call<YourObjectResp> saveObject(#Body YourObjectParam param)
And to minimize your boilerplate, I suggest to make it functional:
Call<YourObjectResp> call = apiService.saveObject(new YourObjectParam());
call.enqueue(new ApiServiceOperator<>(new
ApiServiceOperator.OnResponseListener<YourObjectResp>() {
#Override
public void onSuccess(YourObjectResp body) {
// do something with your response object
}
#Override
public void onFailure(Throwable t) {
// here, you can create another java class to handle the exceptions
}
}));
And for your ApiServiceOperator.java:
/**
* Handles retrofit framework response.
* Extract the body if success, otherwise throw an exception.
*/
public class ApiServiceOperator<T> implements Callback<T> {
interface OnResponseListener<T> {
void onSuccess(T body);
void onFailure(Throwable t);
}
private OnResponseListener<T> onResponseListener;
public ApiServiceOperator(OnResponseListener<T> onResponseListener) {
this.onResponseListener = onResponseListener;
}
#Override
public void onResponse(#NonNull Call<T> call, #NonNull Response<T> response) {
if (response.isSuccessful()) { // here, do the extraction of body
onResponseListener.onSuccess(response.body());
} else {
onResponseListener.onFailure(new ServerErrorException());
}
}
#Override
public void onFailure(#NonNull Call<T> call, #NonNull Throwable t) {
onResponseListener.onFailure(new ConnectionErrorException());
}
// these exception can be on a separate classes.
public static class ServerErrorException extends Exception {
}
public static class ConnectionErrorException extends Exception {
}
}
With these setup, you still minimize your boilerplate and also, it makes thing reusable, scalable, and testable. ApiServiceOperator also is loosely couple with Android Context and instead, throws a plain java exception, in which, you can create a function that knows Android Context to get the appropriate message base on the exception thrown.
I'm getting JSON data from a remote API. For this I'm using the Observer pattern. I created an Observer called WordTranslationObserver that gets the JSON data from the callback method. The problem is that I don't know how to get this data in the Main class...
In my Main class I can't implement PropertyChangeListener and use a translationModel.addChangeListener(this) because I'm in a static context, so "this" can't work.
What is the proper way to be able to get my data from translation.getText() in my Main class ?
Main class
public class Main {
public static void main(String[] args) throws IOException {
WordTranslation wordTranslation = new WordTranslation();
WordTranslationObserver myObserver = new WordTranslationObserver(wordTranslation);
wordTranslation.translate("sắt", "vie", "eng");
}
}
Observer
public class WordTranslationObserver implements PropertyChangeListener {
public WordTranslationObserver(WordTranslation translationModel) {
translationModel.addChangeListener(this);
}
#Override
public void propertyChange(PropertyChangeEvent event) {
System.out.println("Changed property: " + event.getPropertyName());
ArrayList<Translation> translations = (ArrayList<Translation>) event.getNewValue();
// Print recieved data from JSON to the console
// I want to be able to get translation.getText() in my Main class
for (Translation translation : translations) {
System.out.println(translation.getText());
}
}
}
Data
public class WordTranslation {
public static final String TRANSLATIONS = "translations";
private static final String BASE_URL = "http://deu.hablaa.com/hs/translation/";
private List<PropertyChangeListener> listener = new ArrayList<PropertyChangeListener>();
ArrayList<Translation> translations;
public void addChangeListener(PropertyChangeListener newListener) {
listener.add(newListener);
}
public void notifyListeners(String property, Translation[] translationArray) {
translations = new ArrayList<>();
// TODO Auto-generated method stub
for (Translation t : translationArray) {
translations.add(t);
}
for (PropertyChangeListener name : listener) {
name.propertyChange(new PropertyChangeEvent(this, "translations", null, translations));
}
}
public void translate(String word, String from, String to) {
final Translation[][] translations = new Translation[1][1];
String url = String.format("%s%s/%s-%s",BASE_URL, word, from, to);// args) "http://xxx";
System.out.println(url);
final Gson gson = new Gson();
// should be a singleton
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(Call call, final Response response) throws IOException {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
translations[0] = gson.fromJson(response.body().charStream(),Translation[].class);
notifyListeners(TRANSLATIONS,translations[0]);
}
});
}
}
You could have an observer set static variables if you really wanted to, that could be polled for changes but this would defeat the principle of using the observer pattern which is event driven.
Perhaps there is some specific reason why you want this information available in the main class but I would be more inclined to offload any processing onto observers as you have already (cleanly) done, adding new implementations as you need them.
If you just want the information available to your main class (not necessarily in a static context), have your Main class implement PropertyChangeListener and register a new Main() with your WorldTranslation in the same way you have done with the WorldTranslationObserver.
HTH
Trying to use Robolectric and Mockito to test my Retrofit calls in my Android app but I am getting the following error:
Wanted but not invoked: mockApi.register(
,
);
-> at ServiceTest.testAPI(ServiceTest.java:58) Actually, there were zero interactions with this mock.
The RetroFit API call is defined in an interface as follows:
#FormUrlEncoded
#POST("/register")
void register(
#FieldMap Map<String, String> registrationParams,
Callback<JsonObject> response) ;
My test class is as follows:
#Config(constants = BuildConfig.class)
#RunWith(TestRunner.class)
public class SharedServiceTest {
private RegistrationActivity activity;
#Mock
private SharedService mockApi;
#Captor
private ArgumentCaptor<Callback<JsonObject>> cb;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ActivityController<RegistrationActivity> controller = Robolectric.buildActivity(RegistrationActivity.class);
activity = controller.get();
controller.create();
}
#Test
public void testAPI() throws Exception {
activity.populateFields();
activity.validateFields();
activity.register("");
Mockito.verify(mockApi).register(Mockito.anyMap(), cb.capture());
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("sessionToken", Mockito.anyString());
jsonObject.addProperty("userId", Mockito.anyString());
cb.getValue().success(jsonObject, null);
Assert.assertTrue(ShadowToast.getTextOfLatestToast().contains("Registration completed"));
}
}
The method in my RegistrationActivity that uses the API is as follows:
public void register(){
MyApplication.getInstance().getSharedService().register(mRegistrationParams, new Callback<JsonObject>() {
#Override
public void success(JsonObject jsonObject, retrofit.client.Response response) {
Toast.makeText(RegistrationActivity.this, "Registration completed", Toast.LENGTH_LONG).show();
}
#Override
public void failure(RetrofitError error) {
Toast.makeText(RegistrationActivity.this, RetrofitUtils.getErrorMessage(error), Toast.LENGTH_LONG).show();
}
});
}
The real Retrofit service comes from my own Application class which I have mocked in my test folder for robolectric to use:
public class TestMyApplication extends MyApplication
implements TestLifecycleApplication {
#Override
public void onCreate() {
super.onCreate();
}
#Override public void beforeTest(Method method) {
}
#Override public void prepareTest(Object test) {
}
#Override public void afterTest(Method method) {
}
#Override public CPSharedService getCPSharedService() {
return Mockito.mock(SharedService.class);
}
}
I have searched over the other questions on SO that have this error but none of them match what I am trying to do here or provide a solution to my issue so I am just wondering what I am doing wrong?
The mocked instance of SharedService in your TestMyApplication is not the same you declared your test class.
The Mockito.verify(mockApi).register(Mockito.anyMap(), cb.capture()); is failing because the instance referred by mockApi field is actually never called.
Another problem is that the getter in TestMyApplication always returns a new mock for each invokation:
#Override public CPSharedService getCPSharedService() {
return Mockito.mock(SharedService.class); //this creates a new "mocked" instance
}
Your scenario is not 100% clear to me, but it would be better if you could let your test set the instance of the mockApi field in your TestMyApplication instance:
public class TestMyApplication extends MyApplication
implements TestLifecycleApplication {
private SharedService sharedService;
#Override
public void onCreate() {
super.onCreate();
}
#Override public void beforeTest(Method method) {
}
#Override public void prepareTest(Object test) {
}
#Override public void afterTest(Method method) {
}
#Override public CPSharedService getCPSharedService() {
return this.sharedService;
}
public void setCPSharedService(SharedService sharedService) {
// store your mock
this.sharedService = sharedService;
}
}
and in your test class:
#Test
public void testAPI() throws Exception {
// configure you TestMyApplication
assertTrue(MyApplication.getInstance() instanceof TestMyApplication);
TestMyApplication testMyApp = (TestMyApplication) MyApplication.getInstance();
testMyApp.setCPSharedService(this.mockApi);
activity.populateFields();
activity.validateFields();
activity.register("");
Mockito.verify(this.mockApi).register(Mockito.anyMap(), cb.capture());
...
}
I'm new to retrofit and i am trying te get a json response to an object called RootObject. The error that i am stuck with is :
"Error:(21, 44) error: incompatible types: NewsController cannot be
converted to Callback>"
Does someone now my mistake here? thanks in regards!
public class NewsController {
public void getNews(){
Retrofit retrofit = new Retrofit.Builder().baseUrl("apilink").addConverterFactory(GsonConverterFactory.create()).build();
GetNewsService service = retrofit.create(GetNewsService.class);
try {
service.GetNewsItems().enqueue(this); //asynchronous
Response<List<RootObject>> response = service.GetNewsItems().execute(); //synchronous
}
catch (IOException e){
}
}
}
class to put the data:
public class RootObject implements Serializable {
public ArrayList<Result> results ;
public int nextId;
public ArrayList<Result> getResults() { return results; }
public int getNextId() { return nextId; }
public String toString() {
return String.format("JEEJ" + nextId);
}
}
Interface:
public interface GetNewsService {
#GET("/Articles")
Call<List<RootObject>> GetNewsItems();
}
First of all,
change your interface to this:
public interface GetNewsService {
#GET("/Articles")
void GetNewsItems(Callback<List<RootObject>> cb);
}
Also change your newsController class.
public class NewsController {
private RestAdapter restAdapter;
static final String API_URL = "[Enter your API base url here]";
public void getNews(){
OkHttpClient mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(15000,TimeUnit.MILLISECONDS);
mOkHttpClient.setReadTimeout(15000,TimeUnit.MILLISECONDS);
restAdapter = new RestAdapter.Builder().setEndpoint(API_URL).setClient(new OkClient(mOkHttpClient)).setLogLevel(RestAdapter.LogLevel.FULL) .build();
GetNewsService service = restAdapter.create(GetNewsService.class);
Callback<List<RootObject> cb = new Callback<List<RootObject>>() {
#Override
public void success(List<RootObject> rootObjectList, Response response) {
//whatever you want to do with the fetched news items
}
#Override
public void failure(RetrofitError error) {
//whatever you want to do with the error
}
};
service.GetNewsItems(cb);
}
}
You'll need to add the following dependencies in your build.gradle:
compile 'com.squareup.retrofit:retrofit:1.9.0'
compile 'com.google.code.gson:gson:2.3.1'
compile 'com.squareup.okhttp:okhttp:2.4.0'
#megh vidani's answer works, but he had you switch your code from Retrofit 2 to Retrofit 1. Here is how to do it in Retrofit 2. You would need to go back to your original gradle settings, etc. --
public class NewsController {
public void getNews(){
Retrofit retrofit = new Retrofit.Builder().baseUrl("apilink").addConverterFactory(GsonConverterFactory.create()).build();
GetNewsService service = retrofit.create(GetNewsService.class);
service.GetNewsItems().enqueue(new Callback<List<RootObject>>() {
#Override
public void onResponse(Response<List<RootObject>> response) {
// Handle your response
// Note HTTP errors are delivered here, you can check
// response.isSuccess() or response.code() to determine
// HTTP failures
}
#Override
public void onFailure(Throwable t) {
// Network errors
}
});
}
}