Two differents request, two differents Calls and One Recycler - android

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.

Related

Recycleview not populated with JSON and Retrofit?

I am making a simple app in which the user will able to search for books by its name, with google.com book api. For now, I wish to present a list of books with android in their name. I am doing it with Retrofit2 and RecycleView, but nothing is showing.
MainActivity:
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
KnjigaAdapter knjigaAdapter;
List<KnjigaModel> listaKnjiga;
public static final String BASE_URL = "https://www.googleapis.com/books/";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
uzmiKomentare();
}
public void uzmiKomentare() {
Gson gson = new GsonBuilder().serializeNulls().create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
KnjigaApi knjigaApi = retrofit.create(KnjigaApi.class);
final Call<KnjigaModel> pozivZaListuKnjiga = knjigaApi.getKnjige("android");
pozivZaListuKnjiga.enqueue(new Callback<KnjigaModel>() {
#Override
public void onResponse(Call<KnjigaModel> call, Response<KnjigaModel> response) {
if (!response.isSuccessful()) {
return;
}
//generateRecycleView(WHAT TO PUT HERE!!!!);
}
#Override
public void onFailure(Call<KnjigaModel> call, Throwable t) {
Log.d("MainActivity:", t.getMessage());
}
});
}
private void generateRecycleView(List<KnjigaModel> knjige) {
listaKnjiga = new ArrayList<>();
recyclerView = findViewById(R.id.recycleview);
knjigaAdapter = new KnjigaAdapter(this, knjige);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(knjigaAdapter);
if (knjigaAdapter.getItemCount() == 0){
Log.i("List is empty: ","YES");
}
else {
Log.i("list is empty: ","No");
}
}
}
api inteface:
public interface KnjigaApi {
#GET("v1/volumes")
Call<KnjigaModel> getKnjige(#Query("q") String knjiga);
}
model class:
public class KnjigaModel {
#SerializedName("title")
#Expose
private String imeKnjige;
#SerializedName("authors")
#Expose
private String imeAutora;
#SerializedName("thumbnail")
#Expose
private String slikaKnjige;
public KnjigaModel(String imeKnjige, String imeAutora,String slikaKnjige) {
this.imeKnjige = imeKnjige;
this.imeAutora = imeAutora;
this.slikaKnjige = slikaKnjige;
}
public String getImeKnjige() {
return imeKnjige;
}
public String getImeAutora() {
return imeAutora;
}
public String getSlikaKnjige() {
return slikaKnjige;
}
}
and my adapter:
public class KnjigaAdapter extends RecyclerView.Adapter<KnjigaAdapter.KomentariViewHolder> {
private List<KnjigaModel> listaKnjiga;
private LayoutInflater inflater;
private Context context;
public KnjigaAdapter(Context context, List<KnjigaModel> listaKnjiga) {
this.listaKnjiga = listaKnjiga;
this.context = context;
}
#Override
public KomentariViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
inflater = LayoutInflater.from(context);
// Inflate the custom layout
View postView = inflater.inflate(R.layout.single_item, parent, false);
// Return a new holder instance
return new KomentariViewHolder(postView);
}
#Override
public void onBindViewHolder(KomentariViewHolder holder, int position) {
KnjigaModel knjige = listaKnjiga.get(position);
holder.naslovKnjige.setText(knjige.getImeKnjige());
holder.imeAutora.setText(knjige.getImeAutora());
Glide.with(context)
.load(knjige.getSlikaKnjige())
.into(holder.slikaKnjige);
}
#Override
public int getItemCount() {
return listaKnjiga.size();
}
public class KomentariViewHolder extends RecyclerView.ViewHolder {
private TextView naslovKnjige;
private TextView imeAutora;
private ImageView slikaKnjige;
public KomentariViewHolder(View itemView) {
super(itemView);
naslovKnjige = itemView.findViewById(R.id.ime_knjige);
imeAutora = itemView.findViewById(R.id.autor_knjige);
slikaKnjige = itemView.findViewById(R.id.sika_korica);
}
}
}
my JSON format:
https://www.googleapis.com/books/v1/volumes?q=android
First of all, I strongly recommend to use RxJava for asynchronous requests, though it's totally optional.
Your Problem:
Your "getKnjige" Method returns only ONE Model, though the endpoint is named volumes (plural), you need to wrap your KnjigaModel in a class like
data class KnjigaResponse(val items: List<KnjigaModel>)
(Kotlin for simplicity, you can also generate a Java class with a single member volumes member that holds a list of KnjigaModel)
In addition, your model is wrong. authors is not a string, but a List of Strings, and "thumbnail" is wrapped in an "imageLinks" Object, and title is wrapped in a volumeInfo Object.
Your retrofit interface then would look like this:
public interface KnjigaApi {
#GET("v1/volumes")
Call<KnjigaResponse> getKnjige(#Query("q") String knjiga);
Request:
final Call<KnjigaResponse> pozivZaListuKnjiga = knjigaApi.getKnjige("android");
pozivZaListuKnjiga.enqueue(new Callback<KnjigaResponse>() {
#Override
public void onResponse(Call<KnjigaResponse> call, Response<KnjigaResponse> response) {
if (!response.isSuccessful()) {
return;
}
generateRecycleView(response.items);
}
#Override
public void onFailure(Call<KnjigaResponse> call, Throwable t) {
Log.d("MainActivity:", t.getMessage());
}
});

RecyclerView Not showing data from Json array

Whenever i try to add data to recycler view, the recycler view doesn't show any data. I tried debugging the program and I am successfully getting JSON data using Retrofit into the application(Checked by printing it in Log). But RecyclerView shows no data.Here is my code:
CartActivity.java
public class CartActivity extends AppCompatActivity {
RecyclerView listshowrcy;
List<CartDisplay> cartlist = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cart);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
API api = retrofit.create(API.class);
String username = getIntent().getStringExtra("Username");
Call<List<CartDisplay>> call = api.getCartContent(username);
call.enqueue(new Callback<List<CartDisplay>>() {
#Override
public void onResponse(Call<List<CartDisplay>> call, Response<List<CartDisplay>> response) {
List<CartDisplay> cart = response.body();
for(CartDisplay cartContent : cart){
cartlist.add(cartContent);
}
}
#Override
public void onFailure(Call<List<CartDisplay>> call, Throwable t) {
}
});
listshowrcy = (RecyclerView)findViewById(R.id.cartList);
listshowrcy.setHasFixedSize(true);
CartAdapter cardadapter = new CartAdapter(cartlist,this,username);
listshowrcy.setAdapter(cardadapter);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
listshowrcy.setLayoutManager(linearLayoutManager);
}
}
CartAdapter.java
public class CartAdapter extends RecyclerView.Adapter<CartAdapter.Holderview> {
private List<CartDisplay> cartlist;
private Context context;
private String username;
public CartAdapter(List<CartDisplay> cartlist, Context context,String username) {
this.cartlist = cartlist;
this.context = context;
this.username = username;
}
#Override
public Holderview onCreateViewHolder(ViewGroup parent, int viewType) {
View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.cart_item,parent,false);
return new Holderview(layout);
}
#Override
public void onBindViewHolder(Holderview holder, int position) {
holder.pname.setText(cartlist.get(position).getP_name());
holder.pquant.setText(cartlist.get(position).getQuantity());
holder.price.setText(String.valueOf(cartlist.get(position).getPrice()));
}
#Override
public int getItemCount() {
return cartlist.size();
}
class Holderview extends RecyclerView.ViewHolder
{
TextView pname;
TextView pquant;
TextView price;
Holderview(View itemview){
super(itemview);
pname = (TextView)itemview.findViewById(R.id.product_name);
pquant = (TextView)itemview.findViewById(R.id.product_quant);
price = (TextView)itemview.findViewById(R.id.product_price);
}
}
}
After you get your response you must notify adapter, that data has changed:
#Override
public void onResponse(Call<List<CartDisplay>> call, Response<List<CartDisplay>> response) {
List<CartDisplay> cart = response.body();
cartList.clear(); // don't forget to clear list, to avoid duplicates
for(CartDisplay cartContent : cart){
cartlist.add(cartContent);
}
adapter.notifyDataSetChanged();
}
Another way: you can create method: adapter.setData(cardList)
And there refresh adapter data and call notifyDataSetChanged()
In addition to #kdblue's answer, there is another issue with your code. The adapter doesn't know that new data has been added to the underlying list.
You can either use notifyDataSetChanged:
List<CartDisplay> cart = response.body();
for(CartDisplay cartContent : cart){
cartlist.add(cartContent);
}
cardadapter.notifyDataSetChanged();
Or let the adapter handle new items directly, by adding a method to the adapter like:
public void add(CartDisplay cartDisplay) {
cartlist.add(user);
notifyItemInserted(cartlist.size());
}
And adding the items directly to the adapter:
List<CartDisplay> cart = response.body();
for(CartDisplay cartContent : cart){
cardadapter.add(cartContent);
}
Be aware that you will have to change you code structure to apply these strategies.

onCreateView wait queries result

I need to execute a query to my DB to get some IDs and then use those IDs to execute another query to Realm DB and return the result to the Adapter which is used to create UI. My problem is that the adapter is created in onCreatView (main thread) and i need to let that wait queries result. Do you know how can i do? Really thanks :)
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Realm realm = Realm.getDefaultInstance();
cardList = new ArrayList(realm.where(Card.class).findAll().sort("rarity"));
View rootView = inflater.inflate(R.layout.inventory_fragment_layout, null);
RecyclerView recyclerView = rootView.findViewById(R.id.inventory_recycler);
recyclerView.setLayoutManager(new GridLayoutManager(this.getContext(), 2));
adapter = new inventoryFragmentRecyclerAdapter(this.getContext(), getCards()); <----- getCards() is used here.
adapter.setClickListener(this);
recyclerView.setAdapter(adapter);
TextView topText = rootView.findViewById(R.id.inv_topText);
topText.setTypeface(Typeface.createFromAsset(getActivity().getAssets(), "fonts/Square.ttf"));
return rootView;
}
.
.
.
.
.
.
public List<Card> getCards() { //It is used when adapter is created, and it need to contain all cards to display
//Realm realm = Realm.getDefaultInstance();
String urlToGet = "myurl";
String user_name = settings.getString("username", null);
OkHttpClient client = new OkHttpClient();
RequestBody formBody = new FormBody.Builder()
.add("user_name", user_name)
.build();
Request request = new Request.Builder()
.url(urlToGet)
.post(formBody)
.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);
}
responseCardInInventory = response.body().string();
handler.post(new Runnable() {
#Override
public void run() {
List<CardInInv> cardListInInv = new ArrayList<>();
Gson gson = new Gson();
CardInInventory civ = gson.fromJson(responseCardInInventory, CardInInventory.class);
cardListInInv = getCards(civ);
//HERE I NEED TO USE REALM TO EXECUTE A QUERY USING cardListInInv
}
});
}
});
//HERE I NEED TO RETURN THE RESULT OF THE QUERY EXECUTED BEFORE
return cardsIninv;
}
EDIT 1: Hre is my adapter code
public class inventoryFragmentRecyclerAdapter extends RecyclerView.Adapter<inventoryFragmentRecyclerAdapter.ViewHolder> {
private List<Card> dataCard = new ArrayList<>();
private LayoutInflater mInflater;
private ItemClickListener mClickListener;
// data is passed into the constructor
public inventoryFragmentRecyclerAdapter(Context context, List<Card> data) {
this.mInflater = LayoutInflater.from(context);
this.dataCard = data;
}
// inflates the cell layout from xml when needed
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.inventory_rw, parent, false);
return new ViewHolder(view);
}
// binds the data to the in each cell
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.cardImage.setImageBitmap(BitmapFactory.decodeByteArray(dataCard.get(position).getCardImage(), 0, dataCard.get(position).getCardImage().length));
}
// total number of cells
#Override
public int getItemCount() {
return dataCard.size();
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ImageView cardImage;
//instanzio le componenti della pagina
ViewHolder(View itemView) {
super(itemView);
cardImage = itemView.findViewById(R.id.inv_rw_cardimage);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (mClickListener != null) mClickListener.onItemClick(view, getAdapterPosition());
}
}
// convenience method for getting data at click position
Card getItem(int id) {
return dataCard.get(id);
}
// allows clicks events to be caught
public void setClickListener(ItemClickListener itemClickListener) {
this.mClickListener = itemClickListener;
}
// parent activity will implement this method to respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}
Move your query to your adapter and start your adapter with an empty list. Make your query call at the end of your constructor. When you've successfully finished your query, notify your adapter that it has changed and let it handle the changes with your new data at the end of your queries finishing successfully. So after doing that your adapter would look something like this.
public inventoryFragmentRecyclerAdapter(Context context, List<Card> data) {
this.mInflater = LayoutInflater.from(context);
getCards();
}
public void getCards() { //It is used when adapter is created, and it need to contain all cards to display
//Realm realm = Realm.getDefaultInstance();
String urlToGet = "myurl";
String user_name = settings.getString("username", null);
OkHttpClient client = new OkHttpClient();
RequestBody formBody = new FormBody.Builder()
.add("user_name", user_name)
.build();
Request request = new Request.Builder()
.url(urlToGet)
.post(formBody)
.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);
}
responseCardInInventory = response.body().string();
handler.post(new Runnable() {
#Override
public void run() {
List<CardInInv> cardListInInv = new ArrayList<>();
Gson gson = new Gson();
CardInInventory civ = gson.fromJson(responseCardInInventory, CardInInventory.class);
cardListInInv = getCards(civ);
//use realm to do all your card inventory stuff to get your resulting object
dataCard = yourObjectListAfterUsingRealm;
notifyDataSetChanged();
}
});
}
});
}
move This line adapter = new inventoryFragmentRecyclerAdapter(context, yourList);
inside onResponse() and pass the list directly without the getCards();
Edit:
initialize your adapter when your data is ready.where ? this is based on your needs.
or initialize it with empty list and call adapter.notifyDataSetChanged() when something changed (added or removed from the list or a brand new list is initialized)
Create your adapter with empty list
adapter = new inventoryFragmentRecyclerAdapter(this.getContext(), new ArrayList<Card>());
and then in your adapter create a method like this
public void updateCardList(List<Card> cardList) {
this.mCards = cardList;
notifyDataSetChanged();
}
and call this function in the
#Override
public void onResponse(Call call, final Response response)

How to delete duplicate data that I get after updating data in firebase console

I am developing an android app that displays the ranks of students based on their marks retrieved from the firebase database. Everything is working fine but, when I update the marks in the db, it keeps the old data and adds the new data in the recyclerView. I can restart the app to refresh the data. But while it is still running, it shows the old data too.
Below is my firebase data:
Student1: {
c: 70,
cPlus: 90,
java: 70,
name: "Samson",
regno: "16sksb7034",
unix: 60
}
Student2: {
c: 20,
cPlus: 85,
java: 68,
name: "Samson",
regno: "16sksb7034",
unix: 86
}
Student3: {
c: 70,
cPlus: 70,
java: 80,
name: "Samson",
regno: "16sksb7034",
unix: 90
}
Here is my dataModel class:
public class Marks {
private String name;
private String regno;
private int c;
private int cPlus;
private int java;
private int unix;
private int percentage;
public Marks() {}
public Marks(int c, int cPlus, int java, int unix) {
this.c = c;
this.cPlus = cPlus;
this.java = java;
this.unix = unix;
}
public int getPercentage() {
return percentage;
}
public void setPercentage(int percentage) {
this.percentage = percentage;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRegno() {
return regno;
}
public void setRegno(String regno) {
this.regno = regno;
}
public int getC() {
return c;
}
public void setC(int c) {
this.c = c;
}
public int getcPlus() {
return cPlus;
}
public void setcPlus(int cPlus) {
this.cPlus = cPlus;
}
public int getJava() {
return java;
}
public void setJava(int java) {
this.java = java;
}
public int getUnix() {
return unix;
}
public void setUnix(int unix) {
this.unix = unix;
}
}
class MarksComparator implements Comparator<Marks> {
#Override
public int compare(Marks marks1, Marks marks2) {
int Marks1Total = marks1.getPercentage();
int Marks2Total = marks2.getPercentage();
if (Marks2Total < Marks1Total) {
return -1;
} else if (Marks2Total > Marks1Total) {
return 1;
} else {
return 0;
}
}
}
Here's my activity class:
public class MarksFragment extends Fragment{
private List<Marks> mMarksList = new ArrayList<>();
private RecyclerView mRecyclerView;
private MyAdapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private FirebaseDatabase mDatabase;
private DatabaseReference mReference;
private int total=0;
public MarksFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_marks, container, false);
mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
// specify an adapter (see also next example)
/*mAdapter = new MyAdapter(getContext(),mMarksList);
mAdapter.notifyDataSetChanged();
mRecyclerView.setAdapter(mAdapter);*/
//get Firebase Reference
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
mDatabase = FirebaseDatabase.getInstance();
mReference = mDatabase.getReference();
mReference.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
fetchData(dataSnapshot);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
fetchData(dataSnapshot);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return view;
}
public void findPercentage(Marks value) {
total =value.getC() + value.getcPlus() + value.getJava() + value.getUnix();
value.setPercentage(total);
}
private void fetchData(DataSnapshot dataSnapshot) {
Marks value = dataSnapshot.getValue(Marks.class);
Log.v("Marks Fragment", "" +value);
findPercentage(value);
mMarksList.add(value);
Collections.sort(mMarksList, new MarksComparator());
// specify an adapter (see also next example)
mAdapter = new MyAdapter(getContext(),mMarksList);
mAdapter.notifyDataSetChanged();
mRecyclerView.setAdapter(mAdapter);
Here is my adapter class:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder>{
private Context mContext;
private List<Marks> marksList;
public MyAdapter(Context mContext, List<Marks> marksList) {
this.mContext = mContext;
this.marksList = marksList;
}
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView mItemName, mItemRegNo, mItemNo, mTotal;
CircleImageView mImageView;
public MyViewHolder(View view) {
super(view);
mItemName = (TextView) view.findViewById(R.id.card_name);
mItemRegNo = (TextView) view.findViewById(R.id.card_regno);
mItemNo = (TextView) view.findViewById(R.id.item_id);
mImageView = (CircleImageView) view.findViewById(R.id.item_photo);
mTotal = view.findViewById(R.id.card_total);
}
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_item, parent, false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
Marks marks = marksList.get(position);
int count = position + 1;
holder.mItemName.setText("" + marks.getName());
holder.mItemRegNo.setText("" + marks.getRegno());
holder.mItemNo.setText("" + count);
holder.mImageView.setImageResource(R.drawable.after_cookie);
holder.mTotal.setText(""+ marks.getPercentage());
}
#Override
public int getItemCount() {
return marksList.size();
}
}
So the code does what its intended to do it retrieves the data and calculates the total and ranks the students. but when I update the data in firebase console the views in recyclerView duplicates temporarily. Like for example if I update Student1 unix value as 10 then two views will be shown in the recyclerView: 1 for previous value and 2 for updated value and again if I update the values it will yet show another views representing the new data without removing the old views. But if I restart recyclerView gets refreshed and its all ok but while I am running the app during the update it shows temporary duplicate views too.
I am new here and this is my first question so I can't even upload picture as you need 10 points to upload photo. I really hope someone help me out on this. I thank you in advance.
UPDATE
Here is link to the image:
When I start the app, the image is:
first Image
when I update the unix value of Student3, the image in recyclerView becomes like this:
After updating the data in firebase console
So, you see it adds new data as well as keeps the old data untill I restart.
Your problem is that you're never checking if the student already exists in your mMarksList so you're simply duplicating him by adding him again with new grades.
What I would do in you case is to add an unique id in firebase to each student.
Then you can check in your fetchData whether the student with that id is already in the array, delete him and add the new one.
private void fetchData(DataSnapshot dataSnapshot) {
Marks value = dataSnapshot.getValue(Marks.class);
Log.v("Marks Fragment", "" +value);
findPercentage(value);
// Get an iterator.
Iterator<Marks> ite = mMarksList.iterator();
while(ite.hasNext()) {
Marks iteValue = ite.next();
if(iteValue.getId().equals(value.getId())) ite.remove();
}
mMarksList.add(value);
....
}
Optionally To make that even cleaner, you can override the equals and hashcode methods in your Marks data model, so that a Marks object is considered the same if the id is equal. More
//ASSUMING THAT ID IS int
#Override
public int hashCode() {
return id;
}
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
Marks other = (Marks) obj;
if (this.getId != other.getId) {
return false;
}
return true;
}
Then it's possible to either use a hashmap, which will override the old student automatically or a arraylist as is and iterate through it before and check if a student equals your new student, like this:
private void fetchData(DataSnapshot dataSnapshot) {
Marks value = dataSnapshot.getValue(Marks.class);
Log.v("Marks Fragment", "" +value);
findPercentage(value);
// Use an iterator.
Iterator<Marks> ite = mMarksList.iterator();
while(ite.hasNext()) {
Marks iteValue = ite.next();
if(iteValue.equals(value)) ite.remove();
}
mMarksList.add(value);
....
}

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