Custom View w/ EditTexts: state being overwritten

Put problem concerning Views, Layouts and other XML-Resources (like AndroidManifest) here.

Custom View w/ EditTexts: state being overwritten

Postby divestoclimb » Mon Sep 28, 2009 6:08 am

I'm trying to figure out how to properly make a custom view consisting of a LinearLayout and some children, one of which is a EditText. To make this simple, I've pared down the code to the simplest possible case.

res/layout/bughunt.xml is a basic definition of a custom view; it's just a layout with a single EditText inside.
Syntax: [ Download ] [ Hide ]
Using xml Syntax Highlighting
  1.  
  2. <?xml version="1.0" encoding="utf-8"?>
  3.  
  4. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  5.  
  6.         android:orientation="horizontal"
  7.  
  8.         android:gravity="center_vertical"
  9.  
  10.         android:layout_width="wrap_content"
  11.  
  12.         android:layout_height="wrap_content">
  13.  
  14.         <EditText
  15.  
  16.                 android:id="@+id/text1"
  17.  
  18.                 android:layout_width="wrap_content"
  19.  
  20.                 android:layout_height="40sp"
  21.  
  22.                 android:gravity="center"
  23.  
  24.                 android:width="50sp"
  25.  
  26.                 android:singleLine="true"/>
  27.  
  28. </LinearLayout>
  29.  
  30.  
Parsed in 0.002 seconds, using GeSHi 1.0.8.4

My custom view class:
Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.  
  2. public class BugHunt extends LinearLayout {
  3.  
  4.  
  5.  
  6.         public BugHunt(Context context) {
  7.  
  8.                 super(context);
  9.  
  10.                 initLayout(context);
  11.  
  12.         }
  13.  
  14.  
  15.  
  16.         public BugHunt(Context context, AttributeSet attrs) {
  17.  
  18.                 super(context, attrs);
  19.  
  20.                 initLayout(context);
  21.  
  22.         }
  23.  
  24.  
  25.  
  26.         private void initLayout(Context context) {
  27.  
  28.                 LayoutInflater i = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  29.  
  30.                 i.inflate(R.layout.bughunt, this);
  31.  
  32.         }
  33.  
  34. }
  35.  
  36.  
Parsed in 0.032 seconds, using GeSHi 1.0.8.4

Then I put two instances like the following in an Activity layout file:
Syntax: [ Download ] [ Hide ]
Using xml Syntax Highlighting
  1.  
  2. <my.package.BugHunt
  3.  
  4.         layout_width="wrap_content"
  5.  
  6.         layout_height="wrap_content" />
  7.  
  8.  
Parsed in 0.001 seconds, using GeSHi 1.0.8.4

The problem I'm having is when the EditTexts save state, they overwrite each other's state. To reproduce this, put two or more instances of BugHunt into an Activity, run it, and enter some (different) text into each EditText. Then, switch the screen orientation. After the layout is redrawn, all EditTexts will have the same text in them (the text from whichever EditText saved state last).

I can only guess that I'm not using the LayoutInflater properly. Any ideas?
divestoclimb
Developer
Developer
 
Posts: 33
Joined: Mon May 11, 2009 7:46 pm

Top

Postby divestoclimb » Mon Sep 28, 2009 3:19 pm

I got a reply on android-developers that this is the result of having the same ID for each EditText. I'm devising a method to work around this and will post it here for everyone's benefit when I get it working.
divestoclimb
Developer
Developer
 
Posts: 33
Joined: Mon May 11, 2009 7:46 pm

Postby divestoclimb » Tue Sep 29, 2009 2:34 am

Here's the 90% solution.

First, I abstracted a bit of processing into a separate class called ViewId, which generates an ID unique within the passed View:
Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.  
  2. public class ViewId {
  3.  
  4.  
  5.  
  6.         public static int generateUnique(View v) {
  7.  
  8.                 Random r = new Random();
  9.  
  10.                
  11.  
  12.                 int id;
  13.  
  14.                 do {
  15.  
  16.                         id = r.nextInt();
  17.  
  18.                 } while(v.findViewById(id) != null);
  19.  
  20.                 return id;
  21.  
  22.         }
  23.  
  24. }
  25.  
  26.  
Parsed in 0.031 seconds, using GeSHi 1.0.8.4


Then I made these changes in my example BugHunt class:
Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.  
  2.         private void initLayout(Context context) {
  3.  
  4.                 LayoutInflater i = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  5.  
  6.                 i.inflate(R.layout.bughunt, this);
  7.  
  8.  
  9.  
  10.                 mEditText = (EditText)findViewById(R.id.text1);
  11.  
  12.                 setEditTextId(ViewId.generateUnique(getRootView()));
  13.  
  14.         }
  15.  
  16.        
  17.  
  18.         protected void setEditTextId(int id) {
  19.  
  20.                 mEditText.setId(id);
  21.  
  22.         }
  23.  
  24.  
Parsed in 0.035 seconds, using GeSHi 1.0.8.4


This allows the EditText to always have a unique ID after the layout is inflated. This is only half the solution, though, because when the Activity is recreated the EditText will get assigned a different random ID. To handle that, I had to override the onSaveInstanceState and onRestoreInstanceState methods to save/restore the ID in a custom SavedState object (code borrowed heavily from TextView):

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.  
  2.         public static class SavedState extends BaseSavedState {
  3.  
  4.                 int textId;
  5.  
  6.                
  7.  
  8.                 SavedState(Parcelable superState) {
  9.  
  10.                         super(superState);
  11.  
  12.                 }
  13.  
  14.                
  15.  
  16.                 @Override
  17.  
  18.                 public void writeToParcel(Parcel out, int flags) {
  19.  
  20.                         super.writeToParcel(out, flags);
  21.  
  22.                        
  23.  
  24.                         out.writeInt(textId);
  25.  
  26.                 }
  27.  
  28.                
  29.  
  30.                 public static final Parcelable.Creator<SavedState> CREATOR
  31.  
  32.                                 = new Parcelable.Creator<SavedState>() {
  33.  
  34.                         public SavedState createFromParcel(Parcel in) {
  35.  
  36.                                 return new SavedState(in);
  37.  
  38.                         }
  39.  
  40.  
  41.  
  42.                         public SavedState[] newArray(int size) {
  43.  
  44.                                 return new SavedState[size];
  45.  
  46.                         }
  47.  
  48.                 };
  49.  
  50.  
  51.  
  52.                 private SavedState(Parcel in) {
  53.  
  54.                         super(in);
  55.  
  56.                         textId = in.readInt();
  57.  
  58.                 }
  59.  
  60.         }
  61.  
  62.  
  63.  
  64.         @Override
  65.  
  66.         public Parcelable onSaveInstanceState() {
  67.  
  68.                 Parcelable superState = super.onSaveInstanceState();
  69.  
  70.  
  71.  
  72.                 SavedState ss = new SavedState(superState);
  73.  
  74.                 ss.textId = mEditText.getId();
  75.  
  76.  
  77.  
  78.                 return ss;
  79.  
  80.         }
  81.  
  82.  
  83.  
  84.         @Override
  85.  
  86.         public void onRestoreInstanceState(Parcelable state) {
  87.  
  88.                 SavedState ss = (SavedState)state;
  89.  
  90.                 super.onRestoreInstanceState(ss.getSuperState());
  91.  
  92.  
  93.  
  94.                 setEditTextId(ss.textId);
  95.  
  96.         }
  97.  
  98.  
Parsed in 0.040 seconds, using GeSHi 1.0.8.4


The only thing not working here is that focus won't get restored to the EditText, which is weird because adb logcat usually shows that the Window fails to find the ID after it's already been set. I don't really care though, this is enough for me. Hopefully this helps somebody, and hopefully this sort of thing will be made easier in future revisions of the API.
divestoclimb
Developer
Developer
 
Posts: 33
Joined: Mon May 11, 2009 7:46 pm

Top

Return to View, Layout & Resource Problems

Who is online

Users browsing this forum: No registered users and 4 guests