I got a problem where my codes do not show any error but the data won't come out in the RecyclerView. Every time I run it, it displays blank at the RecyclerView section. I have set my database rule to allow read, write: if true; but still didn't work.
Here's Java codes:
public class diary_user extends AppCompatActivity {
RecyclerView recyclerView;
Adapter adapter;
ImageView add;
DrawerLayout drawerLayout;
NavigationView navigationView;
Toolbar toolbar;
TextView date, note, time;
FirestoreRecyclerAdapter adapters;
FirebaseFirestore fStore;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_diary_user);
date = findViewById(R.id.date_text);
time = findViewById(R.id.time_text);
note = findViewById(R.id.note_textview);
recyclerView = findViewById(R.id.recycleView1);
fStore = FirebaseFirestore.getInstance();
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
final String current = user.getUid();
Query query = fStore.collection("Diary").whereEqualTo("UID",current);
FirestoreRecyclerOptions<ModelClass> options = new FirestoreRecyclerOptions.Builder<ModelClass>()
.setQuery(query, ModelClass.class)
.build();
adapters = new FirestoreRecyclerAdapter<ModelClass, ModelViewHolder>(options) {
#NonNull
#Override
public ModelViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_diary, parent,
false);
return new ModelViewHolder(view);
}
#Override
protected void onBindViewHolder(#NonNull ModelViewHolder holder, int position, #NonNull
ModelClass model) {
holder.date.setText(model.getDate());
holder.note.setText(model.getText());
holder.time.setText(model.getTime());
holder.divider.setText(model.getDivider());
}
};
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapters);
}
private class ModelViewHolder extends RecyclerView.ViewHolder {
TextView date, time, note, divider;
public ModelViewHolder(#NonNull View itemView) {
super(itemView);
date = itemView.findViewById(R.id.date_text);
time = itemView.findViewById(R.id.time_text);
note = itemView.findViewById(R.id.note_textview);
divider = itemView.findViewById(R.id.line_textview);
}
}
#Override
protected void onStop() {
super.onStop();
if(adapters != null) {
adapters.stopListening();
}
}
#Override
protected void onStart() {
super.onStart();
if(adapters != null) {
adapters.startListening();
}
}
}
ModelClass codes:
public class ModelClass {
String date,time,text,divider;
ModelClass(String date,String time,String text,String divider){
this.date = date;
this.time = time;
this.text = text;
this.divider = divider;
}
public String getDate() {
return date;
}
public String getTime() {
return time;
}
public String getText() {
return text;
}
public String getDivider() {
return divider;
}
}
XML codes:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#ffffff">
<TextView
android:id="#+id/date_text"
android:layout_width="120dp"
android:layout_height="30dp"
android:layout_marginLeft="30dp"
android:layout_marginTop="20dp"
android:text="16/3/2021"
android:textColor="#000000"
android:textSize="20sp" />
<TextView
android:id="#+id/time_text"
android:layout_width="80dp"
android:layout_height="30dp"
android:layout_marginLeft="290dp"
android:layout_marginTop="20dp"
android:text="1:30PM"
android:textSize="14sp" />
<TextView
android:id="#+id/note_textview"
android:layout_width="200dp"
android:layout_height="30dp"
android:layout_marginLeft="30dp"
android:layout_marginTop="55dp"
android:text="Feeling good today"
android:textSize="16sp" />
<TextView
android:id="#+id/line_textview"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_below="#+id/time_text"
android:layout_marginLeft="30dp"
android:layout_marginTop="25dp"
android:text="_____________________________________________"
android:textColor="#CDC7C7"
android:textSize="16sp" />
</RelativeLayout>
Here's database structure:
add adapters.startListening(); this line mentioned below
adapters.startListening();
recyclerView.setHasFixedSize(true);
also check firestore rules
allow read, write : if true;
also check data collection field name and your model class variable name or you can your anotaion
#PropertyName("Date")
I have set my database rule to allow read, write: if true; but still didn't work.
If you set:
allow read, write: if true;
In your Security Rules, it means that you allow both, read and write operations, to be performed in your Cloud Firestore database. But this is not the reason why your RecylerView is empty. When you try to map a document from Firestore into an object of your "ModelClass", the name of the fields that exist in your class must match the name of your properties that exist in your database. Unfortunately, in your case, the fields don't match. See, the fields in your class start with lowercase, while in the database start with a capital letter, which is not correct.
To solve this, you have two options, you either change the name of your properties in the database to match the one in the class, or you can use an annotation in front of the getters. For example, if you have a field called "date" and the property in the database is called "Date" (upper-case D), your getter should look like this:
#PropertyName("Date")
public String getDate() {
return date;
}
In this way, you tell the compiler to look for a property called "Date" and not "date".
Because you are using public getters, don't also forget to set the fields in your class as private. If you want to keep them, for example, public, the getters are not needed. You can set the values directly on the public fields. So a minimum class declaration might look like this:
public class ModelClass {
public String date, time, text, divider;
}
Please also note, that the public no-argument constructor is also not needed, as it is provided by the compiler.
Related
I have a custom ArrayAdapter of a base class. I also have a subclass and I want the ArrayAdapter to be able to receive two different types of data i.e. (arrayList and arraylist)and display them consecutively in one seamless listview.
I have never done this before so general comments and guidelines are much appreciated. I tried to make the ArrayAdapter generic:
public class MyAdapter<T extends BaseClass> extends ArrayAdapter<T>
with this constructor:
public MyAdapter(Context context, ArrayList<T> items) {
super(context, 0, items);
fullList = items;
}
But I do not know how to then pass a generic ArrayList that can either have type subclass or base class. (and this approach will later involve code that uses instanceOf which is not the best way to do things ... as I have learned from SO)
Instead of generics, I was thinking of extending this custom arrayadapter itself. But I am not sure how this would work.
What is the best way to approach this? Is there maybe a better adapter suited for this case other than arrayadapter. Or could it be that recyclerview has a solution to this problem?
I ultimately want to have two similar but different types of datasets (one subclassed another), outputted as one seamless listview. I was not able to find the general approach to doing this.
What datatypes are you using in your ArrayLists?
If, for instance, Strings and Integers, could you not convert the values and merge into a single ArrayList?
for (int i=0; i<arraylist1.size(); i++) { //arraylist1 containing integers
arraylist2.add(Integer.toString(arraylist1.get(i));
}
then you can just apply the one ArrayList to a standard ArrayAdapter.
You can solve this problem using view type.
example:
row_type_one.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/txtTypeOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
row_type_two.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="#+id/logo"
android:layout_width="100dp"
android:layout_height="match_parent" />
<TextView
android:id="#+id/txtTypeOne"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
YourTypeOne.java
public class YourTypeOne{
public String title;
}
YourTypeTow.java
public class YourTypeTow {
public String title;
public String imgUrl;
}
YourAdapter.java
public class YourAdapter extends RecyclerView.Adapter<YourAdapter.YourViewHolder> {
ArrayList<Object> objects = new ArrayList<>();
public YourAdapter(ArrayList<YourTypeOne> typeOneArray, ArrayList<YourTypeTow> typeTowArray) {
objects.addAll(typeOneArray);
objects.addAll(typeTowArray);
}
#Override
public int getItemViewType(int position) {
return objects.get(position) instanceof YourTypeOne ? 1 : 2;
}
#NonNull
#Override
public YourViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v;
if (viewType == 1) {
v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_type_one, parent, false);
} else {
v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_type_two, parent, false);
}
return new YourViewHolder(v,viewType);
}
#Override
public void onBindViewHolder(#NonNull YourViewHolder holder, int position) {
}
#Override
public int getItemCount() {
return objects.size();
}
class YourViewHolder extends RecyclerView.ViewHolder {
private TextView txtTypeOne;
private TextView txtDescription;
private ImageView logo;
public YourViewHolder(#NonNull View itemView, int vewType) {
super(itemView);
if (vewType==1){
//find views by ides in .xml
txtTypeOne = itemView.findViewById(R.id.txtTypeOne);
}else {
//find views by ides in row_type_two.xml
txtDescription = itemView.findViewById(R.id.txtDescription);
logo = itemView.findViewById(R.id.logo);
}
}
public void bindeData(Object object){
if (object instanceof YourTypeOne){
//set data to layout row_type_one
}else {
//set data to layout row_type_two
}
}
}
}
good luck. :)
I'm trying to find a solution to pass data from my recyclerview adapter to new activity. I researched a lot but I didn't get a solution.
I know that I need to use putExtra() but I don't know how.
The data is taken from API, I have displayed in the CompaniesListActivity but in this activity I want to show only name of the company and in the Company description I want to show the others.
Like this:
Companies:
Company1
Company2
Company3
Company4
Company5
and when I click to one of them I will get
description
nipt
clubs
activity_company_description.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CompanyDescription">
<android.support.v7.widget.RecyclerView
android:id="#+id/company_description"
android:layout_width="395dp"
android:layout_height="715dp"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="8dp" />
</android.support.constraint.ConstraintLayout>
row_company_description.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="160dp"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/row_padding_vertical"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingBottom="#dimen/row_padding_vertical">
<TextView
android:id="#+id/tvCompanyDescription"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginStart="10dp"
android:layout_marginTop="3dp"
android:layout_marginEnd="8dp"
android:text="TextView"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.023"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvCompanyName" />
<TextView
android:id="#+id/tvCompanyNipt"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_marginStart="10dp"
android:layout_marginTop="3dp"
android:layout_marginEnd="8dp"
android:text="TextView"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvCompanyDescription" />
<TextView
android:id="#+id/tvClubs"
android:layout_width="wrap_content"
android:layout_height="15dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvCompanyNipt"
app:layout_constraintVertical_bias="0.2"
tools:layout_editor_absoluteX="5dp" />
</android.support.constraint.ConstraintLayout>
my recyclerview adapter
private Context context;
public CompanyAdapter(Context context, Companies companies) {
this.companies = companies;
this.context = context;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_companies, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder viewHolder, final int position) {
final Company comp = companies.getCompanies().get(position);
viewHolder.compName.setText(comp.getCompany().getName());
viewHolder.compDesc.setText(comp.getCompany().getDescription());
viewHolder.compNipt.setText(comp.getCompany().getNipt());
viewHolder.compClubs.setText(comp.getClubs().toString());
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Company selectedCom = companies.getCompanies().get(position);
Intent intent = new Intent(context, CompanyDescription.class);
intent.putExtra("company", selectedCom);
intent.putExtra("description", comp);
intent.putExtra("nipt", comp);
intent.putExtra("clubs", comp);
context.startActivity(intent);
}
});
}
#Override
public int getItemCount() {
Integer rows = companies == null ? 0 : companies.getCompanies().size();
return rows;
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView compName, compDesc, compNipt, compClubs;
public ViewHolder(#NonNull View itemView) {
super(itemView);
compName = (TextView) itemView.findViewById(R.id.tvCompanyName);
compDesc = (TextView) itemView.findViewById(R.id.tvCompanyDescription);
compNipt = (TextView) itemView.findViewById(R.id.tvCompanyNipt);
compClubs = (TextView) itemView.findViewById(R.id.tvClubs);
}
}
my CompaniesListActivity->
private RecyclerView recyclerView;
private RecyclerView.Adapter adapter;
private UserService service;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_companies_list);
CompaniesRetriver companiesRetriver = new CompaniesRetriver();
this.recyclerView = (RecyclerView) findViewById(R.id.companies_list);
SharedPreferences editor = getSharedPreferences(MY_PREFERENCE, MODE_PRIVATE);
String token = editor.getString("token", "");
final Context sfdsf = this;
Callback<Companies> callback = new Callback<Companies>() {
#Override
public void onResponse(Call<Companies> call, Response<Companies> response) {
if(response.isSuccessful()) {
Companies companies = response.body();
CompanyAdapter adapter = new CompanyAdapter(CompaniesListActivity.this, companies);
recyclerView.setLayoutManager(new LinearLayoutManager(CompaniesListActivity.this));
recyclerView.setAdapter(adapter);
System.out.println(companies);
} else {
System.out.println(response.body());
}
}
#Override
public void onFailure(Call<Companies> call, Throwable t) {
System.out.println(t.getLocalizedMessage());
}
};
companiesRetriver.getCompanies(callback, token);
recyclerView.addItemDecoration(
new DividerItemDecoration(ContextCompat.getDrawable(getApplicationContext(),
R.drawable.item_separator)));
}
and CompanyDescriptionActivity
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_company_description);
Company ind = (Company) getIntent().getSerializableExtra("company");
System.out.println(ind);
CompaniesRetriver companiesRetriver = new CompaniesRetriver();
this.recyclerView = (RecyclerView) findViewById(R.id.company_description);
try this one hope its helpfull to you.
Intent intent = new Intent(context, CompanyDescription.class);
intent.putExtra("key", value);
context.startActivity(intent);
and in Another activity you can get data by this
String value=getIntent().getStringExtra("key");
What you're describing is a problem with your Separation of Concerns here.
Let's analyze what you have, shall we?
Your Code
You have 2 activities. One fetches a list of data from an API, and displays it in a list (which involves creating the adapter and satisfying all the requirements for the list to display). This activity also serves as your data repository to the rest of the activities, since you store this list here, in the list activity.
The other activity is intended to display the Details (or in your case "Description") of an Item (or in your example a Company).
Problem
You have no way for the ListActivity to directly pass a Company to CompanyDescription
What you want to do
Separate things into smaller things, so you can tell a thing only does "this thing and nothing more". A lot of things huh?
Use the framework tools at your disposal.
What I would do
CompanyListActivity should only be in charge of the list; to display it and construct it, that's all it needs to know about it.
CompanyListActivity will inflate the XML (for the RecyclerView), will create an adapter and will request the data to a repository (you have it called CompaniesRetriever) So Companies Retriever will let the activity know when there's data (you already did this, since you pass a callback). There is an issue with your implementation, if the user loads this list and then immediately hits back, I don't see any code in the CompanyListActivity to inform the retriever that the data is no longer needed (since the user left the activity). Keep an eye for that, because if you're passing any context to said "retriever", you may have a memory leak waiting to happen.
So far so good. Now you have your list. And here is where we both do things differently:
In my version of this app, CompaniesRetriever is a singleton class (there can be only one, like Highlander), and so instead of doing new CompaniesRetriever() in the Description activity, you simply use the same instance that the CompaniesList activity used.
This opens a new possibility, when the Retriever (which we cannot see because you didn't share the code) receives the API callback with the data and it's ready to pass it back to the CompaniesList (in the callback you have), it instead keeps a local copy of this data and then passes it along.
Now when the user TAPS an item on the list, instead of passing the whole thing, you simply pass the "id" of the Company (or something unique to identify it) to the next activity via the intent.putString("Id", selectedCompany.getId() (the id can be anything you want as long as it allows the next activity to uniquely identify it).
In the NEXT activity, you obtain the "selected company id" and you call your Retriever (which has a list of all the companies in memory), with a new method whose signature looks like: public Company getCompanyById(String id) (pseudo code obviosuly)
The all the retriever does is:
public Company getCompanyById(String id) {
listofCompanies.get(id) // I would use a hashMap for faster lookup
}
(if you use a list instead you will need to iterate until you find what you're looking for, if you have 1000 companies, that's fine, if you expect a HUGE number, consider a hashmap or a faster data structure)
In any case, the end result is that your new "Description" activity doesn't need to worry too much about anything, it receives an ID and it asks for the data.
This keeps both activities oblivious to the origin of the data, they are simply given the data they need (the flow is mostly in one direction, which is good). Activities ASK for data, don't supply it.
I hope this helps you re-think your code, you wouldn't need to make a lot of changes, for you already have most of the things in place.
How about the XML?!
I'm not 100% sure what you mean by accessing the XML from "there" (your words), but I think you don't need it, because what you were thinking was to read the XML from the company you are displaying in the list, and that is not possible (literally) and unneeded with the architecture I'm proposing (which, for the record, I didn't invent) :D
Hope this guides you in a better direction.
Good luck!
I would try to do it like this:
private Context context;
private onItemClickListener;
public CompanyAdapter(OnItemClickListener listener, Companies companies) {
this.companies = companies;
this.onItemClickListener = listener;
}
public interface OnItemClickListener {
void OnItemClick(Companies companies)
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_companies, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder viewHolder, final int position) {
final Company comp = companies.getCompanies().get(position);
viewHolder.bind(comp, onItemClickListener);
}
#Override
public int getItemCount() {
Integer rows = companies == null ? 0 : companies.getCompanies().size();
return rows;
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView compName, compDesc, compNipt, compClubs;
public ViewHolder(#NonNull View itemView) {
super(itemView);
compName = (TextView) itemView.findViewById(R.id.tvCompanyName);
compDesc = (TextView) itemView.findViewById(R.id.tvCompanyDescription);
compNipt = (TextView) itemView.findViewById(R.id.tvCompanyNipt);
compClubs = (TextView) itemView.findViewById(R.id.tvClubs);
}
public void bind (Companies comp, OnItemClickListener listener) {
compName.setText(comp.getCompany().getName());
compDesc.setText(comp.getCompany().getDescription());
compNipt.setText(comp.getCompany().getNipt());
compClubs.setText(comp.getClubs().toString());
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onItemClickListener.OnItemClick(comp);
Context context = v.getContext;
Intent intent = new Intent(context, CompanyDescription.class);
intent.putExtra("company", selectedCom);
intent.putExtra("description", comp);
intent.putExtra("nipt", comp);
intent.putExtra("clubs", comp);
context.startActivity(intent);
}
});
}
}
With so much confusion I found a simple way how to display the data, I decided not use recyclerview but something simpler. All is working fine now.
Intent intent = getIntent();
company = (Company) intent.getSerializableExtra("company");
tvCompanyName = (TextView) findViewById(R.id.tvCompanyName);
tvCompanyDescription = (TextView) findViewById(R.id.tvCompanyDescription);
tvCompanyNipt = (TextView) findViewById(R.id.tvCompanyNipt);
tvCompanyName.setText(company.getCompany().getName());
tvCompanyDescription.setText(company.getCompany().getDescription());
tvCompanyNipt.setText(company.getCompany().getNipt());
I am trying to work with a custom listview in a fragment, and I tried a lot of things, I watched videos and I looked for solutions in this site, but I get confused because it doesn't work.
My problem is that in the code, I can't make work the custom adapter, it crash when the fragment inflates, and with the "(AppCompatActivity)" added, it doesn't crash but the listview doesn't show anything.
Is there something I could do with this?
Should I try another thing?
This is the Fragment I want to use the ListView
public class RecordFragment extends Fragment {
private ArrayList<Record> scoreList;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_record, container, false);
scoreList= new ArrayList<>();
scoreList.add(new Record("Math", 10, "9/1/2017 13:45"));
scoreList.add(new Record("Math", 8, "7/5/2017 10:50"));
scoreList.add(new Record("Marh", 4, "7/7/2017 16:30"));
CustomAdapter adapter = new CustomAdapter(RecordFragment.this.getActivity(), android.R.layout.simple_list_item_1, scoreList);
ListView list1 = (ListView)view.findViewById(R.id.list1);
list1.setAdapter(adapter);
return view;
}
private class CustomAdapter extends ArrayAdapter<Record>{
AppCompatActivity appCompatActivity;
CustomAdapter(AppCompatActivity context){
super(context, R.layout.record_fragment, scoreList);
appCompatActivity = context;
}
public View getView(int position, View convertView, ViewGroup parent){
LayoutInflater inflater = appCompatActivity.getLayoutInflater();
View item = inflater.inflate(R.layout.record_fragment, null);
TextView tv1, tv2, tv3;
tv1 = (TextView)item.findViewById(R.id.tv1);
tv2 = (TextView)item.findViewById(R.id.tv2);
tv3 = (TextView)item.findViewById(R.id.tv3);
tv1.setText(scoreList.get(position).getTest());
tv2.setText(scoreList.get(position).getScore());
tv3.setText(scoreList.get(position).getDate());
return item;
}
}
public interface OnFragmentInteractionListener {
void onFragmentInteraction(Uri uri);
}
}
This is the XML with the visual interface of each ListView item
<TextView
android:id="#+id/tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="" />
<TextView
android:id="#+id/tv2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="" />
<TextView
android:id="#+id/tv3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="" />
And this is the class I created to represent each ListView item
class Record{
private String test, date;
private int score;
public Record(String test, int score, String date)
{
this.test= test;
this.score= score;
this.date = date;
}
public String getTest() {
return test;
}
public int getScore(){
return score;
}
public String getDate(){
return date;
}
}
If there is something else I have to show tell me, please.
Beforehand, thank you.
EDIT: I fixed some spelling mistakes. And this line in the Fragment doesn't compile:
CustomAdapter adapter = new CustomAdapter(RecordFragment.this.getActivity(), android.R.layout.simple_list_item_1, scoreList);
This message is displayed when I try to run it (I guest is the error log):
Error:(39, 40) error: constructor CustomAdapter in class RecordFragment.CustomAdapter cannot be applied to given types;
required: AppCompatActivity
found: FragmentActivity,int,ArrayList
reason: actual and formal argument lists differ in length
According to your question you are facing this problem
after adding AppcompatActivity your app is not crashing but list is not showing anything
Your Adapter is asking for Context from your Activity which is AppCompat Type . So if when your host activity is not extending AppcompatActivity it will crash
So when you change it to AppcompatActivity it won't crash
Now lets solve the problem of showing a blank listView
you didn't override the parent method getCount() . In the getCount() method return size items in your List.
Another thing CustomAdapter adapter = new CustomAdapter(RecordFragment.this.getActivity(), android.R.layout.simple_list_item_1, scoreList); I don't think your code compiles with this line as your AdaptadorHistorial(AppCompatActivity context){
super(context, R.layout.record_fragment, scoreList);
appCompatActivity = context;
} custom adapter is taking one parameter but you are passing two
your constructor should be like this
AdaptadorHistorial(Context context , List<Record> scoreList){
super(context, R.layout.record_fragment, scoreList);
this.context = context;
this.items = scoreList;
}
#override
public int getCount(){
return items.size();
}
I am trying to create a simple settings page for an app.
I want to create a list of objects, whose layout I define in an XML file.
Within the layout of these objects is a title TextView, a help TextView, and a Spinner.
As I'm adding these objects dynamically in code, how do I then reference these individual Spinner views so that I can get the selected value?
I currently have a Setting object (what the list will be populated with):
public class Setting {
private String name;
private String description;
private String[] values;
public Setting(String name, String description, String[] values) {
this.name = name;
this.description = description;
this.values = values;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public String[] getValues() {
return values;
}
}
A custom ArrayAdapter to translate Setting objects to Views:
(R.layout.spinner_row_text_layout.xml is just a text view to define the layout of the spinner's items)
public class SettingsAdapter extends ArrayAdapter<Setting> {
public SettingsAdapter(Context context, ArrayList<Setting> settings) {
super(context, 0, settings);
}
#Override
// Takes the position in the array, the view to convert, and the parent of the view as parameters
public View getView(int position, View convertView, ViewGroup parent) {
// Get the item in this position
Setting setting = getItem(position);
// Check if a view is being reused, otherwise inflate the view (look at view recycling in lists)
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.settings_row_view, parent, false);
}
// findViewById in the given view to convert
TextView title = (TextView) convertView.findViewById(R.id.titleText);
TextView help = (TextView) convertView.findViewById(R.id.helpText);
Spinner spinner = (Spinner) convertView.findViewById(R.id.spinner);
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>(getContext(), R.layout.spinner_row_text_layout, setting.getValues());
spinner.setAdapter(spinnerAdapter);
title.setText(setting.getName());
help.setText(setting.getDescription());
return convertView;
}
}
And the following layout files...
Settings page layout:
<RelativeLayout
android:id="#+id/relativeLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.cgillions.counter.SettingsActivity">
<ListView
android:id="#+id/settingsList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:choiceMode="singleChoice"
android:divider="#color/black"
android:dividerHeight="5dp"/>
</RelativeLayout>
Layout for each ListView item:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<TextView
android:id="#+id/titleText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello"
android:textSize="20sp"
android:textColor="#color/black"
android:gravity="center_vertical"/>
<TextView
android:id="#+id/helpText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/animation_settings_hint"
android:textSize="12sp"
/>
</LinearLayout>
<Spinner
android:id="#+id/spinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.5"/>
</LinearLayout>
When I start the Activity that corresponds to the Settings page layout, I get the following error:
at junit.framework.Assert.assertNotNull(Assert.java:211)
at com.example.cgillions.counter.SettingsActivity.onCreate(SettingsActivity.java:46)
at android.app.Activity.performCreate(Activity.java:5990)
This is my onCreate() method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
setTitle("Settings");
// Create a new Setting object in order to populate list
animSetting = new Setting("Animation", getResources().getString(R.string.animation_settings_hint), getResources().getStringArray(R.array.anim_values));
settings = new ArrayList<>();
settings.add(animSetting);
ListView settingsList = (ListView) findViewById(R.id.settingsList);
assertNotNull(settingsList);
settingsList.setAdapter(new SettingsAdapter(this, settings));
animSpinner = (Spinner) settingsList.findViewById(R.id.spinner);
assertNotNull(animSpinner);
}
The error occurs on the last line - e.g. the View with ID 'spinner' cannot be found (and therefore is null). I'm accessing the Spinner incorrectly - can someone advise as to how I should do this?
I want to get the Spinner object in order to get the selected item.
Something you might try. Add an Spinner instance to your Setting class:
public class Setting {
private String name;
private String description;
private String[] values;
private Spinner spinner;
public Setting(String name, String description, String[] values) {
this.name = name;
this.description = description;
this.values = values;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public String[] getValues() {
return values;
}
public void setSpinner(Spinner s) {
spinner = s;
}
public Spinner getSpinner() {
return spinner;
}
}
Then, in your getView() method, provide the Spinner to your setting instance:
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>(getContext(), R.layout.spinner_row_text_layout, setting.getValues());
spinner.setAdapter(spinnerAdapter);
if (setting.getSpinner() == null) {
setting.setSpinner(spinner);
}
Then, assuming you can access your list of Settings, you can access the spinner associated with each one. If you find a Setting with a null spinner, then it never came into view in the Activity, and that would mean the user never changed it. But I don't know if that's really what you want.
This will allow you to access the spinner for each Setting, but you have no logic for storing the user's selection, or for setting the selected item in the spinner. I.e., if the spinner has options A, B and C, and the user has selected B, you can now read the B but you don't have a place to store it. The Setting class should have a field for holding the chosen value. And your getView() method should call spinner.setSelection() to tell it to show B as the current value. Otherwise, it will always show A regardless of what the user has previously chosen.
I want my ListView to contain buttons, but setting the button's xml property, onClick="myFunction" and then placing a public void myFunction(android.view.View view) method in the activity causes an NoSuchMethodException (the stack trace is null) to be thrown, as although the onclick listener is there, it doesn't fire myFunction(...) and cause the activity to close.
How do I create a custom Adapter that connects a View.OnClickListener to a button on each row of a ListView?
My ListView is created as follows...
[activity.java content..]
public void myFunction(android.view.View view)
{
//Do stuff
}
[activity.xml content..]
<LinearLayout xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".FrmCustomerDetails" >
<ListView android:id="#+id/LstCustomerDetailsList" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:clickable="true" android:clipChildren="true" android:divider="#null" android:dividerHeight="0dp" android:fastScrollEnabled="true" android:footerDividersEnabled="false" android:headerDividersEnabled="false" android:requiresFadingEdge="vertical" android:smoothScrollbar="true" />
</LinearLayout>
[activity_row_item.xml content..]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="#+id/Llt" android:layout_width="match_parent" android:layout_height="match_parent" >
<Button android:id="#+id/Btn" android:text="Click me" android:onClick="myFunction" />
</LinearLayout>
Here is how to create the custom Adapter, connecting View.OnClickListener to a ListView with a button per row...
1. Create a layout for a typical row
In this case, the row is composed of three view components:
name (EditText)
value (EditText:inputType="numberDecimal")
delete (Button)
Xml
pay_list_item.xml layout is as follows:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<EditText
android:id="#+id/pay_name"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="2"
android:hint="Name" />
<EditText
android:id="#+id/pay_value"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:inputType="numberDecimal"
android:text="0.0" />
<Button
android:id="#+id/pay_removePay"
android:layout_width="100dp"
android:layout_height="fill_parent"
android:text="Remove Pay"
android:onClick="removePayOnClickHandler" />
</LinearLayout>
Note: the button has onClick handler defined in xml layout file, because we want to refer its action to a specific list item.
Doing this means that the handler will be implemented in Activity file and each button will know which list item it belongs to.
2. Create list item adapter
This is the java class that is the controller for pay_list_item.xml.
It keeps references for all of its views, and it also puts these references in tags, extending the ArrayAdapter interface.
The Adapter:
public class PayListAdapter extends ArrayAdapter<Payment> {
private List<Payment> items;
private int layoutResourceId;
private Context context;
public PayListAdapter(Context context, int layoutResourceId, List<Payment> items) {
super(context, layoutResourceId, items);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.items = items;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
PaymentHolder holder = null;
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new PaymentHolder();
holder.Payment = items.get(position);
holder.removePaymentButton = (ImageButton)row.findViewById(R.id.pay_removePay);
holder.removePaymentButton.setTag(holder.Payment);
holder.name = (TextView)row.findViewById(R.id.pay_name);
holder.value = (TextView)row.findViewById(R.id.pay_value);
row.setTag(holder);
setupItem(holder);
return row;
}
private void setupItem(PaymentHolder holder) {
holder.name.setText(holder.Payment.getName());
holder.value.setText(String.valueOf(holder.Payment.getValue()));
}
public static class PaymentHolder {
Payment Payment;
TextView name;
TextView value;
ImageButton removePaymentButton;
}
}
Here we list the Payment class items.
There are three most important elements here:
PayListAdapter constructor: sets some private fields and calls superclass constructor. It also gets the List of Payment objects. Its implementation is obligatory.
PaymentHolder: static class that holds references to all views that I have to set in this list item. I also keep the Payment object that references to this particular item in list. I set it as tag for ImageButton, that will help me to find the Payment item on list, that user wanted to remove
Overriden getView method: called by superclass. Its goal is to return the single List row. We create its fields and setup their values and store them in static holder. Holder then is put in row’s tag element. Note that there is a performance issue, as the row is being recreated each time it is displayed. I used to add some flag in holder like isCreated, and set it to true after row was already created. then you can add if statement and read tag’s holder instead of creating it from scratch.
Payment.java is quite simple as for now and it looks a bit like BasicNameValuePair:
public class Payment implements Serializable {
private String name = "";
private double value = 0;
public Payment(String name, double value) {
this.setName(name);
this.setValue(value);
}
...
}
There are additional gets and sets for each private field not shown.
3. Add ListView to the activity layout xml file
In its simpliest form, it will be enough to add this view to activity layout:
<ListView
android:id="#+id/EnterPays_PaysList"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</ListView>
4. Set up adapter to this list view in Activity Java code
In order to display items in ListView you need to set up its adapter and map it to some other ArrayList of Payment objects (as I am extending an Array adapter here). Here is code that is responsible for binding adapter to editPersonData.getPayments() ArrayList:
PayListAdapter adapter = new PayListAdapter(AddNewPerson.this, R.layout.pay_list_item, editPersonData.getPayments());
ListView PaysListView = (ListView)findViewById(R.id.EnterPays_PaysList);
PaysListView.setAdapter(adapter);
5. Adding / removing items to ListView (and its adapter)
Adapter is handled just like any other ArrayList, so adding new element to it is as simple as:
Payment testPayment = new Payment("Test", 13);
adapter.add(testPayment);
adapter.remove(testPayment);
6. Handle Remove Payment button click event
In an activity’s code, where ListView is displayed, add public method that will handle remove button click action. The method name has to be exactly the same as it was in pay_list_item.xml:
android:onClick="removePayOnClickHandler"
The method body is as follows:
public void removePayOnClickHandler(View v) {
Payment itemToRemove = (Payment)v.getTag();
adapter.remove(itemToRemove);
}
The Payment object was stored in ImageButton’s Tag element. Now it is enough to read it from Tag, and remove this item from the adapter.
7. Incorporate remove confirmation dialog window
Probably you need also make sure that user intentionally pressed the remove button by asking him additional question in confirmation dialog.
Dialogue
a) Create dialog’s id constant
This is simply dialog’s ID. it should be unique among any other dialog window that is handled by current activity. I set it like that:
protected static final int DIALOG_REMOVE_CALC = 1;
protected static final int DIALOG_REMOVE_PERSON = 2;
b) Build dialog
I use this method to build dialog window:
private Dialog createDialogRemoveConfirm(final int dialogRemove) {
return new AlertDialog.Builder(getApplicationContext())
.setIcon(R.drawable.trashbin_icon)
.setTitle(R.string.calculation_dialog_remove_text)
.setPositiveButton(R.string.calculation_dialog_button_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
handleRemoveConfirm(dialogRemove);
}
})
.setNegativeButton(R.string.calculation_dialog_button_cancel, null)
.create();
}
AlertDialog builder pattern is utilized here. I do not handle NegativeButton click action – by default the dialog is just being hidden. If dialog’s confirm button is clicked, my handleRemoveConfirm callback is called and action is performed based on dialog’s ID:
protected void handleRemoveConfirm(int dialogType) {
if(dialogType == DIALOG_REMOVE_PERSON){
calc.removePerson();
}else if(dialogType == DIALOG_REMOVE_CALC){
removeCalc();
}
}
c) Show Dialog
I show dialog after my remove button click. The showDialog(int) is Android’s Activity’s method:
OnClickListener removeCalcButtonClickListener = new OnClickListener() {
public void onClick(View v) {
showDialog(DIALOG_REMOVE_CALC);
}
};
the showDialog(int) method calls onCreateDialog (also defined in Activity’s class). Override it and tell your app what to do if the showDialog was requested:
#Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_REMOVE_CALC:
return createDialogRemoveConfirm(DIALOG_REMOVE_CALC);
case DIALOG_REMOVE_PERSON:
return createDialogRemoveConfirm(DIALOG_REMOVE_PERSON);
}
}
Take a look at this blog post I wrote on exactly this matter:
Create custom ArrayAdapter
There are comments that explain every action I make in the adapter.
Here is the explanation in short:
So lets for example take a row where you want to place a CheckBox, ImageView
and a TextView while all of them are clickable. Meaning that you can click the
row it self for going to another Actvity for more details on the row, check its
CheckBox or press the ImageView to perform another operation.
So what you should do is:
1. First create an XML layout file for your ListView row:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<CheckBox
android:id="#+id/cbCheckListItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp" />
<TextView
android:id="#+id/tvItemTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="item string" />
<ImageView
android:id="#+id/iStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:contentDescription="#drawable/ic_launcher"
android:src="#drawable/ic_launcher" />
</LinearLayout>
2. Second in your java code define a ViewHolder, a ViewHolder
is designed to hold the row views and that way operating more quickly:
static class ViewHolder
{
TextView title;
CheckBox checked;
ImageView changeRowStatus;
}
3. Now we have to define CustomArrayAdapter, using the array adapter
we can define precisely what is the desired output for each row based on the content of this
row or it’s position. We can do so by overriding the getView method:
private class CustomArrayAdapter extends ArrayAdapter<RowData>
{
private ArrayList<RowData> list;
//this custom adapter receives an ArrayList of RowData objects.
//RowData is my class that represents the data for a single row and could be anything.
public CustomArrayAdapter(Context context, int textViewResourceId, ArrayList<RowData> rowDataList)
{
//populate the local list with data.
super(context, textViewResourceId, rowDataList);
this.list = new ArrayList<RowData>();
this.list.addAll(rowDataList);
}
public View getView(final int position, View convertView, ViewGroup parent)
{
//creating the ViewHolder we defined earlier.
ViewHolder holder = new ViewHolder();)
//creating LayoutInflator for inflating the row layout.
LayoutInflater inflator = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//inflating the row layout we defined earlier.
convertView = inflator.inflate(R.layout.row_item_layout, null);
//setting the views into the ViewHolder.
holder.title = (TextView) convertView.findViewById(R.id.tvItemTitle);
holder.changeRowStatus = (ImageView) convertView.findViewById(R.id.iStatus);
holder.changeRowStatus.setTag(position);
//define an onClickListener for the ImageView.
holder.changeRowStatus.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
Toast.makeText(activity, "Image from row " + position + " was pressed", Toast.LENGTH_LONG).show();
}
});
holder.checked = (CheckBox) convertView.findViewById(R.id.cbCheckListItem);
holder.checked.setTag(position);
//define an onClickListener for the CheckBox.
holder.checked.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
//assign check-box state to the corresponding object in list.
CheckBox checkbox = (CheckBox) v;
rowDataList.get(position).setChecked(checkbox.isChecked());
Toast.makeText(activity, "CheckBox from row " + position + " was checked", Toast.LENGTH_LONG).show();
}
});
//setting data into the the ViewHolder.
holder.title.setText(RowData.getName());
holder.checked.setChecked(RowData.isChecked());
//return the row view.
return convertView;
}
}
4. Now you need to set this adapter, as the adapter of your ListView.
this ListView can be created in java or using an XML file, in this case I’m using a list that was
defined in the XML file using the “list” id:
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
ListView list = (ListView)findViewById(R.id.list);
CustomArrayAdapter dataAdapter = new CustomArrayAdapter(this, R.id.tvItemTitle, rowDataList);
list.setAdapter(dataAdapter);
}
5. Finally if we want to be able to press the row it self and not only a certain view in it
we should assign an onItemClickListener to the ListView:
list.setOnItemClickListener(new OnItemClickListener()
{
public void onItemClick(AdapterView<?> parent, View view,int position, long id)
{
Toast.makeText(activity, "row " + position + " was pressed", Toast.LENGTH_LONG).show();
}
});
First, the way of adding listeners in xml using onClick="function" is deprecated. You need a ViewHolder class to link the button in the xml to your java code. Then you can implement onClickListener for that.
Inside your getView() implementation of CustomAdapter, you can try like below.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.xxxxx, null);
holder = new ViewHolder();
holder.invite = (Button) convertView.findViewById(R.id.button);
} else {
holder = (ViewHolder) convertView.getTag();
}
final int pos = position;
holder.button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
handleClick(pos);
}
});
}
class ViewHolder {
Button button;
}