I have and Android app which adds TableViews, TableRows and TextViews dynamically at run time according to data found in a Firebase repository. More specifically, a TableView is added for each "Poll" found in the repo to the static LinearLayout, for each "Election" found in the Poll a TableRow is added to that Tablelayout, and for each "Nominee" in the Election a TableRow containing two TextViews for data is added to the TableLayout as well. So potentially I could have multiple tables added at runtime, each containing rows for multiple elections, and each election having multiple data rows for nominees. Here's my static layout for good measure....
<?xml version="1.0"?>
<ScrollView android:layout_height="android:layout_width="match_parent" android:id="#+id/scrollView1" xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout android:layout_height="match_parent" android:layout_width="match_parent" android:id="#+id/linearLayout" android:orientation="vertical">
</LinearLayout>
</ScrollView>
..and here's a representation of my Firebase repo...
-POLLS
NUMPOLLS - 5
(pollskey) - NAME - Poll1
NUMELECTIONS - 2
ELECTIONS
(electionskey) - NAME - Election1
NUMNOMINATIONS - 2
NUMVOTERS - 2
NUMBERTOELECT - 1
VOTERS - (votesrkey) - NAME - Charles
NUMBER - (678) 333-4444
.
.
.
(voterskey) - ...
NOMINATIONS - (nominationskey) - NAME - Richard Nixon
NUMBEROFVOTES - 2
.
.
.
(nominationskey) - ...
.
.
.
(electionskey) - ...
.
.
.
(pollskey) - ...
So my question really has two threads. The first is Android oriented...
What's the best way to iterate through Android views added dynamically to change TextView data when data in the repo changes, it being the case that I have no idea how many such TableView, or TableRows there will be at compile time?
Right now I'm doing something like this, but it's very slow going I feel like there has to be a better way...
private void appendCandidatesAndVotes(DataSnapshot election, TableLayout tbl) {
Random randGen = new Random();
DataSnapshot nominees = election.child("Nominations");
for (DataSnapshot nominee : nominees.getChildren()) {
// Create row for candidate name and number of votes
TableRow rowNameAndVotes = new TableRow(this);
// Generating a random row ID here allows us to pass this to
// the valueEventListener and quickly locate this row in
// the case of a data change (someone has cast a vote) so we
// can update
int uniqueRowId = randGen.nextInt(1000);
rowNameAndVotes.setId(uniqueRowId);
rowNameAndVotes.setBackgroundColor(Color.WHITE);
rowNameAndVotes.setLayoutParams(new TableRow.LayoutParams (
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT));
// Create candidate name view
TextView viewCandidateName = new TextView(this);
viewCandidateName.setId(2);
viewCandidateName.setText(nominee.child("Name").getValue(String.class));
viewCandidateName.setTextColor(Color.BLACK);
viewCandidateName.setPadding(5, 5, 5, 5);
rowNameAndVotes.addView(viewCandidateName);
// Create number of votes view
TextView viewNumVotes = new TextView(this);
viewNumVotes.setId(3);
viewNumVotes.setText(nominee.child("NumberOfVotes").getValue(String.class));
viewNumVotes.setTextColor(Color.BLACK);
viewNumVotes.setPadding(3, 5, 5, 5);
rowNameAndVotes.addView(viewNumVotes);
// Add row to table
tbl.addView(rowNameAndVotes);
// Lets get a Firebase reference for this nominee and
// attach a listener to alert us to all future changes of
// the values therein, so we can update vote counts dynamically
Firebase nomineeRef = nominee.getRef();
nomineeRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot nomineeSnapshot) {
String nomineeName = nomineeSnapshot.child("Name").getValue(String.class);
LinearLayout layout = (LinearLayout) findViewById(R.id.linearLayout);
int layoutChildCount = layout.getChildCount();
for (int i = 0; i < layoutChildCount; i++) {
View layoutChildView = layout.getChildAt(i);
// If its a Table, loop through all row
if (layoutChildView instanceof TableLayout) {
TableLayout tbl = (TableLayout)layoutChildView;
int tableChildCount = tbl.getChildCount();
for (int j = 0; j < tableChildCount; j++) {
View tblChildView = tbl.getChildAt(i);
if (tblChildView instanceof TableRow) {
TableRow row = (TableRow)tblChildView;
int rowChildren = row.getChildCount();
for (int k = 0; k < rowChildren; k++) {
TextView tv = (TextView)(row.getChildAt(k));
String rowCandidateName = tv.getText().toString();
if (rowCandidateName) ...
}
}
}
}
}
}
I think the way this is layed out, the only layout I can directly refer to is the base LinearLayout. I can give all TableRows unique ids with setId(), but as far as I know I can't pass any data to the ValueEventListener, nor can I refer to any variables in the enclosing scope (I hope I'm wrong about this, it would make things way easier). I know I can refer to static final variables inside the ValueEventListener, but I don't see how this will help me since I don't know how many Tables or Rows I'll be dealing with at runtime.
So, in short, how can I link a particular call to the ValueEventListener with the TableRow it's data is associated with?
Now for question 2, which is a bit more Firebase specific....
It seems like ValueEventListener is called once when the activity is loaded, and then again for any changes to the underlying data. But I only want it called for changes to underlying data, not on loading. Am I using the wrong EventListener? Or is there some way to avoid this behavior?
For the Firebase specific question - this is by design. Firebase doesn't encourage distinguishing between initial data and data updates, since in a real-time scenario you can never really be "caught up" to the latest data.
If you don't want to retrieve all the data on startup, then I'd use queries and limits to restrict what data you can receive. For instance, if you only want to receive the latest 100 polls, you could do something like:
Firebase ref = new Firebase(url);
Query q = ref.limit(100);
q.addChildEventListener(...);
and the child event listener will be called once per poll. When a new poll is added it will be called again (and there will be a child-removed event for the oldest poll in the list of 100).
Hope this helps!
Related
I would like to connect many TextViews with resources, for example, I have got 21 TextViews in xml file which all create a table. I would like to simple attach them to my code, but I want to avoid 21 lines of code.. I thought about looping it, but I am probably wrong, because it doesn't work.
for (int id = 1; id < 21; id++)
textView[id] = (TextView) findViewById(R.id.textView+"id");
}
You can link ViewGroup which is your table here and iterate their childs.
Lets assume we have a linear layout named as mLinearLayout.
for(i=0; mLinearLayout.getChilds.size; i++){
View child = mLinearLayout.getChildAt(i);
if(child instanceof TextView)
mList.add((TextView) child)
}
For more complex layouts you might need recursive functions to iterate child view groups.
Good luck
Instead of findViewById you can try findViewWithTag because now you can use a string argument like what you want.
Of course, you'll probably have to adjust a few things to be able to use this.
Your TextViews will need to be a direct child of a parent layout in your xml and tag them: android:tag="mytext1"
Then in Java, for example:
LinearLayout parentLayout = (LinearLayout) findViewById(R.id.parent);
...
textview[i] = (TextView) parentLayout.findViewWithTag("mytext" + i);
And you can loop that findViewWithTag
I've looked around at tutorials for TableLayouts and other such things, but all of it seems to be programmed as a static number of rows with textview's. I'm wondering if it would be possible to create a simple table of data, with 3 columns and a variable set of rows I can add/remove items from in the Java code.
Basically, have something like this:
DATA DATA DATA
row1 data data
row2 data data
and fill this table with data from an object array in the activity's Java class. Later, if I want to add another object, it will just create another row.
For instance, if I have this in java:
Class data{
public data(String d1, String d2, String d3){
data1=d1;
data2=d2;
data3=d3;
}
}
and this in the activity class:
Class activity{
data info[] = {
new data("row1", "row1", "row1"), new data("row2","row2","row2"),
new data("row3","row3","row3")};
}
}
And I will use a for loop to add this data into the table in the activity, regardless of how many rows I need for all of it to be fit. If I add a new object of row4, it will just add another row, and end with:
row1 row1 row1
row2 row2 row2
row3 row3 row3
I hope I haven't been too vague... Thanks in advance, fellas! :)
I feel very stupid, but I've figured this out on my own.
I simply created a <TableView> inside of my <SrollView>, and dynamically added rows to it via a for loop that goes from 0 to myArray.Length().
Bam:
TableLayout prices = (TableLayout)findViewById(R.id.prices);
prices.setStretchAllColumns(true);
prices.bringToFront();
for(int i = 0; i < drug.length; i++){
TableRow tr = new TableRow(this);
TextView c1 = new TextView(this);
c1.setText(drug[i].getName());
TextView c2 = new TextView(this);
c2.setText(String.valueOf(drug[i].getPrice()));
TextView c3 = new TextView(this);
c3.setText(String.valueOf(drug[i].getAmount()));
tr.addView(c1);
tr.addView(c2);
tr.addView(c3);
prices.addView(tr);
}
It's a drug-wars style game... Tryin' to start small in the game development field.
But... She works, and does exactly what I want it to do. Now I can wrap this into a seperate method and update it whenever I want. If I want to add a row, I just add an array entry.
Figured I'd answer my own question... lol!
If you want to have a completely dynamic table as you are used to by ListView or RecyclerView there is the table view on GitHub.
Your code will look like this:
String[][] myData = new String[][] { {"row1", "row1", "row1"},
{"row2", "row2", "row2"},
{"row3", "row3", "row3"} };
TableDataAdapter<String[]> myDataAdapter =
new SimpleTableDataAdapter(getContext(), myData);
TableHeaderAdapter myHeaderAdapter =
new SimpleTableHeaderAdapter(getContext(), "Header1", "Header2", "Header3");
TableView<String[]> table = findViewById(R.id.table);
table.setDataAdapter(myDataAdapter);
table.setHeaderAdapter(myHeaderAdapter);
Because the data is managed in a model you can update the table very easily.
myDataAdapter.getData().add(new String[]{"row4", "row4", "row4"});
myDataAdapter.getData().add(new String[]{"row5", "row5", "row5"});
myDataAdapter.notifyDatasetChanged();
As the table provides a lot of customization and styling possibilities result could look like this: (or completely different)
Best regards :)
You can also create it using a RecyclerView. I think it would be a lot quicker to render. All you have to do is to use GridLayoutManager. the number of gridcells on each row is the number of your columns. the Header row can be implemented as a different ViewHolder which uses a different ui and all you have to do with the rest of your data is to put it in a single dimension array.
You can create any view programatically from Java, by example: TextView tv = new TextView(context);
So you can do that for the the Table and for their rows and columns.
Once you have donde creating it you have to add it to your layout, you have to find the root xml element and then added to it:
Viewgroup root = findeViewById(R.id.root);
root.addViews(programaticView);
Problem
I've been trying to place a couple of TextViews underneath eachother, but I can't seem to get it working. They always overlap eachother in stead of getting placed beneath eachother.
What have I tried
I've tried messing with gravity (which doesn't work with a RelativeLayout), and all sorts of layout parameters. I've concluded that the best solution for me would be to use the RelativeLayout.BELOW parameter. The only problem is I'm trying to find the id of the previous TextView.
I assign the id's for the TextViews using the iterator. It seems I can't use the iterator - 1 to count as an id (even though other answers on SO suggest this works). I've even tried assigning the current TextView to a "previous_tv" variable to use in the next iteration.
I tried finding the TextView I just placed using this.findViewByID and the correct iterator value for the id, this also did not work.
Code
I'm trying to figure out what I should place as an id for my parameter rule. This is the code I'm reffering to -edit, placed full code as per request-:
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE); //Don't show application name on the top of the screen (valuable space)
setContentView(R.layout.activity_task_play); //Set which layout file to use for this activity
//Get the Task that was sent by TaskActivity
this.task = (Task) this.getIntent().getSerializableExtra("TASK_OBJECT");
//Get the Sums for this Task
this.sums = this.task.getSums();
//GridView setup
this.gridview = (GridView) this.findViewById(R.id.gridView_task_play);
ArrayAdapter<String> adapter = this.task.getArrayAdapterForGridView(this);
this.gridview.setAdapter(adapter);
//TextView setup
RelativeLayout layout = (RelativeLayout) this.findViewById(R.id.activity_task_play_relative);
for(int i = 0; i < this.sums.length; i++)
{
//Layout parameters
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.CENTER_HORIZONTAL);
//This variable counts how many times the placeholder text is skipped and a operator (+-/ etc.) is placed in the sum_text.
//This is usefull for placing the correct placeholder for each number in the sum (a, b, c etc.)
int times_skipped = 0;
TextView tv = new TextView(this);
String sum_text = "";
for(int j = 0; j < this.sums[i].getVariables().length; j++)
{
if(this.isParsable(this.sums[i].getVariables()[j]))
{
sum_text += TaskPlayActivity.PLACEHOLDERS[(j - times_skipped)] + " ";
}
else
{
sum_text += this.sums[i].getVariables()[j] + " ";
times_skipped++;
}
}
if(i > 0)
{
params.addRule(RelativeLayout.BELOW, i - 1);
}
tv.setId(i);
tv.setText(sum_text + "= " + this.sums[i].getAnswer());
tv.setTextColor(TaskPlayActivity.COLOURS[i]);
tv.setTextSize(25);
tv.setLayoutParams(params);
layout.addView(tv);
}
}
XMLAdded the XML. I'm not very good in layouts so it could very well be that the fault resides here.
<RelativeLayout
android:id="#+id/activity_task_play_relative"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="220dip"
android:layout_alignParentBottom="true">
<GridView
android:id="#+id/gridView_task_play"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#EFEFEF"
android:horizontalSpacing="1dp"
android:numColumns="3"
android:paddingTop="1dp"
android:verticalSpacing="1dp" >
</GridView>
</RelativeLayout>
</RelativeLayout>
Any other suggestions are welcome as well, though I would prefer keeping a RelativeLayout. Thanks in advance.
The documentation of View#setId says the identifier should be a positive number so you should make sure not to use zero as identifier value.
Also you have to create a new LayoutParams instance for each TextView. As it is all your TextViews share the same LayoutParams object and changes to that one object affect all TextViews.
And you could use View#generateViewId to generate an ID and remember the ID of the last iteration.
Seems like you are calling this code in a loop. In that case just put i - 1 (previous view id) as id for the rule.
Currently the list when populated is starting with the view # the bottom of the list. Is there a way using listAdapters to force it to the top of the list?
Currently the orientation scrolls to the bottom on create. Is there a way to pin the screen to the top when it creates? http://imgur.com/wGTEy in this example you see that entry 1 on create is shoved upwards to make room for six... Instead I want it to populate like this. http://imgur.com/6Lg6e... entry 1 is the top of the list and 6 is pushed off to the bottom for the scroll.
If you look at the picture above you will notice it starts at the bottom of the list instead of at the top. Any Ideas?
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mStrings);
setListAdapter(mAdapter);
registerForContextMenu(getListView());
populateFields();
private void populateFields() {
if (mRowId != null) {
Cursor note = mDbHelper.fetchDaily(mRowId);
startManagingCursor(note);
String body = note.getString(note.getColumnIndexOrThrow(NotesDbAdapter.KEY_DBODY));
mAdapter.clear();
if (!(body.trim().equals(""))){
String bodysplit[] = body.split(",");
for (int i = 0; i < bodysplit.length; i++) {
mAdapter.add(bodysplit[i].trim());
}
}
}
}
**edited to fix != string error.
You want the items later in the list to be at the top of the ListView? If so, check out this questions: Is it possible to make a ListView populate from the bottom?
You are completely changing the adapter, so the scroll position is lost in the process... You can use:
ListView listView = getListView();
int position = listView.getFirstVisiblePosition();
if (!(body.trim().equals(""))){
String bodysplit[] = body.split(",");
for (int i = 0; i < bodysplit.length; i++) {
mAdapter.add(bodysplit[i].trim());
}
}
listView.setSelection(position);
But this is not perfect as it is, if a row is added before position the index will be off. If your list contains unique values you can use ArrayAdapter#getPosition(), to find the new index.
While I still recommend using a CursorAdapter, because it handles large table data better, I want to address a point on efficiency with your ArrayAdapter code.
By using adapter.clear() and adapter.add() you are asking the ListView to redraw itself on every step... potentially dozens or hundreds of times. Instead you should work with the ArrayList directly and then ask the ListView to redraw once itself with ArrayAdapter#notifyDataSetChanged() after the loop completes.
I have a ViewGroup defined in XML with a view inside, at onCreate time I'd like to have a variable of those.
I don't want to go through the hassle of using a listview+adapter cause its clearly overkill as I know the list won't change since onCreate()
This is more or less the code I'd like to have.
TextView mytextview = myViewGroup.findViewById(R.id.mytext);
for(String test : strings){
mytextview = mytextview.clone();
mytextview.setText(test);
myViewGroup.addView(mytextview);
}
But it is not working.
Maybe use an inflater, and put the textview in an external layout file:
View v = LayoutInflater.from(this).inflate(R.layout.textview_include, null);
viewGroup.addView(v);
Using code of Mathias Lin and using the hint from javahead76:
LinearLayout x = (LinearLayout) findViewById(R.id.container);
for (int i = 0; i < 5; i++) {
View c = LayoutInflater.from(this).inflate(R.layout.singlerow, x);
TextView t = ((TextView)findViewById(R.id.textView1));
t.setId(i+10000);
t.setText("text"+i);
}
TextView b = (TextView) findViewById(10003);
b.setText("10003");
If you do this, you will most likely get the exact same id for every view created this way. This means doing things like ((TextView)v).setText("some text"); will be called on every TextView previously inflated from the same layout. You can still do it this way, but you should call setId() and have some reasonable method for ensuring you do not get the same id twice in a row - incrementation or universal time, etc.
Also, I think Android reserves a certain range of id's for dynamically created id's. You might avoid ID's in this range; but honestly, I don't know the id system works so I could be wrong on this point.