Ive been doing this tutorial using Android Studio IDE.
The problem I have is that the tutorial was done with older libraries of gson and retrofit 1.8.0...
I was following along well with retrofit2.0-beta3 until I came upon this error that I cant seem to resolve..
It has something to do with this line...(this line is in my MainActivity.Java under onCreate())
SCService scService = SoundCloud.getService();
scService.getRecentTracks(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()), new Callback<List<Track>>() {
#Override
public void onResponse(Response<List<Track>> tracks) {
Log.d("TAG", "ONRESPONSE() - -- - some else wrong");
// response.isSuccess() is true if the response code is 2xx
if (tracks.isSuccess()) {
Log.d("TAG", "ONRESPONSE()_isSuccess? - -- - some else wrong");
List<Track> track = tracks.body();
loadTracks(track);
} else {
Log.d("TAG", "some else wrong");
}
}
#Override
public void onFailure(Throwable t) {
// handle execution failures like no internet connectivity
Log.d("Error", t.getMessage());
}
});
so I think that the problem starts with scService Interface..
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.http.GET;
import retrofit2.http.Query;
interface SCService {
#GET("tracks?client_id=" + Config.CLIENT_ID)
public void getRecentTracks(#Query("created_at[from]") String date, Callback<List<Track>> cb);
}
Here is my Soundcloud class....
import retrofit2.Retrofit;
import retrofit2.Retrofit;
public class SoundCloud {
private static final Retrofit REST_ADAPTER = new Retrofit.Builder().baseUrl(Config.API_URL).build();
private static final SCService SERVICE = REST_ADAPTER.create(SCService.class);
public static SCService getService() {
return SERVICE;
}
}
This is the Config class didnt think it would be needed...
public class Config {
public static final String CLIENT_ID = "c85f6828ae5eaf5981937ead09ef1b45";
public static final String API_URL = "https://api.soundcloud.com/";
}
I have been at this the whole day, Any help would be much appreciated..
It could be few things, but most likely the problem is that Gson converter is no longer automatically registered with retrofit and thus your code doesn't know how to get you object from Json. I.e. in retrofit2 you need to register Gson like:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://api.nuuneoi.com/base/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
Take a look at this article: Retrofit 2 changes, Custom Gson Object (in the middle of the page)
Related
I am having a problem with the API that I have implemented to my website that is connected to an activity in the Android application that I am developing. According to the Logcat of my Android Studio,
that line 118 of the ForgotPassword.java of my Android application
is throwing the java.lang.AssertionError. I have studied the problem again, then learned that the response of the API that is implemented on my website is being read by my Android application as null, thus with the aforementioned error.
I have also tried to solve the problem by doing the following:
Created a separate response class called ForgotPassRP with the same variables, because the DataRP response class is being used by other classes.
Updated the version of the retrofit that is implemented in my
build.grade(:app) from 2.7.2 to 2.9.0 and the retrofit2:converter-gson from 2.5.0 to 2.9.0.
Currently, I am still finding a solution by digging more about REST APIs on websites and Retrofit on Android, and I will implement anything new that I can learn from these. So am I missing something on my website, and Android code, or are there variables that are missing, while I am trying to do a POST method to the API that I have implemented to my website?
These are the following body of codes that I have analyzed so far that are connected to the problem (java.lang.AssertionError) that I am encountering on my Android application
The screenshot of the actual error that is being shown in the Logcat of my Android Studio:
Website:
1. APIS.php
//This is where the #POST method from my Android application API interface of the "ForgotPassword.java" is communicating with
public function forgot_password_post()
{
$response = array();
$user_info = $this->common_model->check_email($this->get_param['email'])[0];
if (!empty($user_info))
{
$this->load->helper("rendomPassword");
$info['new_password'] = get_random_password();
$updateData = array(
'user_password' => md5($info['new_password'])
);
$data_arr = array(
'email' => $user_info->user_email,
'password' => $info['new_password']
);
if ($this->common_model->update($updateData, $user_info->id, 'tbl_users')) {
$subject = $this->app_name . ' - ' . $this->lang->line('forgot_password_lbl');
$body = $this->load->view('admin/emails/forgot_password.php', $data_arr, TRUE);
if (send_email($user_info->user_email, $user_info->user_name, $subject, $body))
{
$row_info = array('success' => '1', 'msg' => $this->lang->line('password_sent_mail'));
}
else
{
$row_info = array('success' => '0', $this->lang->line('email_not_sent'));
}
}
}
else
{
$row_info = array('success' => '0', 'msg' => $this->lang->line('email_not_found'));
}
$this->set_response($row_info, REST_Controller::HTTP_OK);
}
Android Application
1. ForgotPassword.java
package com.example.mototecxecommerceapp.activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.example.mototecxecommerceapp.R;
import com.example.mototecxecommerceapp.response.DataRP;
import com.example.mototecxecommerceapp.response.ForgotPassRP;
import com.example.mototecxecommerceapp.rest.ApiClient;
import com.example.mototecxecommerceapp.rest.ApiInterface;
import com.example.mototecxecommerceapp.util.API;
import com.example.mototecxecommerceapp.util.ConstantApi;
import com.example.mototecxecommerceapp.util.Method;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.textfield.TextInputEditText;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.jetbrains.annotations.NotNull;
import io.github.inflationx.viewpump.ViewPumpContextWrapper;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class ForgetPassword extends AppCompatActivity {
private Method method;
private TextInputEditText editText;
private ProgressDialog progressDialog;
private InputMethodManager imm;
#Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase));
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fp);
method = new Method(ForgetPassword.this);
method.forceRTLIfSupported();
progressDialog = new ProgressDialog(ForgetPassword.this);
imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
MaterialToolbar toolbar = findViewById(R.id.toolbar_fp);
toolbar.setTitle(getResources().getString(R.string.forget_password));
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
editText = findViewById(R.id.editText_fp);
MaterialButton button = findViewById(R.id.button_fp);
button.setOnClickListener(v -> {
String string_fp = editText.getText().toString();
editText.setError(null);
if (!isValidMail(string_fp) || string_fp.isEmpty()) {
editText.requestFocus();
editText.setError(getResources().getString(R.string.please_enter_email));
} else {
editText.clearFocus();
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
if (method.isNetworkAvailable()) {
forgetPassword(string_fp);
} else {
method.alertBox(getResources().getString(R.string.internet_connection));
}
}
});
}
private boolean isValidMail(String email) {
return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches();
}
public void forgetPassword(String sendEmail) {
progressDialog.show();
progressDialog.setMessage(getResources().getString(R.string.loading));
progressDialog.setCancelable(false);
JsonObject jsObj = (JsonObject) new Gson().toJsonTree(new API(ForgetPassword.this));
jsObj.addProperty("email", sendEmail);
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Call<DataRP> call = apiService.getForgotPass(API.toBase64(jsObj.toString()));
call.enqueue(new Callback<DataRP>() {
#Override
public void onResponse(#NotNull Call<DataRP> call, #NotNull Response<DataRP> response) {
try {
DataRP dataRP = response.body();
assert dataRP != null; //This is the part of the code where the java.lang.AssertionError is being thrown.
if (dataRP.getStatus().equals("1")) {
if (dataRP.getSuccess().equals("1")) {
editText.setText("");
}
method.alertBox(dataRP.getMsg());
} else {
method.alertBox(dataRP.getMessage());
}
} catch (Exception e) {
Log.d(ConstantApi.exceptionError, e.toString());
method.alertBox(getResources().getString(R.string.failed_response));
}
progressDialog.dismiss();
}
#Override
public void onFailure(#NotNull Call<DataRP> call, #NotNull Throwable t) {
// Log error here since request failed
Log.e(ConstantApi.failApi, t.toString());
progressDialog.dismiss();
method.alertBox(getResources().getString(R.string.failed_response));
}
});
}
#Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
}
2. ApiInterface.java
//This is the post method that is being sent to the forgot password API of my website.
#POST("apis/forgot_password")
#FormUrlEncoded
Call<DataRP> getForgotPass(#Field("data") String data);
3. DataRP.java
package com.example.mototecxecommerceapp.response;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
//This is the response from the #POST method.
public class DataRP implements Serializable {
#SerializedName("status")
private String status;
#SerializedName("message")
private String message;
#SerializedName("success")
private String success;
#SerializedName("msg")
private String msg;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getSuccess() {
return success;
}
public void setSuccess(String success) {
this.success = success;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
I am also including the screenshot of the API logs that are being stored in the database that I have integrated into my website. This part is what seems to be the response of the "apis/forgot_password", whenever a post method that is thrown by my Android application is executed.
The SQL database of my website showing the logs related to "apis/forgotpassword"
This is also my first time asking a question in StackOverflow. So please bear with any "rookie mistakes" with the format/structure of the question that I have posted :)
In the android application development most used Retrofit2 Retrofit2 Documention
Then Generate Response Model Class (Call API using Postman). Create a Response Model with JSON
Source type:> JSON. Annotation style:>Gson.
Use this code in the "onResponse" method section.
if (response.isSuccessful()) {
if (response.body() != null) {
DataRP dataRp=response.body();
//code statement... } }
I have two java classes communicating using a vert.x EventBus.
I have a Productor.java class:
package TP1;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.json.JsonObject;
public class Productor extends AbstractVerticle
{
public void start() throws Exception
{
System.out.println("> Launching Productor...");
EventBus ebReady = vertx.eventBus();
//Send ready message
ebReady.send("canal-ready", "ready", messageReady -> {
//If Consumer received the ready message
if(messageReady.succeeded())
{
//Parse json response
JsonObject jsonObject = new JsonObject(messageReady.result().body().toString());
//Get answer value
int answerValue = Calcul.factorial(jsonObject.getInteger("param"));
String answer = Integer.toString(answerValue);
messageReady.reply(answer);//ERROR HERE
}
else
System.out.println("> No response!");
});
}
}
and a Consumer.java class:
package TP1;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.json.JsonObject;
public class Consumer extends AbstractVerticle
{
public void start() throws Exception
{
System.out.println("> Launching Consumer...");
String jsonString = "{\"class\":\"Calcul\",\"method\":\"factoriel\",\"param\":5}";
JsonObject jsonObj = new JsonObject(jsonString);
EventBus ebReady = vertx.eventBus();
//Wait for ready message
ebReady.consumer("canal-ready", messageReady -> {
//Parse the ready message
String readyString = messageReady.body().toString();
//Make sure it's the ready message
if(readyString.equals("ready"))
{
//Send json back (messageReady.succeeded())
messageReady.reply(jsonObj, messageReadyReply -> {
System.out.println(messageReadyReply);
});
}
});
}
}
I can't build the Productor class but have no problem with the Consumer one.
What's wrong with the messageReady.reply(answer); part in the Productor.java class?
You were missing a call to result() (see here) before getting the message and executing methods on it. However, you're using methods that have been deprecated in the 3.8 version (example) and are missing from 4.0, so I would advise that you use the other signature instead.
I want to try using Retrofit in a new Android project.
I have added the following to my build.gradle:
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.google.code.gson:gson:2.8.0'
I have a POJO called 'Turbine' which looks as follows:
public class Turbine {
String name;
}
I have my Endpoint service class:
import java.util.List;
import greenapps.objects.Turbine;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
public interface GreenAppService {
#GET("turbines/{id}")
Call<List<Turbine>> turbine(#Path("id") String id);
}
In my main activity in android I have the following code (this is where I want to execute the call and get back my Turbine pojo object filled with data from the backend:
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
...
...
...
//Relevant snippet starts here
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants.API_V1_ENDPOINT)
.build();
GreenAppService service = retrofit.create(GreenAppService.class);
Call<List<Turbine>> turbine = service.turbine("1");
turbine.enqueue(new Callback<Turbine>() {
#Override
public void onResponse(Call<Turbine> call, Response<Turbine> response) {
}
#Override
public void onFailure(Call<Turbine> call, Throwable t) {
}
});//Relevant snippet ends here
On the turbine.enqueue line I am getting the following error:
I presume the syntax here is wrong somehow, but I don't quite see what is causing the issue.
Also, once this works, how do I get my Turbine object? Is it a case of doing Turbine t = response.body();
Because you defined that you are waiting an Array of Turbine.
turbine.enqueue(new Callback<Turbine>()
to
turbine.enqueue(new Callback<ArrayList<Turbine>>()
also you need to update onResponse and onFailure methods with arraylist.
I strongly suggest you to apply singleton pattern for your GreenAppService object.
UPDATE
Here is an example of singleton pattern.
public final class WebService {
private static GreenAppService sInstance;
public static GreenAppService getInstance() {
if (sInstance == null) {
sInstance = new Retrofit
.Builder()
.baseUrl(Constants.API_V1_ENDPOINT)
.build();
}
return sInstance;
}
}
After that we call like
WebService
.getInstance()
.yourmethod()
.enqueue()
Easy way to add retrofit in your project with just one click. (one-time setup)
Retrofit zip
Extract the file and you will get the “Retrofit” folder inside.
Copy this folder following the Android Studio path
Android\Android Studio\plugins\android\lib\templates\other
Restart your Android Studio.
Select your project in which you want to add retrofit and find an option below.
Project > New > Other> Retrofit
You can see full blog here
https://medium.com/#mestri.vinayak.n/quick-install-retrofit-in-your-android-project-custom-template-a14a6adc77c2
I'm trying to unit test presenter in my Android app. Method I'm trying to test looks like this:
#Override
public boolean loadNextPage() {
if (!mIsLoading) {
mIsLoading = true;
if (mViewReference.get() != null) {
mViewReference.get().showProgress();
}
mService.search(mSearchQuery, ++mCurrentPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(itemsPage -> {
mIsLoading = false;
mTotalPages = itemsPage.getPagination().getTotalPages();
if (mViewReference.get() != null) {
mViewReference.get().showMovies(itemsPage.getItems());
}
},
error -> {
mIsLoading = false;
Log.d(LOG_TAG, error.toString());
});
}
return mTotalPages == 0 || mCurrentPage < mTotalPages;
}
mService is Retrofit interface and mService.search() method returns RxJava's Observable<SearchResults>. My unit test code looks like this:
package mobi.zona.presenters;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import com.example.api.Service;
import com.example.model.Movie;
import com.example.model.SearchResults;
import com.example.views.MoviesListView;
import rx.Observable;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
#RunWith(MockitoJUnitRunner.class)
public class SearchPresenterTest {
#Mock
Service mService;
#Mock
MoviesListView mMoviesListView;
#Test
public void testLoadNextPage() throws Exception {
String searchQuery = "the hunger games";
SearchResults searchResults = new SearchResults();
List<Movie> movies = new ArrayList<>();
searchResults.setItems(movies);
when(mService.search(searchQuery, 1)).thenReturn(Observable.just(new SearchResults()));
MoviesListPresenter presenter = new SearchPresenter(mZonaService, mMoviesListView, searchQuery);
presenter.loadNextPage();
verify(mService, times(1)).search(searchQuery, 1);
verify(mMoviesListView, times(1)).showProgress();
verify(mMoviesListView, times(1)).showMovies(movies);
}
}
The problem is the third verify(mMoviesListView, times(1)).showMovies(movies); line - it allways fails. Whem I'm trying to debug this test I see that control flow never goes into .subscribe(itemPage - {.... I think that it's something related to the fact that I'm subscribing on Schedulers.io() thread, but have no idea on how to fix this. Any ideas?
EDIT 1:
Changed the presenter to take Scheduler's as constructor parameters. Changed test to look like this:
#Test
public void testLoadNextPage() throws Exception {
String searchQuery = "the hunger games";
SearchResults searchResults = new SearchResults();
List<Movie> movies = new ArrayList<>();
searchResults.setItems(movies);
when(mZonaService.search(searchQuery, 1)).thenReturn(Observable.just(new SearchResults()));
MoviesListPresenter presenter = new SearchPresenter(mZonaService, mMoviesListView, searchQuery,
Schedulers.test(), Schedulers.test());
presenter.loadNextPage();
verify(mZonaService, times(1)).search(searchQuery, 1);
verify(mMoviesListView, times(1)).showProgress();
verify(mMoviesListView, times(1)).showMovies(movies);
}
Still getting this test failure message:
Wanted but not invoked:
mMoviesListView.showMovies([]);
-> at com.example.presenters.SearchPresenterTest.testLoadNextPage(SearchPresenterTest.java:46)
However, there were other interactions with this mock:
mMoviesListView.showProgress();
-> at com.example.presenters.SearchPresenter.loadNextPage(SearchPresenter.java:41)
In my apps interactors/use-cases/model (mService in your case) is responsible for specifying Scheduler for the operation (since it knows better what kind of operation it does).
So, move your subscribeOn to mService. After that your mock will work fine.
Going deeper, if now you'll want to test mService I would recommend you to make it "dependent" on Scheduler. In other words - add Sheduler as a constructor parameter.
public class MyService {
private final Scheduler taskScheduler;
public MyService(Scheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
// ...
public Observable<Something> query() {
return someObservable.subscribeOn(taskScheduler);
}
}
Then, in tests you can use Schedulers.immediate() and for actual app Schedulers.io() (or whatever you like, really).
I am new to Android testing. All I am trying now is new Espresso with Junit4. the thing I go till now is Espresso is for ui testing and with junit we can do logical testing. So I am trying Junit to test my retrofit code:
protected String signIn(String emailNumber, String password) {
RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(Constants.API_URL).build();
RetroInterface retroInterface = restAdapter.create(RetroInterface.class);
retroInterface.signIn(emailNumber, password, new Callback<SignInPojo>() {
#Override
public void success(SignInPojo signInPojo, Response response) {
if (signInPojo.getError()) {
Snackbar.with(getApplicationContext()).text(signInPojo.getMessage())
.textColor(getResources().getColor(R.color.red_400)).show(SignInActivity.this);
result = "successerror";
} else {
Log.d("Json", signInPojo.getName());
result = "Successfully Signed In";
editor = sharedPreferences.edit();
editor.putBoolean(Constants.IS_LOGGED_IN, true);
editor.apply();
startActivity(new Intent(getApplicationContext(), LenderActivity.class));
finish();
}
}
#Override
public void failure(RetrofitError error) {
Log.d("RetroError", error.toString());
Log.d("RetroUrl", error.getUrl());
result = "failed";
}
});
return result;
}
with these test Class:
SignInActivityJunit.java
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
#RunWith(AndroidJUnit4.class)
#SmallTest
public class SignInActivityJunit extends ActivityInstrumentationTestCase2<SignInActivity>{
private SignInActivity signInActivity;
public SignInActivityJunit() {
super(SignInActivity.class);
}
#Before
public void setUp() throws Exception {
super.setUp();
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
signInActivity = getActivity();
}
#Test
public void checkSignIn_Success() {
String result = signInActivity.signIn("99929992", "aaaaaaaa");
assertThat(result, is(equalTo("success")));
}#Test
public void checkSignIn_SuccessButError() {
String result = signInActivity.signIn("99929992", "aaaaaaaa");
assertThat(result, is(equalTo("successerror")));
}#Test
public void checkSignIn_Fail() {
String result = signInActivity.signIn("99929992", "aaaaaaaa");
assertThat(result, is(equalTo("success")));
}
#Override
protected void tearDown() throws Exception {
super.tearDown();
}
}
Now these all cases failed because on debugging I saw that they are not waiting for the network to return call(As per my guess). They are skipping success and failure methods.
So the question is.. how to make unit test wait till retrofit returns the request. Or is there any other efficient way to test these network connectivity.
Thank you
This won't answer your question, but I think a more correct way of testing web services would be to mock the server and request, or to actually test the UI with espresso.
In first case, with jUnit, you don't create an actual request, but rather a mocking one that will return a predifined result (the result could be read from a file). Here you could test that the conversion of response to your model classes is successful, if you have any.
In the second case, with Espresso, test the actual thing on device, like open LoginActivity -> retrieve the login/username fields -> type text -> click on Login button. And as expected result here you could put some controls from the main activity (the one that redirects you after a successful login)