Background
Hi, I am new to Android and trying to get familiar with ListView. So I decide to write a simple program for user to enter quotes and display them in order. I implement a StringAdapter and call the notifyDataSetChanged every time when the user confirms.
Question
The weird thing is that the ListView would sometimes update the oldest quotes and sometimes the newer one. and I don't know the problem.
Please ignore the view data button. In this state, I have entered four quotes:
Quotes: hi - Signature:William Shakespeare
Quotes: hello - Signature:William Shakespeare
Quotes: Virtue is bold and goodness never fearful. - Signature:William Shakespeare
Quotes: Love all, trust a few, do wrong to none. - Signature:William Shakespeare
(in reverse order, meaning in time sequence, it goes 4,3,2,1)
Code
main activity
public class storage extends AppCompatActivity {
// the adapter
private StringAdapter sa;
// the edit text view
public EditText etString,etSignature;
// the list view
public ListView lv;
// the array list to capture the quotes and signature
private ArrayList<String[]> dataString = new ArrayList<String[]>();
// add the string and notify
public void addString(String[] s){
this.dataString.add(0,s);
((BaseAdapter)this.lv.getAdapter()).notifyDataSetChanged();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_storage);
// Link the view elements
this.etString = (EditText) findViewById(R.id.etInput);
this.etSignature = (EditText) findViewById(R.id.etSignature);
this.lv = (ListView) findViewById(R.id.stringList);
Button btn_confirm = (Button) findViewById(R.id.btnConfirm),
btn_viewData = (Button) findViewById(R.id.btnViewData);
// load the adapter
this.sa = new StringAdapter(this,this.dataString);
lv.setAdapter(sa);
btn_confirm.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
storage s = (storage) v.getContext();
// get the String
String sString = s.etString.getText().toString(),
sSignature = s.etSignature.getText().toString();
System.out.println("Quotes: " + sString + "\nSignature:" + sSignature);
// verify it is not empty
if (!sString.isEmpty()&&!sSignature.isEmpty()) {
// add new string and notify
s.addString(new String[]{s.etString.getText().toString(),
s.etSignature.getText().toString()});
((StringAdapter) s.lv.getAdapter()).print_stringData();
// prompt the result
Toast.makeText(s.getBaseContext(),
"Enter Quotes from"+etSignature.getText().toString(),Toast.LENGTH_SHORT).show();
} else {
// prompt the result
Toast.makeText(s.getBaseContext(),
"Please Enter Quotes and Signatures!",Toast.LENGTH_SHORT).show();
}
}
});
}
}
StringAdapter
public class StringAdapter extends BaseAdapter {
private Context mContext;
private ArrayList<String[]> dataStrings = new ArrayList<String[]>();
public StringAdapter(Context c,ArrayList<String[]> dataStrings){this.mContext=c;this.dataStrings=dataStrings;}
public int getCount(){return this.dataStrings.size();}
public Object getItem(int position){ return this.dataStrings.get(position);}
public long getItemId(int postion){ return (long) postion;}
public void print_stringData(){
System.out.println("String Adapter Output:");
for(String [] s: this.dataStrings){
System.out.println("Quotes: "+s[0]+"\nSignature:"+s[1]);
}
}
public View getView(int position, View convertView, ViewGroup parent){
LinearLayout ll;
if(convertView == null){
// set the linear layout
ll = new LinearLayout(this.mContext);
ll.setOrientation(LinearLayout.VERTICAL);
ll.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
// get the data and set the text inside
String[] data = this.dataStrings.get(position);
TextView //tvNo = new TextView(this.mContext),
tvString = new TextView(this.mContext),
tvSignature = new TextView(this.mContext);
ll.addView(tvString);
ll.addView(tvSignature);
tvString.setText(data[0]);
tvString.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
tvSignature.setText(data[1]);
tvSignature.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
tvSignature.setGravity(Gravity.RIGHT);
}
else{
ll = (LinearLayout) convertView;
}
return ll;
}
}
Comments
Some might notice that I add the String[] always to the first element. Actually I have tried to add to the last. The weird behavior still exists.
Environment
Android SDK Version: API 23 lollipop
Emulator Version: Nexus S API 23
Yes, of course, you get that error. Why? Because ListView always re-use convertView in your getView function of Adapter.
Look at your if,else:
else{
ll = (LinearLayout) convertView;
}
return ll;
At this block, you tell the adapter reuse the convertView, but you dont re-set the data. As a result, it will show the data of the previous row.
How to resolve it? just set the data in else block as you do in if one.
P/s: you should learn how to use ViewHolder in ListView to avoid laggy in when scrolling.
Related
Currently I'm trying to send intent data from user input to display onto my custom adapter. The intent data from user input from the detailspage.java class will be sent to the listview page with custom Arrayadapter. However each time i add a new item it overwrites my previous entries. Hope some kind souls can help someone new to android.
Below are snippets of my code.
// Set a click listener on that button
btnAddData.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
saveExercise(exerciseName);
}
});
}
private void saveExercise(String name) {
// Read from input fields
// Use trim to eliminate leading or trailing white space
String setString = mSetEditText.getText().toString().trim();
String repString = mRepEditText.getText().toString().trim();
String nameString = name;
Intent intent = new Intent(ExerciseDetailActivity.this, WorkoutSetActivity.class);
intent.putExtra("name", nameString);
intent.putExtra("set", setString);
intent.putExtra("rep", repString);
startActivity(intent);
}
The intent data will be sent to the activity page to initiate the custom ArrayAdapter.
public class WorkoutSetActivity extends AppCompatActivity {
ArrayList<WorkoutSet> workout = new ArrayList<WorkoutSet>();
/** Adapter for the ListView */
WorkoutSetAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.exercise_list);
adapter = new WorkoutSetAdapter(this, workout);
ListView listView = (ListView) findViewById(R.id.list);
listView.setAdapter(adapter);
Intent intent = this.getIntent();
String name = intent.getExtras().getString("name");
String set = intent.getExtras().getString("set");
String rep = intent.getExtras().getString("rep");
adapter.add(new WorkoutSet(name,set,rep));
Toast.makeText(WorkoutSetActivity.this, "Workout added.", Toast.LENGTH_SHORT).show();
adapter.notifyDataSetChanged();
}
}
Lastly the custom ArrayAdapter code. This is where I set the custom listview.
public class WorkoutSetAdapter extends ArrayAdapter<WorkoutSet> {
public WorkoutSetAdapter(Activity context, ArrayList<WorkoutSet> workout) {
super(context, 0, workout);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Check if the existing view is being reused, otherwise inflate the view
View listItemView = convertView;
if (listItemView == null) {
listItemView = LayoutInflater.from(getContext()).inflate(
R.layout.activity_workout_set, parent, false);
}
WorkoutSet currentWorkout = getItem(position);
// Find individual views that we want to modify in the list item layout
TextView nameTextView = (TextView) listItemView.findViewById(R.id.workout_name);
TextView setTextView = (TextView) listItemView.findViewById(R.id.workout_set);
TextView repTextView = (TextView) listItemView.findViewById(R.id.workout_rep);
nameTextView.setText(currentWorkout.getName());
setTextView.setText("Sets: "+ currentWorkout.getSet());
repTextView.setText("Reps: "+ currentWorkout.getRep());
return listItemView;
}
}
each time i add a new item it overwrites my previous entries
There are no "previous entries". startActivity loads an empty adapter each time and you only add one element.
What you want is a SQLite database (or any persistent storage layer, but SQLite is provided without more libraries).
On the plus side, your Intent and Adapter code look fine.
Every time,you call startActivity (), you open a new WorkoutSetActivity, it's a new WorkoutSetActivity object instance, so the arrayAdapter is also new one, it will only display the data you send this time.
If you want to display all the data, you must save the previous data to database or file. At WorkoutSetActivity onCreate (), you should get the previous data, put them and current data to the adapter.
I am trying to figure this out, but i just cant see what i am doing wrong. I want to dynamically fill ListView with products (add product to array then refresh list, basically for every click add one product to array then display content of array in ListView) which is in 2nd fragment with the data from the first Fragment. Passing Data is working (used Log to confirm), but my main problem is updating the ListView which has custom adapter. With this code nothing is happening (ViewList is not refreshing, I can see every log (and everything else works) and no errors).
Here is the code (All this is in 2nd Fragment):
This code is global
ReceiptListAdapter receiptListAdapter;
ArrayList<ReceiptItem> receiptItemArrayList;
int cat = 0;
int prodct = 0;
With this i get 2 numbers from 1st fragment (to this 2nd fragment)
public void use(int num1, int num2) {
Log.d("LOG","Category: " + num1 + "Product: " + num2); //This works
cat = num1;
prodct = num2;
ListView receiptItemListView = (ListView) getView().findViewById(
R.id.receiptItemsList);
receiptItemArrayList = (ArrayList<ReceiptItem>)generateReceiptItemsForTest();
receiptItemListView.setAdapter(receiptListAdapter); //i think here is the problem
receiptListAdapter.notifyDataSetChanged();
}
OnViewCreated
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ListView receiptItemListView = (ListView) getView().findViewById(
R.id.receiptItemsList);
receiptItemArrayList = (ArrayList<ReceiptItem>) generateReceiptItemsForTest();
receiptListAdapter = new ReceiptListAdapter(
getActivity().getBaseContext(), R.layout.receipt_item,
receiptItemArrayList);
receiptItemListView.setAdapter(receiptListAdapter);
TextView txtTotalAmmount = (TextView) getView().findViewById(
R.id.txtReceiptTotalAmmount);
double totalAmmount = getTotalAmmount(receiptItemArrayList);
totalAmmount = Math.round(totalAmmount * 100) / 100;
txtTotalAmmount.append(String.valueOf(totalAmmount));
}
private List<ReceiptItem> generateReceiptItemsForTest() {
List<ReceiptItem> receiptItemList = new ArrayList<ReceiptItem>();
DatabaseAdapter db = new DatabaseAdapter(getActivity());
if(cat != 0 && prodct != 0 )
{
name = db.readFromCategoriesAndGetOnePreduct(cat, prodct).getName();
price = db.readFromCategoriesAndGetOnePreduct(cat, prodct).getPrice();
Log.d("Name: " + name,"Price: " + String.valueOf(price)); //Also working (showing what i want) and i can see it in log but the ListView isnt refreshing this data
receiptItemList.add(new ReceiptItem(name,1,price,1);
}
}
return receiptItemList;
}
private double getTotalAmmount(ArrayList<ReceiptItem> receiptItems) {
double totalAmmount = 0;
for (ReceiptItem receiptItem : receiptItems) {
totalAmmount += receiptItem.getTotalPrice();
}
return totalAmmount;
}
ReceiptListAdapter
public class ReceiptListAdapter extends ArrayAdapter<ReceiptItem> {
private Context context;
public ReceiptListAdapter(Context context, int rowResourceId,
ArrayList<ReceiptItem> items) {
super(context, rowResourceId, items);
this.context = context;
}
#SuppressWarnings("deprecation")
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.receipt_item, null);
}
ReceiptItem item = getItem(position);
if (item != null) {
TextView itemDescription = (TextView) view
.findViewById(R.id.txtReceiptItemDescription);
TextView itemAmmount = (TextView) view
.findViewById(R.id.txtReceiptItemAmmount);
TextView itemPrice = (TextView) view
.findViewById(R.id.txtReceiptItemPrice);
TableRow listItem = (TableRow) view
.findViewById(R.id.trReceiptItem);
if (position % 2 == 0)
listItem.setBackgroundDrawable(view.getResources().getDrawable(
R.drawable.list_item_selector_a));
else
listItem.setBackgroundDrawable(view.getResources().getDrawable(
R.drawable.list_item_selector_b));
itemDescription.setText(item.getDescription());
itemAmmount.setText(String.valueOf(item.getAmmount()));
itemPrice.setText(String.format("%.2f", item.getPrice()));
}
return view;
}
}
Only time I got the list to update is when I used this code
receiptListAdapter = new ReceiptListAdapter(getActivity().getBaseContext(),R.layout.receipt_item,
instead of
receiptItemListView.setAdapter(receiptListAdapter);
But it only adds one time and refreshes the list again on click, but i know why.
Can you post the ReceiptListAdapter code? That is where you need to make the connection between the data source and the UI. You do this by registering ContentObservers on the database and then the ListView registers its ContentObserver with the Adapter. The Adapter notifies the ListView to update by calling the onChanged() method of the ListView DataSetObserver.
When working with a database, a common choice is to use a CursorLoader to fetch the data from the database on a background thread paired with a CursorAdapter. The CursorLoader automatically registers with the database to be notified of changes. You then implement the LoaderManager callbacks which tell you when the dataset changes and passes you an updated cursor. You then call swapCursor(cursor) on the CursorAdapter, passing it the new cursor and it notifies the ListView DataSetObserver that the dataset has changed. The ListView then queries the adapter to get the new values and refreshes the UI.
Check out this tutorial for some good example code: http://www.vogella.com/tutorials/AndroidSQLite/article.html
I figured it out i had to make this global List<ReceiptItem> receiptItemList = new ArrayList<ReceiptItem>(); Also I would like to thank middleseatman for explaining some things, it really helped me.
I am looking to integrate MergeAdapter into my project and I am having an issue trying to retrieve which section the user has clicked. I want to set it up so when user clicks any item in any section, the section number is returned so I know which section the user is in. The app I am working on requires this.
Code below sets up 3 sections with some data in each section.
public class SectionTesting extends Activity
{
ListView listView;
private MergeAdapter mergeAdapter = null;
private static final String[] items =
{ "One", "Two", "Three" };
Context context;
#Override
protected void onCreate(Bundle savedInstanceState)
{
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.mergelayout);
context = this;
listView = (ListView) findViewById(R.id.mergeListView);
mergeAdapter = new MergeAdapter();
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, new ArrayList<String>(
Arrays.asList(items)));
TextView sectionHeader = new TextView(this);
sectionHeader.setText("Section One");
mergeAdapter.addView(sectionHeader);
mergeAdapter.addAdapter(adapter);
TextView sectionHeaderTwo = new TextView(this);
sectionHeaderTwo.setText("Section Two");
mergeAdapter.addView(sectionHeaderTwo);
mergeAdapter.addAdapter(adapter);
TextView sectionHeaderThree = new TextView(this);
sectionHeaderThree.setText("Section Three");
mergeAdapter.addView(sectionHeaderThree);
mergeAdapter.addAdapter(adapter);
listView.setAdapter(mergeAdapter);
listView.setOnItemClickListener(new OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> arg0, View v, int position,
long arg3)
{
Toast.makeText(
context,
"You clicked Section "
+ mergeAdapter.getSectionForPosition(position),
Toast.LENGTH_SHORT).show();
}
});
}
}
I was hoping getSectionForPosition method would return the value I require, but it just returns 0 everytime. I tried calling the method getSections().length as well to check it was returning the correct number of sections, but again it comes back as 0 everytime.
Any help would be much appreciated!!
Edit
I managed to come up with this messy solution. In the MergeAdapter class, under the addView method, I added this line of code
view.setId(1000);
Then I added this method here
public int getSectionNumber(int position)
{
int section = 0;
for (ListAdapter piece : getPieces())
{
int size = piece.getCount();
if (position < size)
{
return section-=1;
}
position -= size;
if(size == 1)
{
if(piece.getItem(0) instanceof TextView)
{
TextView tv = (TextView) piece.getItem(0);
if(tv.getId() == 1000)
{
section++;
}
}
}
}
return (-1);
}
The code seems to work fine, its just a bit sloppy I think. If anyone can come up with a cleaner solution that would be much appreciated, if not, then I will just add this as the answer when I can and accept it
You have at least two problems:
In order to use getSectionForPosition(), your Adapter has to implement the SectionIndexer interface. ArrayAdapter<> does not. All MergeAdapter does is try to use any SectionIndexer implementations passed to it, and you have passed zero such implementations.
You are trying to reuse the same Adapter instance several times inside of the MergeAdapter, and I have no idea if that will work and certainly do not recommend it.
To address these, create individual adapters per section, and have those adapters implement SectionIndexer.
What im trying to do is display a checkbox, a button and a spinner in a TableLayout. The values are obtained from a web service and tableRows are added dynamically to the TableLayout.
The first button creates a dialog with a listview. The listview contains the numbers 1-30. When the user clicks selects a value in the ListView, i need the text of the button to change to the value clicked.
The code works for the first button that is clicked(The first time the listview is opened and a value is selected the text of the button is changed). But the second time i open the list view and select a button, the text of the first button changes and not the one which was clicked. What am i doing wrong?
protected void fillTableView() {
for (MedicineInfo temp : orderedMedList) {
LayoutInflater inflater = getLayoutInflater();
TableRow tr = (TableRow) inflater.inflate(
R.layout.neworderrestockmedicinelist2, tlOrderInfo, false);
CheckBox cbMedicine = (CheckBox) tr
.findViewById(R.id.cbNewOrderRestockMedName);
cbMedicine.setText(temp.vcProduct);
Button btnQty = (Button) tr
.findViewById(R.id.btnNewOrderRestockQty);
Spinner spnIntakeUnit = (Spinner) tr
.findViewById(R.id.spnNewOrderRestockIntakeUnit);
IntakeUnitAdapter intakeUnitAda = new IntakeUnitAdapter(this);
spnIntakeUnit.setAdapter(intakeUnitAda);
ArrayAdapter<String> arrayAda = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, numberArray);
arrayAda.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
tlOrderInfo.addView(tr);
btnQty.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
dlgQty = new Dialog(mContext);
TableRow tr = (TableRow) v.getParent();
ListView lvQty = new ListView(mContext);
QuantityAdapter qtyAda = new QuantityAdapter(mContext);
lvQty.setAdapter(qtyAda);
dlgQty.addContentView(lvQty,
new LinearLayout.LayoutParams(
LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT));
ItemSelectTest test = new ItemSelectTest(tr);
lvQty.setOnItemClickListener(test);
showDialog(DLG_QTY);
test = null;
}
});
}
}
private class ItemSelectTest implements OnItemClickListener {
Button btn = null;
public ItemSelectTest(TableRow tr) {
Button bt = (Button) tr.findViewById(R.id.btnNewOrderRestockQty);
btn = bt;
}
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
btn.setText(String.valueOf(arg3));
}
}
In the same shown above, i'm passing the TableRow that contains the button. I've also tried passing v from onClick. The problem is the same either way..
Thanks
I think that the problem is the way that you're showing the dialog. If you look at the docs for showDialog (here):
Show a dialog managed by this activity. A call to onCreateDialog(int,
Bundle) will be made with the same id the first time this is called
for a given id. From thereafter, the dialog will be automatically
saved and restored.
Presumably you're returning dlgQty in onCreateDialog. The thing is, that dialog will be cached after the first time. A quick fix for this is to always call removeDialog when you are done with it.
In one of my activities, I create a Linear Layout and some other Widgets when a bundle is received from an Intent. Currently, that Layout is overwrited each time I come back to that Activity. How can I create a new Layout each time without rewriting the code?
CODE:
public class FrontPageActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.frontpage);
Bundle bundle = this.getIntent().getExtras();
try{
String size = bundle.getString("size");
int toppcount = bundle.getStringArrayList("toppings").toArray().length;
LinearLayout container = (LinearLayout)findViewById(R.id.container);
TextView t = new TextView(this);
TextView tprice = new TextView(this);
tprice.setGravity(Gravity.RIGHT);
LinearLayout inner = new LinearLayout(this);
LinearLayout.LayoutParams innerparams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
inner.setLayoutParams(innerparams);
inner.setBackgroundDrawable(getResources().getDrawable(R.drawable.background));
inner.setPadding(10,10,10,10);
if(toppcount == 0){
t.setText(size+" Cheese Pizza");
}
else{
t.setText(size+" "+toppcount+" Topping Pizza");
}
tprice.setText(getPrice(size, toppcount)+"");
tprice.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.FILL_PARENT));
container.addView(inner);
inner.addView(t);
inner.addView(tprice);
}
catch(NullPointerException e){
}
final Intent sender = new Intent(this.getApplicationContext(), OrderPizzaActivity.class);
Button badd = (Button)findViewById(R.id.buttonaddpizza);
badd.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
startActivityForResult(sender, 0);
}
});
}
It sounds like you would need to create a data structure to hold the LinearLayouts, or provide a ViewGroup container for them to be added to each time.
Currently you are creating, modifying, and then overwriting the same LinearLayout in the try{} catch(){} block. Which I would guess to be the reason why it keeps overwriting.
As I understood you add new "options" to the "final order". Every time additional topping added you create a layout and fill it with specific data. And you want it to be aka OrderList. If what this app is about, you can have an application level variable myOrder:List. Add there toppings (topping = new Order()) and read list in FrontPageActivity.
Recommend you to have a separate layout for an order. Looping through orders fill layout with data and inflate in a container of Activity.
Idea in pseudo:
MyApplication extends Application {
private static List<Order> mOrderList = null;
public MyApplication(){
mOrderList = new ArrayList<Order>();
}
public static List<Order> getOrderList(){
return mOrderList();
}
public static void addOrder(Order order) {
mOrderList.add(order);
}
}
options activity:
MyApplication.add(order);
MyApplication.add(order);
FrontPageActivity
foreach(Order order : MyApplication.getOrderList()) {
// fill layout with order data
// inflate orderLayout into container
}