RetroFit Call fails after first time - android

My RetroFit call that gets a list of two strings and two objects succeed on the first time but the response every time after that gets an empty body for some reason.
This is the Adapter for it:
public class AssistsAdapter extends RecyclerView.Adapter<AssistsAdapter.AssistsViewHolder> {
private List<TopAssists> mTopAssistsList;
private int mRowLayout;
private Context mContext;
public class AssistsViewHolder extends RecyclerView.ViewHolder{
LinearLayout assistsLayout;
TextView playerRank, playerAssists, playerName, playerTeam;
public AssistsViewHolder(View itemView) {
super(itemView);
assistsLayout = itemView.findViewById(R.id.assists_layout);
playerRank = itemView.findViewById(R.id.assists_rank);
playerAssists = itemView.findViewById(R.id.assists_assists);
playerName = itemView.findViewById(R.id.assists_player);
playerTeam = itemView.findViewById(R.id.assists_team);
}
}
public AssistsAdapter(List<TopAssists> topAssistsList, int rowLayout, Context context){
mTopAssistsList = topAssistsList;
mRowLayout = rowLayout;
mContext = context;
}
#NonNull
#Override
public AssistsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(mRowLayout, parent, false);
AssistsViewHolder holder = new AssistsViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(AssistsViewHolder holder, int position) {
holder.playerRank.setText(String.valueOf(mTopAssistsList.get(position).getRank()));
holder.playerAssists.setText(String.valueOf(mTopAssistsList.get(position).getAssists()));
holder.playerName.setText(mTopAssistsList.get(position).getPlayer().getName());
holder.playerTeam.setText(mTopAssistsList.get(position).getTeam().getName());
}
#Override
public int getItemCount() {
return this.mTopAssistsList.size();
}
This is the fragment that initiate the RetroFit call:
public class TournamentsInfoFragment extends Fragment {
private final static String TAG = "Call Failed";
private StandingsAdapter mStandingsAdapter;
private RecyclerView mRecyclerView;
private ProgressBar mProgressBar;
private String mTournamentId;
private String mRegion;
private String mKey;
private GoalsAdapter mGoalsAdapter;
private AssistsAdapter mAssistsAdapter;
private TextView mFailedMessage;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_standings, container, false);
// Initializing the failure message in case of an error
mFailedMessage = view.findViewById(R.id.standings_failed);
// Getting the arguments from the previous fragment
Bundle bundle = getArguments();
mTournamentId = bundle.getString("tournament_id");
mRegion = bundle.getString("region");
mKey = bundle.getString("key");
// Showing the Progressbar
mProgressBar = view.findViewById(R.id.standings_progress_bar);
// Creating an instance of the ApiInterface
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
// Calling to get the standings from the API
Call<StandingsResponse> call = apiService.getStandings(mRegion, mTournamentId, mKey);
// Logging the URL Call
Log.wtf("URL Called", call.request().url() + "");
call.enqueue(new Callback<StandingsResponse>() {
#Override
public void onResponse(Call<StandingsResponse> call, Response<StandingsResponse> response) {
// Checking the response code to act accordingly
if(response.code() == 404){
mFailedMessage.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE);
}
else{
if (response.body() == null){
Toast.makeText(getActivity().getApplicationContext(),
R.string.standings_problem, Toast.LENGTH_LONG).show();
mProgressBar.setVisibility(View.GONE);
}
else {
generateStandings(response.body().getStandings());
mStandingsAdapter.notifyDataSetChanged();
mProgressBar.setVisibility(View.GONE);
}
}
}
#Override
public void onFailure(Call<StandingsResponse> call, Throwable t) {
Log.e(TAG, t.toString());
Toast.makeText(getActivity(), R.string.enqueue_failure, Toast.LENGTH_LONG).show();
mProgressBar.setVisibility(View.GONE);
}
});
// Calling to get the Goals and assists leaders from the API
Call<LeadersResponse> leadersCall = apiService.getLeaders(mRegion,mTournamentId,mKey);
// Logging the URL Call
Log.wtf("Leaders URL Called", leadersCall.request().url() + "");
leadersCall.enqueue(new Callback<LeadersResponse>() {
#Override
public void onResponse(Call<LeadersResponse> call, Response<LeadersResponse> response) {
// Checking the response code and acting accordingly
if(response.code() == 404){
mFailedMessage.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE);
}
else{
if (response.body() == null){
Toast.makeText(getActivity().getApplicationContext(),
R.string.leaders_problem, Toast.LENGTH_LONG).show();
mProgressBar.setVisibility(View.GONE);
}
else {
generateGoals(response.body().getTopGoalsList());
generateAssists(response.body().getTopAssists());
mGoalsAdapter.notifyDataSetChanged();
mAssistsAdapter.notifyDataSetChanged();
mProgressBar.setVisibility(View.GONE);
}
}
}
#Override
public void onFailure(Call<LeadersResponse> call, Throwable t) {
Log.e(TAG, t.toString());
Toast.makeText(getActivity(), R.string.enqueue_failure, Toast.LENGTH_LONG).show();
mProgressBar.setVisibility(View.GONE);
}
});
return view;
}
/**
* Method to generate List of standings using RecyclerView with custom adapter
*/
private void generateStandings(final List<Standings> empDataList) {
mRecyclerView = getView().findViewById(R.id.standings_rv);
mStandingsAdapter = new StandingsAdapter(empDataList, R.layout.item_standings, getActivity());
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mRecyclerView.setAdapter(mStandingsAdapter);
}
/**
* Method to generate List of Goal leaders using RecyclerView with Custom adapter
*/
private void generateGoals(List<TopGoals> topGoals) {
mRecyclerView = getView().findViewById(R.id.goals_rv);
mGoalsAdapter = new GoalsAdapter(topGoals, R.layout.item_goals, getActivity());
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mRecyclerView.setAdapter(mGoalsAdapter);
}
/**
* Method to generate List of assists leaders using RecyclerView with Custom adapter
*/
private void generateAssists(List<TopAssists> topAssists) {
mRecyclerView = getView().findViewById(R.id.assists_rv);
mAssistsAdapter = new AssistsAdapter(topAssists, R.layout.item_assists, getActivity());
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mRecyclerView.setAdapter(mAssistsAdapter);
}
It shows the RecyclerView for the first time but every time after that the response body is empty and causes a NullPointerException, I had to create an if statement and Toast just to prevent the application from crashing.
Error Log:
6-03 21:55:51.241 6034-6034/com.mad.footstats E/URL Called: https://api.sportradar.us/soccer-t3/eu/en/tournaments/sr:tournament:17/standings.json?api_key=w7c74newrykj8m57rda6xwrk 06-03 21:55:51.253 6034-6034/com.mad.footstats E/Leaders URL Called: https://api.sportradar.us/soccer-t3/eu/en/tournaments/sr:tournament:17/leaders.json?api_key=w7c74newrykj8m57rda6xwrk 06-03 21:55:51.278 6034-6034/com.mad.footstats E/RecyclerView: No adapter attached; skipping layout 06-03 21:55:51.279 6034-6034/com.mad.footstats I/chatty: uid=10085(com.mad.footstats) identical 1 line 06-03 21:55:51.279 6034-6034/com.mad.footstats E/RecyclerView: No adapter attached; skipping layout 06-03 21:55:51.507 1393-3495/? W/audio_hw_generic: Not supplying enough data to HAL, expected position 10934976 , only wrote 10934640 06-03 21:55:51.557 1393-3495/? W/audio_hw_generic: Hardware backing HAL too slow, could only write 0 of 720 frames

Related

Getting "No adapter attached; skipping layout" error when parsing a named JSON inside another list

I want to parse some JSON (Car models):
{"modelos": [{"nome": AMAROK},{"nome": JETTA}]}
I have the code below: ADAPTER
public class ListaVeiculosAdapter extends RecyclerView.Adapter<ListaVeiculosAdapter.ListaVeiculosViewHolder> {
private List<VeiculosResponse> veiculos;
public ListaVeiculosAdapter(List<VeiculosResponse> veiculos) {
this.veiculos = veiculos;
}
public ListaVeiculosAdapter() {
}
#NonNull
#Override
public ListaVeiculosViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_veiculo, parent, false);
return new ListaVeiculosViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ListaVeiculosViewHolder holder, int position) {
holder.textNomeVeiculo.setText(veiculos.get(position).getNome());
}
#Override
public int getItemCount() {
return (veiculos!= null && veiculos.size()>0) ? veiculos.size() : 0;
}
static class ListaVeiculosViewHolder extends RecyclerView.ViewHolder{
private TextView textNomeVeiculo;
public ListaVeiculosViewHolder(View itemView){
super(itemView);
textNomeVeiculo = itemView.findViewById(R.id.text_veiculo);
}
}}
Main Activity:
RecyclerView recyclerVeiculos;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lista_veiculos);
// Toolbar toolbar = findViewById(R.id.my_toolbar);
//setSupportActionBar(toolbar);
recyclerVeiculos = findViewById(R.id.my_recycler_view);
ListaVeiculosAdapter adapter = new ListaVeiculosAdapter();
ApiService.getInstance().getModels().enqueue(new Callback<VeiculosResult>() {
#Override
public void onResponse(Call<VeiculosResult> call, Response<VeiculosResult> response) {
RecyclerView.LayoutManager linearLayoutManager = new LinearLayoutManager(ListaVeiculosActivity.this);
recyclerVeiculos.setLayoutManager(linearLayoutManager);
recyclerVeiculos.setAdapter(new ListaVeiculosAdapter(response.body().getModelos()));
}
#Override
public void onFailure(Call<VeiculosResult> call, Throwable t) {
}
});
}}
The problem is that I get the error
"E/RecyclerView: No adapter attached; skipping layout"
when I try to run the application.
Other Retrofit configuration codes:
public interface VeiculosService {
#GET("marcas/59/modelos")
Call<VeiculosResult> getModels();
}
private static VeiculosService INSTANCE;
public static VeiculosService getInstance() {
if(INSTANCE == null){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://parallelum.com.br/fipe/api/v1/carros/")
.addConverterFactory(MoshiConverterFactory.create())
.build();
INSTANCE = retrofit.create(VeiculosService.class);
}
return INSTANCE;
}
My idea is to create a list (recycler view) with car models so the user can choose which car they want.
You have to set layout manager and set adapter to your recyclerview before API call.
Modify your MainActivity like this.
RecyclerView recyclerVeiculos;
List<VeiculosResponse> veiculos;
ListaVeiculosAdapter adapter;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lista_veiculos);
// Toolbar toolbar = findViewById(R.id.my_toolbar);
//setSupportActionBar(toolbar);
recyclerVeiculos = findViewById(R.id.my_recycler_view);
RecyclerView.LayoutManager linearLayoutManager = new
LinearLayoutManager(ListaVeiculosActivity.this);
recyclerVeiculos.setLayoutManager(linearLayoutManager);
// create an empty list and pass it to your adapter
veiculos = new ArrayList<>()
adapter = new ListaVeiculosAdapter(veiculos)
recyclerVeiculos.setAdapter(adapter);
ApiService.getInstance().getModels().enqueue(new Callback<VeiculosResult>() {
#Override
public void onResponse(Call<VeiculosResult> call, Response<VeiculosResult> response) {
if (response.isSuccessful() && response.body() != null){
veiculos.addAll(response.body().getModelos());
// after getting new data you have to notify your adapter that your data set is changed like below.
adapter.notifyDataSetChanged();
}
}
#Override
public void onFailure(Call<VeiculosResult> call, Throwable t) {
}
});
// ...
I think what has been stated by #Jakir will sort out the skipping adpter issue. This mostly occurs when there is an issue with how you are attaching adapter to recyclerView.
//Set layoutmanager attaching adapter to recyclerView
recyclerView.setLayoutManager(myLayoutManager)
recyclerView.setAdapter(myAdapter);
myAdapter.notifyDatasetChanged();
Then for the json response you need to create a pojo classes for it.
This will fetch the array of Json objects. ModelusData class will now contain the individual modelus info
public class ModelusObject {
#SerializedName("modelus")
private List<ModelusData> list;
public List<ModelusData> getList() {
return list;
}
public void setList(List<ModelusData> list) {
this.list = list;
}
}
For the ModelusData class
public class ModelusData{
#SerializedName("nome")
String nome;
#SerializedName("codigo")
String codigo;
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getCodigo() {
return codigo;
}
public void setCodigo(String codigo) {
this.codigo = codigo;
}
}

MVVM Set Recyclerview Adapter With Items In Fragment

I am using MVVM pattern with retrofit and recyclerview for the first time. I have made the network call in my viewmodel class but have no idea how to display the data in recyclerview.
Here's my viewmodel code:
public class HomeSessionsViewModel extends ViewModel {
private static final String TAG = HomeSessionsViewModel.class.getSimpleName();
private Context context;
public String sessionImg;
public String title, programName, batchName, batchPlayersCount;
public HomeSessionsViewModel(Context context) {
this.context = context;
}
public void fetchSessions(String coachId){
Log.d(TAG, "Call made");
Call<SessionDetails> call = RestClient.getRestInstance()
.getSessionsService()
.fetchSessions(coachId);
call.enqueue(new Callback<SessionDetails>() {
#Override
public void onResponse(Call<SessionDetails> call, Response<SessionDetails> response) {
if (response.isSuccessful()){
SessionDetails details = response.body();
List<Sessions> sessions = details.getSessions();
for (int i = 0; i < sessions.size(); i++){
Log.d(TAG, "Session Name:\t" + sessions.get(i).session_name);
sessionImg = sessions.get(i).sessionImage;
title = sessions.get(i).session_name;
programName = sessions.get(i).program_name;
batchName = sessions.get(i).batch_name;
batchPlayersCount = sessions.get(i).participants_count;
// addData(sessions);
}
}
}
#Override
public void onFailure(Call<SessionDetails> call, Throwable t) {
Log.d(TAG, "Can't Get Sessions:\n");
Log.d(TAG, t.getMessage());
}
});
}
}
and adapter code:
public class SessionsAdapter extends RecyclerView.Adapter<SessionsViewHolder> {
private final Context context;
private List<HomeSessionsViewModel> itemsList;
public SessionsAdapter(Context context, List<HomeSessionsViewModel> itemsList) {
this.context = context;
this.itemsList = itemsList;
}
#Override
public SessionsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
HomeSessionsBinding sessionsBinding = HomeSessionsBinding.inflate(inflater, parent, false);
return new SessionsViewHolder(sessionsBinding);
}
#Override
public void onBindViewHolder(SessionsViewHolder viewholder, int position) {
HomeSessionsViewModel viewModel = itemsList.get(position);
viewholder.bindSessions(viewModel);
}
#Override
public int getItemCount() {
if (itemsList == null) {
return 0;
}
return itemsList.size();
}
}
and in my fragment I have this:
public class HomeFragment extends Fragment implements OnItemClickedListener, DatePickerListener {
private RecyclerView actionsRV, sessionsRV;
private List<ActionsViewModel> actionsList = new ArrayList<>();
private ActionsAdapter actionsAdapter;
private HomeFragmentBinding fragmentBinding;
private HomeFragmentViewModel homeViewModel;
private HomeSessionsViewModel sessionsViewModel;
private List<HomeSessionsViewModel> sessionsViewModelList = new ArrayList<>();
private SessionsAdapter sessionsAdapter;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
fragmentBinding = DataBindingUtil.inflate(inflater, R.layout.home_fragment, container, false);
homeViewModel = new HomeFragmentViewModel(getActivity());
fragmentBinding.setHomeViewModel(homeViewModel);
init();
return fragmentBinding.getRoot();
}
private void init() {
addActions();
initPicker();
populateSessions();
}
private void initPicker() {
fragmentBinding.datePicker.setListener(this)
.setMonthAndYearTextColor(getActivity().getResources().getColor(R.color.white))
.setDateSelectedColor(getActivity().getResources().getColor(R.color.white))
.setDateSelectedTextColor(getActivity().getResources().getColor(R.color.event_color_03))
.setTodayButtonTextColor(getActivity().getResources().getColor(R.color.white))
.setTodayDateTextColor(getActivity().getResources().getColor(R.color.accent))
.setUnselectedDayTextColor(getActivity().getResources().getColor(R.color.white))
.init();
}
private void populateSessions() {
sessionsViewModel = ViewModelProviders.of(this).get(HomeSessionsViewModel.class);
sessionsViewModel.fetchSessions("4086");
//sessionsViewModel.fetchSessions();
//sessionsViewModel.fetchSessions(""); // TODO: 3/16/2019 Use coach id from db
sessionsRV = fragmentBinding.sessionsRV;
//sessionsViewModelList = sessionsViewModel.items();
sessionsAdapter = new SessionsAdapter(getActivity(), sessionsViewModelList);
}
private void addActions() {
actionsRV = fragmentBinding.actionsRV;
actionsRV.setHasFixedSize(true);
LinearLayoutManager hlm = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false);
fragmentBinding.actionsRV.setLayoutManager(hlm);
ActionsViewModel action1 = new ActionsViewModel();
action1.name = "Attendance";
action1.img = getResources().getDrawable(R.drawable.ic_attendance);
actionsList.add(action1);
ActionsViewModel action2 = new ActionsViewModel();
action2.name = "Evaluate";
action2.img = getResources().getDrawable(R.drawable.ic_evaluate);
actionsList.add(action2);
ActionsViewModel action3 = new ActionsViewModel();
action3.name = "Players";
action3.img = getResources().getDrawable(R.drawable.ic_players);
actionsList.add(action3);
ActionsViewModel action4 = new ActionsViewModel();
action4.name = "Programs";
action4.img = getResources().getDrawable(R.drawable.ic_programs);
actionsList.add(action4);
ActionsViewModel action5 = new ActionsViewModel();
action5.name = "Tips/VOD";
action5.img = getResources().getDrawable(R.drawable.ic_tips_vod);
actionsList.add(action5);
actionsAdapter = new ActionsAdapter(getActivity(), actionsList);
actionsAdapter.setItemClickedListener(this);
actionsRV.setAdapter(actionsAdapter);
}
#Override
public void itemClicked(int position) {
switch (position){
case 0:
Toast.makeText(getActivity(), "Attendance Clicked", Toast.LENGTH_SHORT).show();
break;
case 1:
Toast.makeText(getActivity(), "Evaluate Clicked", Toast.LENGTH_SHORT).show();
break;
case 2:
Toast.makeText(getActivity(), "Players Clicked", Toast.LENGTH_SHORT).show();
break;
case 3:
Toast.makeText(getActivity(), "Programs Clicked", Toast.LENGTH_SHORT).show();
break;
case 4:
Snackbar.make(getActivity().findViewById(android.R.id.content), "Tips Coming Soon", Snackbar.LENGTH_SHORT).show();
break;
}
}
#Override
public void onDateSelected(DateTime dateSelected) {
}
}
Displaying the actions recyclerview was straightforward but I'm not sure how to go about the sessions. I have followed this tutorial series but I'm stuck how to get the data from viewmodel into fragment recyclerview. I have read that adding view widgets into viewmodel can cause memory leaks. Is there any way to do this? Thanks.
You can use LiveData for these kind of purposes. LiveData is Lifecycle aware and can be used in ViewModel.
In your ViewModel, add a property of type MutableLiveData.
private MutableLiveData<List<Session>> sessionsLiveData = new MutableLiveData<List<Session>>();
public LiveData<List<Session>> getSessionLiveData(){
return sessionsLiveData;
}
Set value to this LiveData when network call is made and you have required data for the adapter.
call.enqueue(new Callback<SessionDetails>() {
#Override
public void onResponse(Call<SessionDetails> call, Response<SessionDetails> response) {
if (response.isSuccessful()){
SessionDetails details = response.body();
List<Session> sessions = details.getSessions();
sessionsLiveData.postValue(sessions);
}
}
#Override
public void onFailure(Call<SessionDetails> call, Throwable t) {
Log.d(TAG, "Can't Get Sessions:\n");
Log.d(TAG, t.getMessage());
}
});
In your Fragment, observer this LiveData.
viewModel.getSessionLiveData().observe(this, new Observer<List<Session>>() {
#Override
public void onChanged(#Nullable final List<Session> sessions) {
// set Data to adapter here.
adapter.setData(sessions);
}
});

Android Retrofit: URL query string "text={userInput}&key={apiKey}" must not have replace block. For dynamic query parameters use #Query

java.lang.IllegalArgumentException: URL query string "text={userInput}&key={apiKey}" must not have replace block. For dynamic query parameters use #Query.
Here is my code:
public interface Service {
#GET("/check.php?text={userInput}&key={apiKey}")
Call<List<Errors>> readErrors(#Query("userInput") String userInput,
#Query("apiKey") String apiKey);
}
And my call request:
public void loadJson(){
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl("https://api.textgears.com")
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
Service serviceAPI = retrofit.create(Service.class);
Call<List<Errors>> loadErrorsCall = serviceAPI.readErrors(userInput, apiKey);
loadErrorsCall.enqueue(new Callback<List<Errors>>() {
#Override
public void onResponse(Call<List<Errors>> call, Response<List<Errors>> response) {
errors = new ArrayList<>(response.body());
Log.i("ORIG. ARRAY SIZE", String.valueOf(errors.size()));
if (errors != null){
for (int i = 0; i < 5; i++){
errorArrayList.add(errors.get(i));
}
Log.i("NEW ARRAY SIZE", String.valueOf(errorArrayList.size()));
}
mErrorsRecyclerView.setItemAnimator(new DefaultItemAnimator());
mErrorsRecyclerView.setAdapter(new ResultAdapter(getContext(), errorArrayList));
}
#Override
public void onFailure(Call<List<Errors>> call, Throwable t) {
Log.i("Error: ", t.getMessage());
}
});
}
What should be the solution to my problem?
Edit:
I already fixed my problem but my other problem is that my recycler view is not displaying. Saying that the adapter is not found, when in fact I have an adapter. Here are my codes, the Adapter class and the fragment class.
Adapter class:
public class ResultAdapter extends RecyclerView.Adapter<ResultAdapter.ResultViewHolder>{
Context mContext;
List<Photo> photoList = new ArrayList<>();
List<Errors> errorsList = new ArrayList<>();
public ResultAdapter (Context mContext, List<Errors> errorsList){
this.errorsList = errorsList;
this.mContext = mContext;
}
#Override
public ResultViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.result_card, viewGroup, false);
return new ResultViewHolder(view);
}
#Override
public void onBindViewHolder(ResultViewHolder resultViewHolder, int i) {
Errors errors = errorsList.get(i);
//Log.i("Position: ", i+1 + " Id: " + photos.getId());
resultViewHolder.mNumErrorsTextView.setText(errorsList.size());
resultViewHolder.mIdErrorTextView.setText(errors.getId());
resultViewHolder.mLengthErrorTextView.setText(errors.getLength());
resultViewHolder.mBadErrorTextView.setText(errors.getBad());
}
#Override
public int getItemCount() {
return errorsList.size();
}
public class ResultViewHolder extends RecyclerView.ViewHolder{
#BindView(R.id.NumofErrorsTextView)
TextView mNumErrorsTextView;
#BindView(R.id.ErrorIdTextView)
TextView mIdErrorTextView;
#BindView(R.id.ErrorLengthTextView)
TextView mLengthErrorTextView;
#BindView(R.id.ErrorBadTextView)
TextView mBadErrorTextView;
public ResultViewHolder(#NonNull View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
Fragment class:
public class Tab1Fragment_GrammarChecker extends Fragment {
private static final String TAG = "Tab1Fragment";
#BindView(R.id.InputTextEditText)
EditText mInputGrammarEditText;
#BindView(R.id.ErrorsRecyclerView)
RecyclerView mErrorsRecyclerView;
List<Errors> errors = new ArrayList<>();
public ArrayList<Errors> errorArrayList = new ArrayList<>();
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.tab1_grammar_checker, container, false);
ButterKnife.bind(this, view);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
mErrorsRecyclerView.setLayoutManager(layoutManager);
loadJson();
return view;
}
#OnClick(R.id.checkGrammarButton)
public void setOnClick(View view){
Toast.makeText(getActivity(), "Check Grammar", Toast.LENGTH_LONG).show();
}
public void loadJson(){
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl("https://api.textgears.com")
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
Service serviceAPI = retrofit.create(Service.class);
Call<List<Errors>> loadErrorsCall = serviceAPI.readErrors(userInput, apiKey);
loadErrorsCall.enqueue(new Callback<List<Errors>>() {
#Override
public void onResponse(Call<List<Errors>> call, Response<List<Errors>> response) {
errors = new ArrayList<>(response.body());
Log.i("ORIG. ARRAY SIZE", String.valueOf(errors.size()));
if (errors != null){
for (int i = 0; i < 5; i++){
errorArrayList.add(errors.get(i));
}
Log.i("NEW ARRAY SIZE", String.valueOf(errorArrayList.size()));
}
mErrorsRecyclerView.setItemAnimator(new DefaultItemAnimator());
mErrorsRecyclerView.setAdapter(new ResultAdapter(getContext(), errorArrayList));
}
#Override
public void onFailure(Call<List<Errors>> call, Throwable t) {
Log.i("Error: ", t.getMessage());
}
});
}
}
Your API Interface should be like following:
public interface Service {
#GET("/check.php")
Call<List<Errors>> readErrors(#Query("userInput") String userInput,
#Query("apiKey") String apiKey);
}
So, replace your interface with this one.
A TIP: Please add a layout manager to the recycler view:
LinearLayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
mErrorsRecyclerView.setLayoutManager(mLayoutManager);

Two differents request, two differents Calls and One Recycler

I am trying to do this..
.
I am consuming two different requests resources from the same API and in MainActivity doing two different calls. But, I can't show the content I want from both JSON on one RecyclerView view.
MainActivity.java
public class MainActivity extends AppCompatActivity {
private Retrofit retrofit;
private static final String TAG = "Football";
private RecyclerView recyclerView;
private ListaPartidosAdapter listaPartidosAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView)findViewById(R.id.recyclerView);
listaPartidosAdapter = new ListaPartidosAdapter(this);
recyclerView.setAdapter(listaPartidosAdapter);
recyclerView.setHasFixedSize(true);
final LinearLayoutManager layoutManager = new LinearLayoutManager(this, VERTICAL, true);
recyclerView.setLayoutManager(layoutManager);
retrofit = new Retrofit.Builder()
.baseUrl("http://api.football-data.org/v2/")
.addConverterFactory(GsonConverterFactory.create())
.build();
obtenerDatos();
}
private void obtenerDatos() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
String todayDate=df.format(calendar.getTime());
calendar.add(Calendar.DATE,3);
String endDate = df.format(calendar.getTime());
Log.i(TAG, "todayDate : " + todayDate);
Log.i(TAG, "endDate : " + endDate);
footballdataService service = retrofit.create(footballdataService.class);
Call<PartidosRespuesta> partidosRespuestaCall = service.obtenerlistaPartidos(todayDate,endDate);
Call<StandingsRespuesta> standingsRespuestaCall = service.obtenerStandings();
partidosRespuestaCall.enqueue(new Callback<PartidosRespuesta>() {
#Override
public void onResponse(Call<PartidosRespuesta> call, Response<PartidosRespuesta> response) {
if(response.isSuccessful()) {
PartidosRespuesta partidosRespuesta = response.body();
List<Partido> listaPartidos = partidosRespuesta.getMatches();
listaPartidosAdapter.adicionarListaPartidos((ArrayList<Partido>) listaPartidos);
}
else {
Log.e(TAG, "onResponse: " + response.errorBody());
}
}
#Override
public void onFailure(Call<PartidosRespuesta> call, Throwable t) {
Log.e(TAG, "onFailure: " + t.getMessage());
}
});
standingsRespuestaCall.enqueue(new Callback<StandingsRespuesta>() {
#Override
public void onResponse(Call<StandingsRespuesta> call, Response<StandingsRespuesta> response) {
if(response.isSuccessful()) {
StandingsRespuesta standingsRespuesta = response.body();
List<Stand> listaStands = standingsRespuesta.getStandings();
listaPartidosAdapter.adicionarListaStands((ArrayList<Stand>) listaStands);
}
}
#Override
public void onFailure(Call<StandingsRespuesta> call, Throwable t) {
}
});
}
}
As I say before, each request has a different enqueue Call. I don't know if it is the right way of do it but think yes because each call has its own service.
ListaPartidosAdapter.java
public class ListaPartidosAdapter extends RecyclerView.Adapter<ListaPartidosAdapter.ViewHolder> {
private static final String TAG = "Football_Adapter";
private ArrayList<Partido> dataset;
private ArrayList<Stand> dataset_stand;
private Context context;
public ListaPartidosAdapter(Context context) {
this.context = context;
this.dataset = new ArrayList<Partido>();
this.dataset_stand = new ArrayList<Stand>();
}
#Override
public ListaPartidosAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_partidos, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ListaPartidosAdapter.ViewHolder holder, int position) {
Partido p = dataset.get(position);
String status = p.getStatus();
if (status.equals("SCHEDULED")){
String status_ = "SCH";
holder.status.setText(status_);
}
holder.utcDate.setText(p.getUtcDate());
Partido.EquipoCasa homeTeam = p.getHomeTeam();
String id_homeTeam = homeTeam.getId();
holder.homeTeam.setText(homeTeam.getName());
Partido.EquipoVisita awayTeam = p.getAwayTeam();
holder.awayTeam.setText(awayTeam.getName());
Stand s = dataset_stand.get(position);
Stand.Table table = (Stand.Table) s.getTable();
Stand.Table.Equipo team = (Stand.Table.Equipo) table.getEquipo();
String id_equipo = team.getId();
holder.homeTeam.setText(team.getName());
if(id_homeTeam.equals(id_equipo)){
Glide.with(context)
.load(team.getCrestUrl())
.centerCrop()
.crossFade()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(holder.team);
//holder.team.setImageDrawable(team.getCrestUrl());
}
}
#Override
public int getItemCount() {
return dataset.size()+dataset_stand.size();
}
public void adicionarListaPartidos(ArrayList<Partido> listaPartidos){
dataset.addAll(listaPartidos);
notifyDataSetChanged();
}
public void adicionarListaStands(ArrayList<Stand> listaStands){
dataset_stand.addAll(listaStands);
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView status;
private TextView utcDate;
private TextView homeTeam;
private TextView awayTeam;
public ImageView team;
public ViewHolder(View itemView) {
super(itemView);
status = (TextView) itemView.findViewById(R.id.status);
utcDate = (TextView) itemView.findViewById(R.id.utcDate);
homeTeam = (TextView) itemView.findViewById(R.id.homeTeam);
awayTeam = (TextView) itemView.findViewById(R.id.awayTeam);
team = (ImageView) itemView.findViewById(R.id.team);
}
}
}
The problem comes in this line. Stand s = dataset_stand.get(position);, if a comment it with code below, It works without using the second JSON or the second request but as I showed on image I want to merge two different requests on the same RecyclerView view.
The problem statement
The problem comes in this line. Stand s = dataset_stand.get(position);, if a comment it with code below, It works without using the second JSON or the second request.
Yes, it's expected. Why? Because you're calling RecyclerView.Adapter#notifyDataSetChanged() method inside adicionarListaPartidos(ArrayList<Partido> listaPartidos) after inserting data to ArrayList in RecyclerView array adapter class which informs ArrayAdapter to redraw/refresh components inside RecyclerView container.
Now RecycelrView starts binding component within the onBindViewHolder(ListaPartidosAdapter.ViewHolder holder, int position) method. So, what's happening here?
#Override
public void onBindViewHolder(ListaPartidosAdapter.ViewHolder holder, int position) {
Partido p = dataset.get(position);
String status = p.getStatus();
if (status.equals("SCHEDULED")){
String status_ = "SCH";
holder.status.setText(status_);
}
holder.utcDate.setText(p.getUtcDate());
Partido.EquipoCasa homeTeam = p.getHomeTeam();
// other code
Stand s = dataset_stand.get(position); // <====== the problem
// other code
}
Your dataset_stand list will be empty (size of the list is 0) until you get data/response from standingsRespuestaCall.enqueue() method in your activity.
Keep in mind that Retrofit's Call#enque() method is Asynchronous. Which means in your case obtenerDatos() method executes top to bottom in a single hit. You only get data when Retrofit returns success response with onResponse() method.
The easiest way to fix this issue is to comment out the notifyDataSetChanged() method inside adicionarListaPartidos(ArrayList<Partido> listaPartidos). Like below
public void adicionarListaPartidos(ArrayList<Partido> listaPartidos){
dataset.addAll(listaPartidos);
// notifyDataSetChanged(); // <====== just COMMENT OUT this line
}
This will prevent onBindViewHolder() being called. When the second request standingsRespuestaCall.enqueue() completes it's operation, as per your code notifies dataset changed. Like below.
public void adicionarListaStands(ArrayList<Stand> listaStands){
dataset_stand.addAll(listaStands);
notifyDataSetChanged(); // <==== DO NOT remove this
}
Side Note: Your code is problematic. You're using multiple request to fill a single RecyclerView container. Your RecyclerView fails to display record if standingsRespuestaCall.enqueue() fails to get response from server.
PS: Please check method names I mentioned in my answer properly and alter the code accordingly. Do not get confused with the method names.

Using retrofit with IMDB API

I'm building a simple IMDB app and I'm almost done save for one tiny detail. The API(http://www.omdbapi.com/) supplies only 10 movies at a time, and the user can specify which "page" do they want. I would like to retrieve all entries. My code looks something like this:
//This populates the list
private void populateList(String title) {
myAPI.getSearchResults(title, page).enqueue(new Callback<Movies>() {
#Override
public void onResponse(Call<Movies> call, Response<Movies> response) {
movies = response.body().getSearch();
recyclerView.setAdapter(new ItemAdapter(movies));
recyclerView.addOnItemTouchListener(
new ItemClickableListener(getActivity(), new ItemClickableListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
String id = movies.get(position).getImdbID();
showDetails(id, view);
}
}));
}
#Override
public void onFailure(Call<Movies> call, Throwable t) {
Log.d(TAG, "Error: " + t);
}
});
}
And in my interface:
//For populating the list
#GET("?")
Call<Movies> getSearchResults(#Query("s") String title, #Query("page") int pages);
There is a way to know how many entries there are in total but the query must run at least once to retrieve that info. I tried fixing it with a "do...while" loop and adding each consecutive batch of movies to a list and only then populating the RecyclerView but it just wouldn't work (it would leave the loop without displaying a thing). Maybe I overlooked something and that is the correct answer, but even then - Isn't there a more elegant approach?
I think you need EndlessRecyclerView to retrieve pages ten by ten. with following code:
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mAdapter = new MyAdapter(getActivity(), this);
scrollListener = new EndlessRecyclerOnScrollListener((LinearLayoutManager) mRecyclerView.getLayoutManager()) {
#Override
public void onLoadMore(int page) {
callWebservice(page);
}
};
mRecyclerView.addOnScrollListener(scrollListener);
mRecyclerView.setAdapter(mAdapter);
When callWebservice is done add Items to your list:
#Override
public void onResponse(Call<List<ShortVideoModel>> call, Response<List<ShortVideoModel>> response) {
mAdapter.addItems(response.body());
}
I ended up checking out EndlessRecyclerView and it works almost perfectly, but I've run into a few issues so I'm posting the code here. It kept stacking listeners and adapters so I swap them. It also kept scrolling up each time data is inserted so I forced it to stay but it's little jittery.
public class SearchFragment extends Fragment {
final String TAG = "LOG.SearchFragment";
final String baseUrl = "http://www.omdbapi.com/";
Button searchButton;
EditText searchField;
RecyclerView recyclerView;
LinearLayoutManager llm;
String title = "";
int page = 1;
List<Search> movies;
Gson gson;
Retrofit retrofit;
MyAPI myAPI;
ItemClickableListener listener;
EndlessRecyclerOnScrollListener scrollListener;
int firstItem;
float topOffset;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "Starting SearchFragment...");
return inflater.inflate(R.layout.search_fragment, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//Preparing RecyclerView
recyclerView = (RecyclerView) getActivity().findViewById(R.id.recycler_view);
llm = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(llm);
setOnScrollManager();
//List for the movies
movies = new ArrayList<>();
//UI
searchField = (EditText) getActivity().findViewById(R.id.search_field);
searchButton = (Button) getActivity().findViewById(R.id.search_button);
searchButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (!searchField.getText().toString().equals("")) {
gson = new GsonBuilder().create();
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
myAPI = retrofit.create(MyAPI.class);
title = searchField.getText().toString();
movies.clear();
page=1;
setOnScrollManager();
fetchMovies(title, page);
}
}
});
}
private void setOnScrollManager() {
if (scrollListener!=null) recyclerView.removeOnScrollListener(scrollListener);
scrollListener = new EndlessRecyclerOnScrollListener((LinearLayoutManager) recyclerView.getLayoutManager()) {
//This happens when user scrolls to bottom
#Override
public void onLoadMore(int newPage) {
Log.d(TAG, "OnLoadMore "+newPage);
//Preparing the scroll
firstItem = llm.findFirstVisibleItemPosition();
View firstItemView = llm.findViewByPosition(firstItem);
topOffset = firstItemView.getTop();
//Getting new page
page=newPage;
fetchMovies(title, page);
}
};
recyclerView.addOnScrollListener(scrollListener);
}
//This populates the list
private void fetchMovies(String title, int page) {
Log.d(TAG, "Getting "+title+", page "+page);
myAPI.getSearchResults(title, page).enqueue(new Callback<Movies>() {
#Override
public void onResponse(Call<Movies> call, Response<Movies> response) {
if (movies.size()==0) Toast.makeText(getActivity(), "No movies found", Toast.LENGTH_SHORT).show();
movies.addAll(response.body().getSearch());
//We swap the adatper's content when user scrolls down and loads more data
recyclerView.setRecycledViewPool(new RecyclerView.RecycledViewPool());
recyclerView.swapAdapter(new ItemAdapter(movies), true);
//Scrolling
Log.d(TAG, "Scrolling to "+firstItem);
llm.scrollToPositionWithOffset(firstItem, (int) topOffset);
//We avoid stacking up listeners
if (listener!=null) recyclerView.removeOnItemTouchListener(listener);
listener = new ItemClickableListener(getActivity(), new ItemClickableListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
String id = movies.get(position).getImdbID();
showDetails(id, view);
}
});
recyclerView.addOnItemTouchListener(listener);
}
#Override
public void onFailure(Call<Movies> call, Throwable t) {
Log.d(TAG, "Error: " + t);
}
});
}
//This gets the movie details
private void showDetails(String id, final View view){
myAPI.getDetails(id).enqueue(new Callback<MovieDetails>() {
#Override
public void onResponse(Call<MovieDetails> call, Response<MovieDetails> response) {
showPopup(response.body(), view);
}
#Override
public void onFailure(Call<MovieDetails> call, Throwable t) {
Log.d(TAG, "Error: " + t);
}
});
}
//This displays the movie details
private void showPopup(MovieDetails details, View anchorView) {
View popupView = getActivity().getLayoutInflater().inflate(R.layout.popup_layout, null);
PopupWindow popupWindow = new PopupWindow(popupView,
RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT);
TextView title = (TextView) popupView.findViewById(R.id.movie_detail_title);
TextView year = (TextView) popupView.findViewById(R.id.movie_detail_year);
TextView rating = (TextView) popupView.findViewById(R.id.movie_detail_rating);
TextView director = (TextView) popupView.findViewById(R.id.movie_detail_director);
TextView stars = (TextView) popupView.findViewById(R.id.movie_detail_stars);
TextView desc = (TextView) popupView.findViewById(R.id.movie_detail_desc);
title.setText(details.getTitle());
title.setTextColor(Color.parseColor("#ffffff"));
year.setText(details.getYear());
year.setTextColor(Color.parseColor("#ffffff"));
rating.setText(details.getImdbRating()+"/10");
rating.setTextColor(Color.parseColor("#ffffff"));
director.setText("Dir: "+details.getDirector());
director.setTextColor(Color.parseColor("#ffffff"));
stars.setText("Stars: "+details.getActors());
stars.setTextColor(Color.parseColor("#ffffff"));
desc.setText(details.getPlot());
desc.setTextColor(Color.parseColor("#ffffff"));
UrlValidator urlValidator = new UrlValidator();
if (urlValidator.isValid(details.getPoster())) {
ImageView poster = (ImageView) popupView.findViewById(R.id.movie_detail_poster);
ImageLoader imageLoader = ImageLoader.getInstance();
imageLoader.displayImage(details.getPoster(), poster);
}
// If the PopupWindow should be focusable
popupWindow.setFocusable(true);
// If you need the PopupWindow to dismiss when when touched outside
popupWindow.setBackgroundDrawable(new ColorDrawable(Color.parseColor("#CC000000")));
int location[] = new int[2];
// Get the View's(the one that was clicked in the Fragment) location
anchorView.getLocationOnScreen(location);
// Using location, the PopupWindow will be displayed right under anchorView
popupWindow.showAtLocation(anchorView, Gravity.NO_GRAVITY,
location[0], location[1] + anchorView.getHeight());
}
}

Categories

Resources