Inflating Android list subviews -> duplicate IDs - android

I have some content in an Android app which is shown in a list. Each list entry has similar fields - let's say a picture, some text, and a text box. However, some list entries are different than others. The order of the content is based on the result of a server call.
The list itself needs to be fairly dynamic, and I'm currently using a linearlayout rather than a listview for a few reasons. My code looks something like this:
LinearLayout list = findViewById(android.R.id.list);
while (more content to add) {
switch (content type) {
case A:
View v = layoutInflater.inflate(R.layout.list_item_type_a, list, false);
EditText editText = (EditText)v.findViewById(android.R.id.edit);
// Do stuff with editText
list.addView(v);
break;
case B:
View v = layoutInflater.inflate(R.layout.list_item_type_b, list, false);
....
}
}
This works great. Except - when I put this in a fragment, and rotate the screen, now my app crashes because I have multiple fields with the same android.R.id.edit identifier.
I had thought this was a fairly elegant solution and the Android gods seem to disagree. Do I need to rip out the ID for all of my xml sublayouts? If I go this route, how should I grab references to the content?

So obviously, ListView or RecyclerView would be preferable to use here, but since you've stated you have reasons not to, I'd suggest that you disable automatic state saving for each of the views.
You can just call editText.setSaveEnabled(false), which will fix the issue, but have the side effect of not automatically retaining the view's state (e.g. input data will be lost). If you're maintaining this data yourself and restoring it on configuration changes or state restoration, this should be a totally workable solution.
I believe you could also just call setSaveFromParentEnabled(false) on the containing LinearLayout (although I haven't used that flag myself), which should disable state saving for any view in the sub-hierarchy. Same caveat applies.

I would suggest using a ListView instead of the LinearLayout and creating a custom adapter to fill the ListView. You could still have the list_item layouts that you have and then add them to the list in the newView method of your adapter. Pass your content type through a method, say getItemViewType(). Something like this:
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
// Choose the layout type
int contentType = getItemViewType();
int layoutId = -1;
switch (contentType) {
case A: {
View v = layoutInflater.inflate(R.layout.list_item_type_a, list, false);
EditText editText = (EditText)v.findViewById(android.R.id.edit);
// Do stuff with editText
list.addView(v);
break;
}
case B: {
View v = layoutInflater.inflate(R.layout.list_item_type_b, list, false);
....
}
}

I think this problem occur due to device screen rotation
When the phone rotates and the screen changes orientation, Android usually destroys your application’s existing Activities and Fragments and recreates them. Android does this so that your application can reload resources based on the new configuration.
The most important aspect of handling orientation changes is saving state. In most cases this involves implementing the onSaveInstanceState method (this could be in your Activity, Fragment or both) and placing the values you need to save in the Bundle argument that gets passed to the method.
For more details and code examples...
Please read this article

If you have a dynamic list, you should be using a ListView or RecyclerView.

Related

How do I initialize elements of a GridView on creation?

My android is very rusty, so this is the best way I can explain this:
A card contains an image, a value, and a URL
I have an array of values, a parallel array of images, and of URLs (values[i] <-> images[i] <-> URLs[i])
Have a GridView that I want to use to display many of these cards
The problem:
I have a class that extends BaseAdapter to create a custom view to display the three elements of the card
Using the getView method of said adapter, I use the "i" expected by getView as a mental index of which card we are talking about.
Unfortunately I realized that i=0 means the currently visible first card, I thought it meant the overall first card. This makes it useless as a system to keep track of the overall position of cards.
So, the visible elements are populated correctly in the view. But, if I scroll down and then back up, some internal elements have been jumbled up. So clicking a card might now lead to the URL of a card that was initialized after it.
What I need help with:
A better way to index or populate each card's content that will be permanent.
I am wildly confident I am doing this in a horrendous way. I'm imagining there must be some way to say that:
When GridView is created -> populate each card's details and fill in GridView.
Current Main Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_card_list);
gv = (GridView) findViewById(R.id.cardGridView);
gv.setAdapter(new CardView(this, cardURLs, cardNames, cardPrices, cardImages));
}
Current CardView Activity:
public CardView(CardListActivity mainActivity, String[] cardURLs, String[] cardNames, Double[] cardPrices, int[] cardImages){
//...
inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public class Holder{
TextView priceTV;
ImageView cardIV;
String cardName;
}
#Override
public View getView(final int i, View convertView, ViewGroup parent) {
//...
View rowView;
rowView = inflater.inflate(R.layout.card_item_view, null);
//HERE IS WHERE I SET THE PRICE AND IMAGE USING i
holder.priceTV.setText("$" + prices[i].toString());
holder.cardIV.setImageResource(images[i]);
//...
return rowView;
}
Turns out the problem was something else.
The actual problem ended up being caused by these Dialogs I would create to verify if the user wanted to open the website.
I was creating them inside getView, all in the same variable, which meant that the last elelemnt to get initialized would be the one used in the dialog.
I fixed this by moving the dialog creation into the onClick for the view.
Firstly, You should wrap your contents into objects so that each CardContent object contains a url, an image and a value, Then pass those into your adapter. That will be much easier on you, you only need to maintain 1 List of CardContent rather than 3 individual lists and hoping the order doesn't get messed up.
Secondly, This sounds like a case for a Recyclerview. You can use a GridLayoutManager with a Recyclerview instead of a GridView so that your views get recycled and you have less overhead. Luckily the code is largely the same.
See https://developer.android.com/training/material/lists-cards.html for pretty much what you want.

Adding and Deleting between two seperate Array ListViews

I want to create a custom ListView.
Initially, the custom ListView has one array of data, but when user taps one of the list items, it's then removed from current array and added to another. If the user taps on the second array, the list item is then added back over into the first array.
Please suggest how to apply logic to do this.
Updates : I wants to use only one listview/recyclerview.
Following are screen shots..
Regarding the object switch - this is a simple transfer between lists, , just know beforehand if the insertion and removal is index based, e.g:
contacts.add(iLocation, ContactObject);
favorites.remove(iOtherLocation);
Regarding the ListView stuff, I would suggest converting to RecyclerView, let's build a general scenario:
You have a screen (Activity or Fragment) that holds one list (the implementation can be ListView or Recycler), and another screen that holds the other list.
In both of your lists you have adapters in which you implement the logic for the clicks on the objects in the lists.
The click transfers the object, either directly to the other list, OR to a temporary Object holder (because you might need it for other stuff), in which case you will need to pull that object from the other view, either way you remove it from the current one.
you switch to the other view, and refresh it.
An easy way to go -
Assuming the screens are the same, use only one Activity, holding a single RecyclerView, and handle 2 adapters, each for every list, the adapters allow you to handle the clicks easily, with an index for the object clicked, the click executes the info swap action,the Activity handles the visual swap Action.
a very general example would be:
//init everything obviously ;)
List<ContactObject> contacts;
List<ContactObject> favoritesContacts;
//the AdapteListener is an interface declared inside the adapter
mContactsRecyclerAdapter = new ContactsRecyclerAdapter(this, contacts,new ContactsRecyclerAdapter.AdapterListener()
{
#Override
public void cellClicked(int iIndex, ContactObject object)
{
favoritesContacts.add(iIndex, ContactObject);
contacts.remove(iIndex);
mContactsRecyclerAdapter.notifyDataSetChanged();
mFavoritesRecyclerAdapter.notifyDataSetChanged();
mRecyclerView.swapAdapter(mFavoritesRecyclerAdapter, false);
}
});
And vice-versa for the other adapter.
Hope this helps, comment if you have problems and I'll update.
Please implements with custom view extend with Linearlayout
Custom view has 2 child Linearlayout in which will add with this custom view
First time add all the element in first Linearlayout and based on user action please remove from first Linearlayout and add it in another layout

Show views currently focused (Even without layout selectors)

I'm trying to inspect a code for a very big Android (Amazon Fire TV) activity but i keep loosing the focus in the running app and i don't know what element is being focused.
I'm looking for a way (Wether it's an App, a developer setting - Show Layout Limits gets near - or something i can code inside the activity) to see what view is being focused, without having to change the layout (Selectors) of every single view.
What do you suggest?
Activity has a method called getCurrentFocus().
Maybe you could call hasFocus() on all the Views if the above doesn't work. I imagine the method would look something like this:
public View getFocusedView(View layout)
{
View focusedView = null;
// Note: I'm not sure if FOCUS_DOWN is the right one to use here
// so you may want to see the other constants offered
ArrayList<View> views = layout.getFocusables(View.FOCUS_DOWN)
for(View v: views)
{
if(v.hasFocus())
{
focusedView = v;
}
}
return focusedView;
}

Change fragment layout dynamically instead of replacing fragments Android SDK

I'm trying make an app that displays a big amount of text and images in a specific layout when the user clicks on a corresponding listview item. Since I want specific .xml layouts for separate 'chapters', I want only the layout of a fragment to change to the corresponding chapter.
Googling tells me I can do this with fragments, but as far as I understand, I need separate fragment classes and .xml layouts for every chapter I want to implement. With 2 or 3 chapters, that can be done, but with more chapters that will become I thought, isn't it simpler to just keep two fragments (one with a listview and one with the chapter text), but dynamically change the layout of the second fragment if the user clicks on an item in the listview.
Can this be done with some code like this (just thinking out loud)?
Int[] layouts = {
{R.layout.chapter1, R.layout.chapter2, R.layout.chapter3}
};
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
index = intent.getIntent("index", 0);
return inflater.inflate(layouts[index], container, false);
}
Or is there another way to achieve this?
Fragments at their core are View wrappers that contains states of Views. You can use them for other purposes like resource handling, but mostly they're just segments of an Activity state. It most likely would not be a good idea to have a Fragment for every single chapter unless each chapter has their own unique state that needs to be kept. However, if the Views are static, then a single Fragment is all you really need.
In this case, you simply have to have a method like this:
public void setChapter(int chapter)
{
Context ctx = getActivity();
if(ctx == null) {
// detached from Activity, so bail.
return;
}
if(chapter > layouts.length) {
return;
}
ViewGroup container = (ViewGroup) getView().findViewById(R.id.chapter_container);
container.removeAllViews();
View chapterInflater = LayoutInflater.from(ctx).inflate(layouts[chapter], container);
}
So this will wipe out all views currently in your container, inflate a new one, and put it in the container (most likely a simple FrameLayout).
The code in the original question can be used to initialize a Fragment if you want to open it at a certain point. onCreate_() methods are called only when the items is being "built" or "created". onCreateView() won't be called again though, so you need a method to change the layout once it's set.

Marking text in the row of the list as "READ" permenatly Android

What I'm trying to accomplish here is to set the text in a given row as read if the user clicks on it, now I was able to do that by using the onclick method, the problem with it is that it goes away when an intent is fired or the user exits the app. I want the Text to be set up as read permanently. here is my piece of code if anybody can help I would greatly appreciate it.
Thank you in advance:
public void onListItemClick(ListView parent, View v, int position, long id) {
LinearLayout ll = (LinearLayout) v;
TextView clickedTextView = (TextView) ll.getChildAt(1);
clickedTextView.setTextColor(Color.GREEN);
StringTokenizer st = new StringTokenizer(strings[position],"<#>");
for(int i=0;i<3;i++)
{
coupon = st.nextToken("<#>");
}
sharable=st.nextToken();
Intent i = new Intent(getApplicationContext(), CouponImage.class);
i.putExtra("The coupon", coupon);
i.putExtra("Sharable", sharable);
startActivity(i);
}
You'll have to store the read status for each text item, either in a SQLite Database, or in a flat file in Internal Storage.
This is because whenever you scroll the list, leave the app and come back, etc. you end up with the ListAdapter re-rendering your row view, and your views in a list are never 1:1 with underlying data due to view recycling. If you want your change to be "sticky" you need to think about modifying the underlying data for the ListAdapter in a way it the adapter knows how to render correctly, not just changing this particular instance of a row -- you're marking the item read, not just setting one instance of a rendered view of the item as read. You can think of it as an MVC thing if it helps.
That is, the real change here should be to your Adapter's getView method, with a change to its data source and then possibly a call to notifyDataSetChanged.
Fredley's answer about SQLite or storage may be overkill if that data doesn't need to persist beyond this one session in the activity, or if the data you're working with is also transient (e.g. network data that changes often that's temporarily loaded into an ArrayAdapter), and in any case it's a bit misleading because just dumping data to disk doesn't solve the fundamental issue with your conflation of views of data with your models.

Categories

Resources