I'm new to Java, so please excuse me if this is a dumb question.
I got this class:
public class CustomDrawerAdapter extends ArrayAdapter<DrawerItem> {
Context context;
List<DrawerItem> drawerItemList;
int layoutResID;
private FriendInfo[] friends = null;
public void setFriendList(FriendInfo[] friends)
{
this.friends = friends;
}
public int getCount() {
return friends.length;
}
public FriendInfo getItem(int position) {
return friends[position];
}
public long getItemId(int position) {
return 0;
}
public CustomDrawerAdapter(Context context, int layoutResourceID,
List<DrawerItem> listItems) {
super(context, layoutResourceID, listItems);
this.context = context;
this.drawerItemList = listItems;
this.layoutResID = layoutResourceID;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
DrawerItemHolder drawerHolder;
View view = convertView;
if (view == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
drawerHolder = new DrawerItemHolder();
view = inflater.inflate(layoutResID, parent, false);
drawerHolder.ItemName = (TextView) view
.findViewById(R.id.drawer_itemName);
drawerHolder.icon = (ImageView) view.findViewById(R.id.drawer_icon);
view.setTag(drawerHolder);
} else {
drawerHolder = (DrawerItemHolder) view.getTag();
}
DrawerItem dItem = (DrawerItem) this.drawerItemList.get(position);
drawerHolder.icon.setImageDrawable(view.getResources().getDrawable(
dItem.getImgResID()));
drawerHolder.ItemName.setText(friends[position].userName);
return view;
}
private static class DrawerItemHolder {
TextView ItemName;
ImageView icon;
}
At FriendInfo I get the following error:" 'getItem(int)' in '(My Package name).CustomDrawerAdapter' clashes with 'getItem(int)' in 'android.widget.ArrayAdapter'; attempting to use incompatible return type"
I'm new to Java and I don't know how to fix this, could somebody help me?
Your CustomDrawerAdapter extends ArrayAdapter<T> with T being a DrawerItem. T is a type parameter which is replaced by a type argument when you define your CustomDrawerAdapter class, the type argument being DrawerItem in your case.
So CustomDrawerAdapter is basically an adapter pulling the items to show from an array of DrawerItems.
The ArrayAdapter defines a method
public T getItem (int position)
which will be
public DrawerItem getItem (int position)
for your class CustomDrawerAdapter.
You might want to read this tutorial about generics: http://docs.oracle.com/javase/tutorial/java/generics. Note that generics are not easy to understand but you absolutely need a basic understanding when doing Android development.
If you decide to override that method then you can't change the return value unless the method has a different signature (name, plus the number and the type of its parameters).
You have to ask yourself: is CustomDrawerAdapter an adapter pulling its data from an array (or list) of FriendInfo or from an array of DrawerItem? Depending on the answer your class definition would be
public class CustomDrawerAdapter extends ArrayAdapter<DrawerItem>
or
public class CustomDrawerAdapter extends ArrayAdapter<FriendInfo>
Using both classes to back the ArrayAdapter doesn't make sense unless FriendInfo is extending DrawerItem and that seems unlikely.
If you really need a second array of Objects (FriendInfo) for whatever purposes, then don't modify the ArrayAdapter methods (like getCount(), getItemId(int) or getItem(int)) unless you know exactly what you're doing.
E.g. your getCount() is doomed to fail because it returns the size of FriendInfo[] instead of the number of DrawerItems and FriendInfo[] might not have the same number of elements (it certainly doesn't have before you call the setFriendList and who guarantees that the passed array has the same number of elements as the List you pass in the constructor?).
Because it extends ArrayAdapter<DrawerItem>, getItem needs to return a DrawerItem. If you can't find a clean way to fix this while extending ArrayAdapter, it's pretty easy to make your own adapter implementation by extending BaseAdapter.
Related
I'm making an app using TMDB API and have gotten stuck at a small issue.
TMDB API shows seasons and episodes which are empty, basically, those are yet to air but since those are empty, the app shows a blank item that I'm trying to get rid of.
Here's my adapter:
public class SeasonAdapter extends RecyclerView.Adapter<SeasonAdapter.ViewHolder> {
private final List<Season> seasons;
private final Context context;
private final RequestOptions requestOptions;
public SeasonAdapter(List<Season> seasons, Context context) {
this.seasons = seasons;
this.context = context;
requestOptions = new RequestOptions().centerCrop().placeholder(R.drawable.poster_placeholder).error(R.drawable.poster_placeholder);
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_season_item, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
final Season season = seasons.get(position);
holder.tvTitle.setText(season.getSeasonTitle());
if (season.getSeasonDate() != null && !season.getSeasonDate().isEmpty()) {
holder.tvDate.setText(context.getResources().getString(R.string.aired_on) + season.getSeasonDate());
} else {
holder.tvDate.setVisibility(View.GONE);
}
if (season.getSeasonEpisodes() == 0) {
seasons.remove(position);
}
holder.tvEpisodes.setText(String.valueOf(season.getSeasonEpisodes()) + context.getResources().getString(R.string.total_episodes));
Glide.with(context).load(season.getSeasonImageURL()).apply(requestOptions).into(holder.ivPoster);
holder.itemView.setOnClickListener(v -> {
Intent intent = new Intent(context, EpisodeActivity.class);
intent.putExtra("title", season.getShowTitle());
intent.putExtra("seasonTitle", season.getSeasonTitle());
intent.putExtra("seasonNo", season.getSeasonNo());
intent.putExtra("tvId", season.getTvId());
v.getContext().startActivity(intent);
});
}
#Override
public int getItemCount() {
return seasons.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public ImageView ivPoster;
public TextView tvTitle, tvDate, tvEpisodes;
public ViewHolder(#NonNull View itemView) {
super(itemView);
ivPoster = itemView.findViewById(R.id.ivSeasonPoster);
tvTitle = itemView.findViewById(R.id.tvSeasonTitle);
tvDate = itemView.findViewById(R.id.tvSeasonAired);
tvEpisodes = itemView.findViewById(R.id.tvSeasonEpisodes);
//Poster Corners
ivPoster.setClipToOutline(true);
}
}
}
I tried doing this:
if (season.getSeasonEpisodes() == 0) {
seasons.remove(position);
}
It does seem to hide the season which has no episodes but if a show has multiple seasons without episodes, my app crashes so I figured this isn't the right solution so any help is appreciated.
I suggest performing that removal logic in the constructor of the adapter rather than in onBind. onBind happens as the recycler view is finalising the details of each view holder immediately before it's shown to the user. You want to do as little as possible logic in here to keep the recycler view performant.
Inside the constructor (or even before the list is passed in) you should perform a loop and remove those items that don't meet the criteria before setting the instance variable.
It's been a long time since I wrote code in java and so I'd end up with unhelpful incorrect syntax if I tried to do it here.
I am getting a very strange bug in my application. To make it more clear I will create a similar example with minus code.
I am reading objects from Firebase Realtime Database with an addListenerForSingleValueEvent. While I am reading the objects, I am stored them in an Array that I passed to an Adapter in a Recycleview. At this point, I can say, after debugging, that all seems to work correctly.
Then in the Adapter, I have a code similar to this:
public class AdapterObject extends RecyclerView.Adapter<AdapterObject.ViewHolder> {
ArrayList<Object> objectList;
Context mContext;
public AdapterObject (Context context, ArrayList<Object> objectList){
this.mContext = context;
this.objectList = objectList;
}
#NonNull
#Override
public AdapterObject.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.object_grid_layout, parent,false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull AdapterObject.ViewHolder holder, int position) {
Object o = objectList.get(position);
Log.d("TAG", o.getAtribute());
if (o.getAtribute().equals("A")){
holder.atribute.setVisibility(View.VISIBLE);
}
#Override
public int getItemCount() {
return objectList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView atribute;
boolean favorite;
String descuento, precioOriginal;
public ViewHolder(#NonNull View itemView) {
super(itemView);
atribute = itemView.findViewById(R.id.atribute);
}
}
}
As you can see in the code if the current object has the attribute value == "A", then his Textview is displayed, otherwise, the Textview remains hidden.
All seems correct when I debug it because the objects and their attribute corresponds to the Database, but when I deploy the application in the Android simulator and I start going up and down on the Recycleview, the holders start to display the Textviews although the console debugs seems correct...
Is this normal in RecycleViews? How can I fix that? I have found this, do you think it has any relation?
This is an extract from the RecyclerView documentation
As the name implies, RecyclerView recycles those individual elements.
When an item scrolls off the screen, RecyclerView doesn't destroy its
view. Instead, RecyclerView reuses the view for new items that have
scrolled onscreen. This reuse vastly improves performance, improving
your app's responsiveness and reducing power consumption.
That means that when the view gets reused it will keep the current properties. It's up to you to change them when onBindViewHolder gets called.
In your specific case
#Override
public void onBindViewHolder(#NonNull AdapterObject.ViewHolder holder, int position) {
Object o = objectList.get(position);
Log.d("TAG", o.getAtribute());
if (o.getAtribute().equals("A")){
holder.atribute.setVisibility(View.VISIBLE);
} else. {
holder.atribute.setVisibility(View.GONE);
}
}
I have a ListView with a with a Custom Adapter that extends ArrayAdapter, My ListView has 5 different types of Items that it can hold, That's why I am using an ArrayAdapter well the issue is fairy odd to me, I am not sure how to debug this. I will post the video.
as you can see I'm clicking my Checkbox and Modal Items without any issue at the start but as soon as I scroll down and back up they don't click anymore! This is a really weird bug I'm facing. Here is how I do my adapter.
This is my adapter class code. It's fairly compact.
public class SettingAdapter extends ArrayAdapter<Setting> {
private List<Setting> settings;
public SettingAdapter(Context context) {
super(context, 0);
setSettings(new ArrayList<>());
}
public SettingAdapter(Context context, List<Setting> settings) {
super(context, 0);
setSettings(settings);
}
public void setSettings(List<Setting> settings) {
this.settings = settings;
clear();
addAll(settings);
notifyDataSetChanged();
}
#Override
public int getViewTypeCount() {
return 4;
}
#Override
public Setting getItem(int i) {
return settings.get(i);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
return Objects.requireNonNull(getItem(position)).getView(getContext());
}
}
I have an abstract class called Setting my ListView Items will extend that class and implement getView() and such.
Here is an example.
public class Summary extends Setting {
private View view;
public Summary(String name) {
super(name);
}
public int getType() {
return SettingType.SUMMARY;
}
#Override
public View getView(Context context) {
if(view == null) {
view = //Load the view here
}
return view;
}
}
I'm also saving my View reference for later, I'm not sure what I'm doing wrong here can anyone guide me towards how to debug this or fix this? Have spent over 10 hours still no success.
OK, try to not Caching the view, just recreate it on Setting.getView(Context context) and save the state of view and return it.
I Create 4 different items and work with me.
If you want to cache view i really recommended RecyclerView
I have very small question here, in my recycle view adapter class i'm using List<FeaturedTags> and its working fine.
Now we have newly introduced class called 'FeaturedLangTags, the only difference between FeaturedTags & FeaturedLangTagsis just an addition of Lang field. But we are not using this Lang field anyway to show on screen.
The output of the recycle view looks exactly similar to existing FeaturedTags adapter. Here i want to know how i can re-use the existing adapter class to display List<FeaturedLangTags> items?
One simple way is to duplicate the existing adapter and pass the FeaturedLangTagslist, but here so much of code is getting duplicated. I would like to know how i can tweek the existing class?
Create Adapter of generic List<T> which can be used in both condition.
public abstract class GenericAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private ArrayList<T> items;
public abstract RecyclerView.ViewHolder setViewHolder(ViewGroup parent);
public abstract void onBindData(RecyclerView.ViewHolder holder, T val);
public GenericAdapter(Context context, ArrayList<T> items){
this.context = context;
this.items = items;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder holder = setViewHolder(parent);
return holder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
onBindData(holder,items.get(position));
}
#Override
public int getItemCount() {
return items.size();
}
public void addItems( ArrayList<T> savedCardItemz){
items = savedCardItemz;
this.notifyDataSetChanged();
}
public T getItem(int position){
return items.get(position);
}
}
adapter = new GenericAdapter<DataModel>(context,modelList) {
#Override
public RecyclerView.ViewHolder setViewHolder(ViewGroup parent) {
final View view = LayoutInflater.from(context).inflate(R.layout.item_view_holder, parent, false);
ItemViewHolder viewHolder = new ItemViewHolder(context, view);
return viewHolder;
}
#Override
public void onBindData(RecyclerView.ViewHolder holder1, DataModel val) {
DataModel userModel = val;
ItemViewHolder holder = (ItemViewHolder)holder1;
holder.name.setText(userModel.getName());
holder.fatherName.setText(userModel.getFatherName());
}
};
mRecyclerView.setAdapter(adapter);
Create a common interface or superclass to FeaturedTags and FeaturedLangTags, then use the same adapter.
Or use a more generic approach to your adapter like AdapterDelegates.
You can create two different constructors for the adapter and achieve it.
You can have both the classes implement one common interface say CommonInterface
Make your list of type
YourList<CommonInterface>
In your adapter, override the
getItemViewType
Method. Here use the instance of operator to check what type of class it is and return a view type integer. Say for without lang, it will be an integer 0 and with it, 1
Now in your onCreateViewHolder, you get passed the viewType as the second parameter. Using that inflate your layout as your choice for the view.
In your onBindViewHolder, you can check
getItemViewType
On the holder passed in the parameter and cast your interface explicitly to the object you want. Then use it as usual.
Reference for this multiple view types is
Recycler view multiple view types
You can use ConcatAdapter of RecyclerView to concatenate your adapters and reuse them.
MyAdapter adapter1 = ...;
AnotherAdapter adapter2 = ...;
ConcatAdapter concatenated = new ConcatAdapter(adapter1, adapter2);
recyclerView.setAdapter(concatenated);
"Favor composition over inheritance"
I'm attempting to use multiple enums that I've set as subclasses to a master class as the data source for listviews on separate fragments.
Each fragment will have two listviews, but each listview will only need to use certain parts of the data. Each enum has two strings "name" and "abbr" and a double "value".
I would like to set both strings as the ListView titles, and use the value in a calculation.
Listview1 will hold titles, abbrs, and one EditText in the center row. Listview2 will hold titles, abbrs, and one more TextView that will update based on the EditText input and the value from the enum. I realize I will need two custom adapters for this, one for the heterogeneous Listview1, and one for Listview2.
I am a little lost on implementing, and have only attempted doing the custom adapter for Listview2.
I have tried looking at multiple SO questions, and listview tutorials that use database models and then have tried to use that but with my static enum lists, but am just a bit lost. Any help from a high level approach, specifics, or a nice tutorial would be much appreciated. I am probably not even close on the right path as I am new at Android and relatively new at OOP, thanks for bearing with the poor code!
What I have so far (I set up a test project, which is why I have this listview inflated in main as opposed to in a fragment - if there are any issues with this besides switching the context let me know):
Enum class holding all enums
public class Enums {
public enum Pressures{
ITEM1 ("name", "abbr", 1.0),
etc...;
private final String name;
private final String abbr;
private final double value;
Pressures(String name, String abbr, double value) {
this.name = name;
this.abbr = abbr;
this.intermediary = intermediary;
}
public String getNames() {
return name;
}
public String getAbbr() {
return abbr;
}
public double getIntermediary() {
return intermediary;
}
}
public enum Enum2 {
...
}
}
Custom Adapter for Listview2:
public class CustomAdapter extends BaseAdapter {
private Activity activity;
private LayoutInflater inflater;
private List<Enums.Pressures> pressureEnum;
public CustomAdapter(Activity activity, List<Enums.Pressures> units) {
this.activity = activity;
this.pressureEnum = units;
}
#Override
public int getCount() {
return pressureEnum.size();
}
#Override
public Object getItem(int location) {
return pressureEnum.get(location);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (inflater == null)
inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null)
convertView = inflater.inflate(R.layout.list_row, null);
TextView name = (TextView) convertView.findViewById(R.id.name);
TextView abbr = (TextView) convertView.findViewById(R.id.abbr);
Enums.Pressures p = pressureEnum.get(position);
name.setText(p.getNames());
abbr.setText(p.getAbbr());
return convertView;
}
}
Main:
public class Main extends Activity {
//This List was a poor attempt at setting the list from the enum
//I don't believe ArrayList is the proper choice as I have an enum object
//but I'm not quite sure what to use
private List<Enums.Pressures> pressureUnits = new ArrayList<Enums.Pressures>();
private ListView listView;
private CustomAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list);
adapter = new CustomAdapter(this, pressureUnits);
listView.setAdapter(adapter);
}
}
Your List "pressureUnits" is empty. You've specified that it is a list of Pressures but not added anything to it. Usually this is done with the "add" method, i.e. pressureUnits.add(...).
However what I think you want to use is Enums.Pressures.values(). This will return an array containing each of your enum elements. Then you will be able to create an adapter using that array. If you can't do it with the BaseAdapter you are using now, have a look at using the ArrayAdapter class rather than the BaseAdapter.