i'm doing an android App with Retrofit, the problem i have is i need to return the response.body() from the Callback, this is my code:
public class RoundListProvider {
//Load Rounds
private static RoundsInterface roundClient = RetroBuilder.createService(RoundsInterface.class);
private static RoundList mRoundList;
public static RoundList getRoundList(final Context mContext)
{
Toast.makeText(mContext, "We're in", Toast.LENGTH_LONG).show();
//Fetch info from rounds
Call<RoundList> call = roundClient.getRounds();
call.enqueue(new Callback<RoundList>() {
#Override
public void onResponse(Response<RoundList> response, Retrofit retrofit) {
Log.d("BODY", response.body().toString());
Log.d("MESSAGE", response.message());
Toast.makeText(mContext, "Conexión al servidor hecha con exito", Toast.LENGTH_LONG).show();
mRoundList = response.body();
}
#Override
public void onFailure(Throwable t) {
Log.d("BODY", t.getMessage());
Log.d("ERRRO", t.getStackTrace().toString());
}
});
return mRoundList;
}
}
As you can imagine, the return mRoundList is always null, because never waits for response to be done, can someone help me with this?.
You can't mix synchronous method calls and asynchronous callbacks like that - once you enter Callback-land you have to stay there.
The code that calls getRoundList() (presumably in some Activity or Fragment) then does something with the returned value, right? Instead of just storing the body to mRoundList in the onResponse - do what you want to happen with the value there.
Well, the problem is i needed to use the response data in a Recycler View, so i did this, maybe not the best, but it works. In my provider i added a new function:
public static void savedRoundList(RoundList roundList, Activity activity, Context context)
{
RecyclerView recyclerView = (RecyclerView) activity.findViewById(R.id.rvRounds);
recyclerView.setHasFixedSize(true);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(activity);
recyclerView.setLayoutManager(linearLayoutManager);
List<RoundData> roundData = roundList.getRounds();
RoundAdapter adapter = new RoundAdapter(roundData);
Log.d("RESPONSE & ERROR", roundList.getStatus()+ " "+ roundList.getCode());
recyclerView.setAdapter(adapter);
}
So, in the Callback i just us the function savedRoundList:
savedRoundList(response.body(), mActivity, mContext);
And voilà, it works.
Related
I created an app where data is loaded from server with the help of an API using Retrofit 2 . How can I fetch data from different API with the help of single API interface method?
If I request data from first URL then i should get data from first API, And if i request data from second URL then i should get data from second URL only. how can i do this with the help of a single API interface class.
This is my Retrofit
public class RetrofitInstance {
static {
System.loadLibrary("keys");
}
public static native String getUrl();
private static Retrofit retrofit;
public static Retrofit getRetrofit() {
if (retrofit == null) {
retrofit = new Retrofit.Builder().baseUrl(getUrl()).addConverterFactory(GsonConverterFactory.create()).build();
}
return retrofit;
}
}
and This is my API interface class where my api url is stored.
public interface APIInterfaces {
#GET("first.php")
Call<List<VideoModels>> getFirstVid();
#GET("second.php")
Call<List<VideoModels>> getSecondVid();
#GET("third.php")
Call<List<VideoModels>> getThirdVid();
#GET("fourth.php")
Call<List<VideoModels>> getFourthVid();
#GET("fifth.php")
Call<List<VideoModels>> getFifthVid();
}
This is my Activity and API interface method. Where I have to show details from API.
public class TestFragment extends Fragment {
TestFragmentBinding binding;
ArrayList<VideoModels> list = new ArrayList<>();
APIInterfaces interfaces;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = TestFragmentBinding.inflate(inflater, container, false);
ThumbnailViewer adapter = new ThumbnailViewer(list,getContext());
binding.mRecyclerView.setAdapter(adapter);
GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(),2);
binding.mRecyclerView.setLayoutManager(gridLayoutManager);
//Setup Data from API to ArrayList.
interfaces = RetrofitInstance.getRetrofit().create(APIInterfaces.class);
// I want to Change url as request and show data from "getSecondVid()","getThirdVid()" with singple interface method. Is it possible?
interfaces.getFirstVid().enqueue(new Callback<List<VideoModels>>() {
#SuppressLint("NotifyDataSetChanged")
#Override
public void onResponse(Call<List<VideoModels>> call, Response<List<VideoModels>> response) {
list.clear();
if (response.isSuccessful() && response !=null) {
list.addAll(response.body());
adapter.notifyDataSetChanged();
}
else {
Toast.makeText(getContext(), "No Data Found", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<List<VideoModels>> call, Throwable t) {
Toast.makeText(getContext(), "Unable to Connect", Toast.LENGTH_SHORT).show();
}
});
return binding.getRoot();
}
}
Your answer is very helpful for me.
You can do something like this
public interface APIInterfaces {
#GET
Call<List<VideoModels>> getVid(#Url String URL);
}
For every call, you just have to pass the URL to the same method.
you implementation will change to this
// you can pass the URL for the first.php, second.php video etc.
interfaces.getVid("your_url").enqueue(new Callback<List<VideoModels>>() {
#SuppressLint("NotifyDataSetChanged")
#Override
public void onResponse(Call<List<VideoModels>> call, Response<List<VideoModels>> response) {
list.clear();
if (response.isSuccessful() && response !=null) {
list.addAll(response.body());
adapter.notifyDataSetChanged();
}
else {
Toast.makeText(getContext(), "No Data Found", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<List<VideoModels>> call, Throwable t) {
Toast.makeText(getContext(), "Unable to Connect", Toast.LENGTH_SHORT).show();
}
});
I'm using retrofit for the first time and I'm looking to parse some json data but I may have made a mistake initiating the network request on MainActivity. The App doesn't crush but it's not returning any values. it's a Gridlayout with an OnclickListener on each item and I'm only looking to return 2 values (name and Id ). The object currently has 3 items (name, id, and a List<>) this is the Full API end point "https://d17h27t6h515a5.cloudfront.net/topher/2017/May/59121517_baking/baking.json"
public class MainActivity extends AppCompatActivity implements
CakeAdapter.CakeClickedListener {
RecyclerView mRecyclerView;
TextView mCakeName;
ImageView mCakeImage;
TextView mCakeId;
private List<CakesItem> mCakeList = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.cake_list_recycler_view);
mRecyclerView.setHasFixedSize(true);
GridLayoutManager mGridLayoutManager = new GridLayoutManager(MainActivity.this, 2);
final CakeAdapter mCakeAdapter = new CakeAdapter(this);
mRecyclerView.setLayoutManager(mGridLayoutManager);
mRecyclerView.setAdapter(mCakeAdapter);
mCakeAdapter.getCakeData(mCakeList);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
BakingJsonApi bakingJsonApi = retrofit.create(BakingJsonApi.class);
Call<List<CakesItem>> call = bakingJsonApi.getCakes(Constants.JSON_PATH);
call.enqueue(new Callback<List<CakesItem>>() {
#Override
public void onResponse(Call<List<CakesItem>> call, Response<List<CakesItem>> response) {
if (!response.isSuccessful()) {
Toast.makeText(MainActivity.this, "Code: " + response.code(), Toast.LENGTH_SHORT).show();
return;
}
List<CakesItem> cakeItem = response.body();
mCakeAdapter.getCakeData(cakeItem);
}
#Override
public void onFailure(Call<List<CakesItem>> call, Throwable t) {
Toast.makeText(MainActivity.this, "Unable to load data" + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
public interface BakingJsonApi {
#GET("/topher/2017/May/59121517_baking/{json}")
Call<List<CakesItem>> getCakes(#Path("json") String path);
}
class Constants {
static final String BAKING_API = "https://d17h27t6h515a5.cloudfront.net/topher/2017/May/59121517_baking/baking.json";
static final String BASE_URL = "https://d17h27t6h515a5.cloudfront.net/";
static final String JSON_PATH = "baking.json";
}
Maybe update Recycler-Adapter can work. I also modified your condition.
call.enqueue(new Callback<List<CakesItem>>() {
#Override
public void onResponse(Call<List<CakesItem>> call, Response<List<CakesItem>> response) {
if (response.isSuccessful()) {
mCakeList = new ArrayList();
mCakeList.addAll(response.body());
mCakeAdapter.notifyDataSetChanged();
}
}
#Override
public void onFailure(Call<List<CakesItem>> call, Throwable t) {
Toast.makeText(MainActivity.this, "Unable to load data" + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
Try change this condition :
if (!response.isSuccessful()) {
To something like t:
if(response.isSuccessful()){
Modelcake respuesta = response.body();
listcake.addAll(respuesta.getcake()); //in getcake() you get what are you want in your model
adapter.notifyDataSetChanged();
}else{
Log.e("API","onResponse"+response.errorBody());
}
With that should be work.
I have this method that I am trying to pull data from an API, and then update the text view. Everything works except getRecipeName doesn't finish after the "end Method" log. .getRecipeName() uses RetroFit to pull from an API.
I am currently learning MVP, Dagger, RxJava, and Butterknife all at once using
Mindork's Github page on MVP Architecture
I commented out the .subscribeOn and .observeOn to see the result difference and nothing changed.
#Override
public void onRandomButtonClicked() {
getMvpView().showLoading();
Log.e(TAG, "Random Method Open");
getCompositeDisposable().add(getDataManager()
.getRecipeName()
//.subscribeOn(getSchedulerProvider().io())
//.observeOn(getSchedulerProvider().ui())
.subscribe(new Consumer<String>() {
#Override
public void accept(String s) throws Exception {
Log.e(TAG, "accept");
getMvpView().updateTextView(title);
}
}));
Log.e(TAG, "end method");
}
Here is my getRecipeName() method
#Override
public Observable<String> getRecipeName() {
/*Create handle for the RetrofitInstance interface*/
GetDataService service = RetrofitClientInstance.getRetrofitInstance().create(GetDataService.class);
Call<RecipeList> call = service.getRecipe();
call.enqueue(new Callback<RecipeList>() {
#Override
public void onResponse(#NonNull Call<RecipeList> call, #NonNull retrofit2.Response<RecipeList> response) {
Log.e("onResponse","Recipe is Successful = " + response.isSuccessful());
//if response is false then skip to avoid null object reference
if (response.isSuccessful()) {
RecipeList drinkRecipe = response.body();
List<Recipe> recipes = drinkRecipe.getDrinks();
jokeText = String.valueOf(recipes.size());
Recipe myRecipe = recipes.get(0);
jokeText = myRecipe.getStrDrink();
Log.e("On Response", "Result2: " + jokeText);
}
//jokeText = "null";
}
#Override
public void onFailure(Call<RecipeList> call, Throwable t) {
Log.e("On Response","Failure");
}
});
//return jokeText;
return Observable.fromCallable(new Callable<String>() {
#Override
public String call() throws Exception {
return jokeText;
}
});
}
Solution
So as the comments stated RxJava Adapter was the correct way to go. I will just post my working code on myself using the adapter. I found it very difficult to find a working example.
//single api call using retrofit and rxjava
#SuppressLint("CheckResult")
private void getRandomButtonClick(){
retrofit = RetrofitClientInstance.getRetrofitInstance();
retrofit.create(GetDataService.class).getRecipe()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleResults, this::handleError );
}
private void handleResults(RecipeList recipeList) {
int i = recipeList.getDrinks().size();
Log.e(TAG, "size is: "+ i);
Recipe recipe = recipeList.getDrinks().get(0);
getMvpView().updateTextView(recipe.getStrDrink());
}
private void handleError(Throwable t){
Log.e("Observer", "");
}
My Retrofit Client Instance
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
return retrofit;
}
My Interface
public interface GetDataService {
//#Headers({})
#GET("random.php")
Observable<RecipeList> getRecipe();
I found a great resource to reference for me to correctly implement this. Retrofit Android
The reason is because your observable is returning jokeText every time it is subscribed upon. It returns immediately after invocation and will not wait for your network operation.
One possible solution is to use the RxJavaCallAdapter. Link here: https://github.com/square/retrofit/tree/master/retrofit-adapters/rxjava2
It will automatically convert your API returns to observables. No need to manually invoke retrofit requests. Just process the response and convert it to your desired object from there.
Another approach would be to wrap your entire sequence in an Observable.create or Observable.fromAsync.
I am using Android Studio I need to retrieve all data from an API which is http://mindicador.cl/api here I need to obtain four objects which are "uf", "dolar", "euro" and "utm". Actually I can retrieve it already, the problem comes when I want to add it to an array because in a future I want to handle this array in order to show them on RecyclerView.
public class MainActivity extends AppCompatActivity {
private List<IEconomicoObject> iEconomicoObjects;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iEconomicoObjects = new ArrayList<>();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(MiIndicadorAPI.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
MiIndicadorAPI api = retrofit.create(MiIndicadorAPI.class);
final Call<IEconomicoAPI> iEconomicoAPICall = api.getAPI();
iEconomicoAPICall.enqueue(new Callback<IEconomicoAPI>() {
#Override
public void onResponse(Call<IEconomicoAPI> call, Response<IEconomicoAPI> response) {
iEconomicoObjects.add(response.body().getUf());
iEconomicoObjects.add(response.body().getUtm());
iEconomicoObjects.add(response.body().getEuro());
iEconomicoObjects.add(response.body().getDolar());
}
#Override
public void onFailure(Call<IEconomicoAPI> call, Throwable t) {
Log.e("onFailure", "" + t.getMessage());
}
});
// here is the problem, the list is empty and should be show the data added it previously
if (!iEconomicoObjects.isEmpty()) {
for (IEconomicoObject o: iEconomicoObjects) {
Log.e("info", "nombre: " + o.getNombre());
}
} else {
Log.e("info", "the list is empty");
}
}
}
List will appear empty at the point where you are trying to read it. Since retrofit return result in a callback that is called in parallel to UIThread so , you should read the list , inside the callback.
I am building an activity in which I'm loading lists of objects from an api. I need to make multiple requests with retrofit which returns different objects. I can make the requests but I don't know how I can check when they're done.
The following code is what I have.
ApiRepository
public interface ApiRepository {
#GET("/api/troopmarker.json")
Call<List<TroopMarker>> getTroopMarkers();
#GET("/api/troop.json")
Call<List<Troop>> getTroops();
#GET("/api/treasure.json")
Call<List<TroopMarker>> getTreasures();
}
RepositoryService
public interface RepositoryService
{
void loadTroops(final TroopCallback callback);
void loadTroopMarkers(final TroopMarkerCallback callback);
//void loadTreasures(final TreasureCallback callback);
}
RepositoryServiceImpl
public class RepositoryServiceImpl implements RepositoryService {
private String url;
private Activity context;
public RepositoryServiceImpl(String url, Activity context) {
this.url = url;
this.context = context;
}
public void loadTroops(final TroopCallback callback) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiRepository repository = retrofit.create(ApiRepository.class);
repository.getTroops().enqueue(new Callback<List<Troop>>() {
public List<Troop> troops;
#Override
public void onResponse(Call<List<Troop>> call, Response<List<Troop>> response) {
if(response.isSuccessful()) {
Log.d("RETROFIT", "RESPONSE " + response.body().size());
callback.onSuccess(response.body());
}
}
#Override
public void onFailure(Call<List<Troop>> call, Throwable t) {
CharSequence text = "Error loading troops.";
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
callback.onSuccess(null);
}
});
}
public void loadTroopMarkers(final TroopMarkerCallback callback) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiRepository repository = retrofit.create(ApiRepository.class);
repository.getTroopMarkers().enqueue(new Callback<List<TroopMarker>>() {
#Override
public void onResponse(Call<List<TroopMarker>> call, Response<List<TroopMarker>> response) {
if(response.isSuccessful()) {
Log.d("RETROFIT", "RESPONSE " + response.body().size());
callback.onSuccess(response.body());
}
}
#Override
public void onFailure(Call<List<TroopMarker>> call, Throwable t) {
CharSequence text = "Error loading troops.";
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
callback.onSuccess(null);
}
});
}
public void loadTreasures() {
}
}
LoadActivity
public class LoadActivity extends AppCompatActivity
{
//TODO LOAD TROOPS AND TROOPMARKERS
//Load troops, troopmarkers, treasures and put on map
public List<Troop> troops;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loading);
//Start RepositoryService
final RepositoryService repositoryService = new RepositoryServiceImpl("http://internco.eu", this);
//Load troops
repositoryService.loadTroops(new TroopCallback() {
#Override
public void onSuccess(List<Troop> troops) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPS SIZE: " + troops.size());
}
});
//Load troopMarkers
repositoryService.loadTroopMarkers(new TroopMarkerCallback() {
public List<TroopMarker> troopMarkers;
#Override
public void onSuccess(List<TroopMarker> troopMarkers) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPMARKERS SIZE: " + troopMarkers.size());
}
});
//Should now here when I'm done with my requests.
Log.d("RETROFIT", "DONE");
}
}
Can someone point me out on this? I think that I have to use the RxJava library but I can't figure this out.
Your help is much appreciated.
1 hacky way of doing it would be to keep 2 flag variables loadTroopsflag & loadTroopMarkersflag.Then in the onSuccess callbacks of each check whether both are true and if they are then both your requests are complete. There might be edge cases in implementing a workaround like this but it should generally work. In case your requests depend on each other then as you will need to use nested called ie,
repositoryService.loadTroops(new TroopCallback() {
#Override
public void onSuccess(List<Troop> troops) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPS SIZE: " + troops.size());
repositoryService.loadTroopMarkers(new TroopMarkerCallback() {
public List<TroopMarker> troopMarkers;
#Override
public void onSuccess(List<TroopMarker> troopMarkers) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPMARKERS SIZE: " + troopMarkers.size());
}
});
}
});
Something like that,so in case you have more dependencies then your nested callbacks increase which is where Rxjava would come in and solve it in a few lines of code.I don't think you need to jump into Rx just yet as this is a relatively small problem and you Rx java brings in extra space that would increase the size of the app as well as development time.
Also note the part where you mention
//Should now here when I'm done with my requests.
Log.d("RETROFIT", "DONE");
does not imply that the requests are done,it simply means that they are queued up and in progress.These are asynchronous request and will complete when the callback completes.