editTexts changing their value when rotating the device - android

The Context
My application architecture is very similar to the default called "Swipe Views" when you create a new android project.
One activity, 3 Fragments, you can swipe through them. It uses the support library.
everything is up to date.
The device I'm testing on is a galaxy nexus running android 4.1.2
Support library r11, and SDK versions min=14 target=16
The Problem
The second Fragment holds a bunch of EditText which contain various numerical values read from sharedPreferences. When the Activity is first created, everything is fine the EditViews all have their correct value, but if I modify one of the values to, for instance 555, and then I rotate the device, all the EditText are now displaying 555 !
There is only one setText in the whole program and it is displayed later in this post, I tried to remove it, as expected when starting the activity all the EditTexts display the default value (666), I modify one and rotate the device, they all display the new value!
The details
One activity with a SectionsPagerAdapter, a ViewPager and several fragments for each page.
In one of the fragments (ConfigurationFragment) in the onViewCreated method I dynamically create lines with and EditText and an ImageButton.
those lines are actually in a layout (xml file) which I inflate for each line to add, then I get the editText and call setText to set it's value to what it should be.
onViewCreated method:
public void onViewCreated(View view, Bundle savedInstanceState)
{
// populate ports list on view creation
ArrayList<String> listenPorts = mServerManagerThread.getListenPorts();
for (String port : listenPorts)
{
EditText v = ((EditText) addListenPortItem().getChildAt(0));
v.setText(port);
}
super.onViewCreated(view, savedInstanceState);
}
naturally the method getListenPorts returns the same list each time it is called (on startup and when the device is rotated)
what addListenPortItem basically does:
// Instantiate a new "row" view.
final ViewGroup newView = (ViewGroup) LayoutInflater.from(viewGroup.getContext()).inflate(itemResource,
viewGroup, false);
// Set a click listener for the "X" button in the row that will remove
// the row.
newView.findViewById(R.id.delete_button).setOnClickListener(
new OnRemoveClickListener(mContainer, viewGroup, newView, emptyListTextResource));
viewGroup.addView(newView);
viewGroup is a linearLayout which will hold the newly created views
itemResource is the following layout
layout of one line:
<?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" >
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:layout_marginTop="8dp"
android:divider="?android:dividerVertical"
android:dividerPadding="8dp"
android:gravity="right"
android:orientation="horizontal"
android:showDividers="middle" >
<EditText
android:id="#+id/editPort"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="number"
android:text="666" >
</EditText>
<ImageButton
android:id="#+id/delete_button"
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="#string/action_remove_item"
android:src="#drawable/content_remove" />
</LinearLayout>
any idea?
I've been staring at this bug for a couple hours now and I really have no clue what's going on ... I'm starting to suspect a bug in the SDK or the support library, but I know from experience the bug is most often in my brain :)

I found the solution: I moved the code from onViewCreated to onViewStateRestored(..) so that filling the EditTexts with their value is done after the state has been restored.
The problem was that all the EditTexts have the same id so when restoring the state it was messing it up.

That's not a bug, so don't be worried. The problem is that if you rotate the screen the Activity and the Fragments will be recreated. onCreateView() will be called again and this mixes things up and therefore you have such a weird behaviour. You can fix this by overriding these thwo methods:
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Read values from the "savedInstanceState"-object and put them in your textview
}
#Override
protected void onSaveInstanceState(Bundle outState) {
// Save the values you need from your textview into "outState"-object
super.onSaveInstanceState(outState);
}

Related

View won't update in dialog after creation

Surely a simple question to ask but I've tried for hours and I can't seem to get the problem !
I have a DialogFragment which contains a
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/interval_input_layout"
style="#style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:boxStrokeWidth="0dp">
<AutoCompleteTextView
android:id="#+id/time_interval_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
tools:ignore="LabelFor" />
</com.google.android.material.textfield.TextInputLayout>
This is the listener set on this AutoCompleteView
binding.untilInput.onItemClickListener =
AdapterView.OnItemClickListener { parent, view, position, id ->
when (position) {
0 -> {
binding.numberLayout.visibility = View.GONE
}
1 -> {
binding.numberLayout.visibility = View.GONE
}
2 -> {
binding.numberLayout.visibility = View.VISIBLE
}
}
}
While number layout is just a linear layout like this
<androidx.appcompat.widget.LinearLayoutCompat
android:id="#+id/number_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="16dp">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="For number of events"
android:textColor="?android:textColorSecondary" />
</androidx.appcompat.widget.LinearLayoutCompat>
At first the listener wasn't being called so I set it like this instead of directly calling the function setOnItemClickListener (don't know why that wasn't working) , Now the listener is being called , I even put a breakpoint and debugged it and its setting visibility but its not taking any effect and it does not cause any error so I can't seem to get the problem here
Using a DialogFragment I inflated the view layout with the use of databinding in the method OnCreateView & then in OnViewCreated I worked with my view and caused changes to UI and set listeners to it and again cause changes to UI
in OnCreateDialog I called both of these methods onCreateView (to get the view and set it to dialog) and onViewCreated to setup the view
I tried to find the view using findViewById just to ensure if the databinding was working fine and it was working fine but visibility still won't change and the view won't update !
I still have't gotten to the root of the problem but it seems you have to store the view inside a variable so you don't lose a reference to it when onClickListener is called and in my opinion just when the onClickListener is called getting the view from the binding becomes invalid and it should but the small problem is that I am not calling onDestroy to make the binding null because the binding was invalid at that time !
Now I am just storing a refrence to binding like this
val myvar = binding.monthLayout
and I am capturing the value of myvar inside the onClickListener rather than using the binding

setNextFocusDownId(...) Not Doing as Expected

I am attempting to add multiple EditTexts to a GridLayout and want to be able to specify which EditText is focused when the user clicks the "Next" button on the keyboard.
I thought this was going to be as simple as setting the setNextFocusDownId(...) method on the each EditText but unfortunately this doesn't seem to work.
My Layout file simply consists of a GridLayout with an EditText at the bottom:
<?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="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.GridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:grid="http://schemas.android.com/apk/res-auto"
android:id="#+id/Grid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
grid:columnCount="2">
</android.support.v7.widget.GridLayout>
<EditText
android:id="#+id/Last"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Last"/>
</LinearLayout>
And the code in my MainActivity.java file dynamically adds 10 EditTexts and attempts to assign the 'NextFocus' appropriately:
public class MainActivity extends AppCompatActivity
{
final Context context = this;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GridLayout grid = (GridLayout) findViewById(R.id.Grid);
int N=10;
grid.setRowCount((N+1)/2);
EditText prev = null;
for (int i=0 ; i<N ; i++)
{
EditText t = new EditText(context);
t.setInputType(InputType.TYPE_CLASS_TEXT);
t.setMaxLines(1);
t.setHint("" + i);
grid.addView(t);
if (prev != null)
prev.setNextFocusDownId(t.getId());
prev = t;
}
}
}
This does indeed create a grid with two columns of 5 elements however, clicking in the top cell and clicking Next arrow (">") on the keyboard takes you down a row rather then to the expected "NextFocused" ExitText.
Starting in the '0th' cell the "next" button takes you successively through the 'default' sequence:
'0' > '2' > '4' > '6' > '8' > "Last" (rather than the expected '0' > '1' > '2' > ... > '9' > "last").
Interestingly though, setting the NextFocused to findViewById(R.id.Last).getId() (instead of t.getId()) does cause focus to jump to "Last" from every cell in the grid. Any idea why this isn't working for t.getId().
The answer to this question seems to suggest simply setting 'setNextFocusDownId(...)' should do the trick however it's not working for me. Is there something different about accessing ID's of dynamically created View's that is different from XML defined Views that I don't quite understand?
Thanks,
Slarti.
Huh-harr, worked it out!
I didn't realise it but when a view is created dynamically it's ID is always created with a value if '-1'. One can however assign an ID to a view using the View's setId(int). From API level 17 onward one can generate an ID to use for this purpose using generateViewId() but prior to API 17 you have to come up with your own ID. Fortunately, according to this post, R.id... values are all greater than 0x00FFFFFF, so simply creating dynamic ID's by counting up from '0' should keep you well clear of the previously defined values.
Therefor, adding the line t.setId(i); as soon as the TextView is created in the example above resolves the problem nicely.
For another well answered question regarding dynamically assigned ID check out this question.
Cheers,
Slarti.

EditText value is getting replaced with another edit text on backpress

I am in a situation here, I've two edit text in a fragment, on back press the value of first edit text is getting replaced by second edit text. Now both the edit text are having same values.
<EditText
android:id="#+id/edit_text"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="20dp"
android:hint="hint" />
Above is my EditText, this EditText is inside a layout and I've included the layout in my fragment two times to get the EditText, on click of submit I replace this fragment with another one but on backpress when I come back to this fragment, the value of first edit box is getting replaced by second one. Now both has same values.
This is how I am replacing fragment.
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.layout_fragment, fragment, tag);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commitAllowingStateLoss();
getSupportFragmentManager().executePendingTransactions();
Both of your EditTexts are using the same id. In your XML files you need to make sure that the id for both edit texts are different. Even if there are in separate XML files, they must have different ids.
For instance, you might change their declarations to...
<EditText
android:id="#+id/edit_text1"
<EditText
android:id="#+id/edit_text2"
You should use different id for the EditText.
Assume the EditText is defined in edit_text_layout.xml, when including the layout, specify different id.
In fragment_layout.xml:
<include
layout="#layout/edit_text_layout"
android:id="#+id/edit_text_1" />
<include
layout="#layout/edit_text_layout"
android:id="#+id/edit_text_2" />
Explain:
Android save a view's state in a SparseArray, the key is the view's id. When restoring the state, views have the same id will be get the same state value.
If the view doesn't have an id (NO_ID), its state won't be saved or restored.
android.view.View.java
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
......
if (state != null) {
......
container.put(mID, state);
}
}
}
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID) {
Parcelable state = container.get(mID);
if (state != null) {
......
onRestoreInstanceState(state);
......
}
}
}
I know this is an old question but I ran into a very similar issue and although the current answers are correct (its a duplicate ID issue), the actual answers given weren't working or quite in the right area.
I had a Fragment, and in that Fragment I had 2 custom controls, both with unique ID's, but internally in those controls they included the same common Layout, and so that Layout of course had an EditText with the same ID in both.
#wrkwrk is right in that its an ID issue, but changing the ID on the "include" isn't enough if the included layout contains the EditText, and if you want to include the same layout multiple times, you can't give it a different ID in the axml.
So for my work around, in the code for my custom control that loads the EditText in, after loading it in I simply changed the ID afterwards using the View.GenerateViewId() function.
So something like (this is in C#/Xamarin)
var editText = _root.FindViewById<EditText>(Resource.Id.entryEditText);
editText.Id = GenerateViewId();
That seemed to fix the issues for me. So I can have one common ID in the layout so my control can find it fine, but then it changes so multiple instances of that control in a fragment will all save off their EditText state fine when going back.
Alternatively, you could give it no ID in the layout but a Tag instead and do FindViewByTag and then assign it an ID after loading.

Correct way to save and restore state of multiple versions of the one layout

Consider the following overly simplified example. I have a LinearLayout with a list of items (EditTexts). The number of items is not known at compile time (for example it might be a game menu screen where the user has just selected the number of players and on this screen they are entering each individual player's name).
The obvious way to do this is to have a container layout defined in xml (R.layout.list), and individual item layouts defined in xml (R.layout.item); which would look something like this:
public class Main extends Activity {
private static final String FRAG_TAG = "ITEMS_FRAGMENT";
private static final int NUM_ITEMS = 3;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout ll = (LinearLayout) getLayoutInflater().inflate(R.layout.list, null, false);
for (int i = 0; i<NUM_ITEMS; i++) {
ll.addView(getLayoutInflater().inflate(R.layout.item, null, false));
}
setContentView(ll);
}
}
With the R.layout.list and R.layout.item layouts respectively:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/list" />
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/item"
android:singleLine="true"/>
The problem is that on an orientation change, the fact that all items have the same ID (R.id.item) confuses the restoration process. The end result is that the state of the last item is restored to all items in the list (i.e. if the last item's EditText has "hi" in it, every EditText will have "hi" in it after the orientation chance).
In the instance of this in my code, each item has a CheckBox that I can get the state of with a isCheckboxChecked(LinearLayout list, int itemNumber) function that relies on the fact that each item has a view with ID R.id.checkbox. Is there a proper way to do view state restoration that accepts there may be multiple items inflated from the same XML resource (and consequently will have the same ID)?
(ListView is not appropriate for this case, but it handles this correctly. How does it restore state correctly when all of it's items typically have the same ID?)
Android stores states by associating them with element's ID. If all elements have same ID, then a state associated with this ID will be restored to all elements with this ID. LisView does it very differently. It always reads values from Adapter by element position. Because views with the same ID will have different position in the list, they will have different values too.
If your case you have couple of options.
1) You need to assign different ID'S to your items before adding them to the LinearLayout by calling setId() method. You need to define those ID's in Android resources firs (e.g. <item type="id" name="item_01" /> etc.).
2) You need to override onSaveInstanceState(Bundle outState), go through the children of LinearLayout and store their texts into outState bundle associating text with children's index. Then in onRestoreInstanceState() you need to read states from savedInstanceState and set them to your views correspondingly.
3) You use ListView and when user enters text, your adapter persists data (e.g. into a file or database). When configuration changes, or on the next start of Activity your adapter will read those stored values, and they will be restored properly.
Hope one of the options can help you.

How to have several tabs from the same layout, correctly handling edittext?

I've got three tabs, all from a layout xml file,say list.xml. In list.xml I've defined a EditText, something like this:
<EditText
android:id="#+id/filterText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="#+id/filterClear"
android:hint="#string/filterHint"
android:text="#string/filter">
</EditText>
However, it seems that if doing a orientation change and having typed something into one of the three EditTexts (all I guess with the same ID), the text doesn't restore to the same state. Sometimes (depending on which tab I've typed into) all fields are empty or all fields are filled.
What is the proper way to handle this?
I ended up having three separate layout files list1.xml, list2.xml, list3.xml, that only differed in the IDs. So list1.xml would have #+id/filterText1, list2.xml would have #+id/filterText2 and so on.
And then in the ListActivity.java file I used to have this as part of my onCreate:
setContentView(R.layout.list);
And several references to this throughout the activity:
EditText filter = (EditText)findViewById(R.id.filterText);
...
Instead I have now got this in my onCreate:
if(START_TAB1.equals(startMode)) { // Checks which tab is being started
setContentView(R.layout.list1); // Use appropriate layout
filterTextId = R.id.filterText1; // Save correct ID in a class variable
}
// And then the same "else if..." for all other tabs
And this when referencing the field itself throughout the activity:
EditText filter = (EditText)findViewById(filterTextId); // Use common ID

Categories

Resources