Robolectric inflating custom views for tests - android

I'm trying to write some tests for a custom view, but I am having trouble inflating the custom view in my test case. The error I get is
android.view.InflateException: <merge /> can be used only with a valid ViewGroup root and attachToRoot=true
at android.view.LayoutInflater.inflate(LayoutInflater.java:458)
at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
at android.view.LayoutInflater.inflate(LayoutInflater.java:353)
at com.androidas.models.ui.order.MyLocationViewTest.setUp(MyLocationViewTest.java:45)
I even tried making a dummy activity (TestActivity) with a tag in it to try to inflate it
public class MyLocationView extends RelativeLayout {
private TextView mTextEta;
private TextView mTextOnTime;
private Date mMeetTime;
private CalendarManager mCalendarManager;
public MyLocationView(Context context) {
this(context, null);
}
public MyLocationView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyLocationView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater.from(context)
.inflate(R.layout.v_mylocation, this, true);
mTextEta = (TextView) findViewById(R.id.order_statusTracking_txt_myEta);
mTextOnTime = (TextView) findViewById(R.id.order_statusTracking_txt_myDelay);
}
}
layout
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<ImageView
android:id="#+id/order_statusTracking_img_walking"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_walk"
/>
<TextView
android:id="#+id/order_statusTracking_txt_myEta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/spacing_small"
android:layout_toRightOf="#+id/order_statusTracking_img_walking"
android:layout_toEndOf="#+id/order_statusTracking_img_walking"
android:gravity="center_vertical"
tools:text="Arrive in 7min"
style="#style/HeaderText"
/>
<TextView
android:id="#+id/order_statusTracking_txt_myDelay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/order_statusTracking_txt_myEta"
android:layout_below="#+id/order_statusTracking_txt_myEta"
android:layout_marginTop="#dimen/spacing_normal"
tools:text="On Time"
style="#style/InfoText.Black"
/>
</merge>
and Test case:
#RunWith(RobolectricTestRunner.class)
#Config(emulateSdk = 18)
public class MyLocationViewTest {
MyLocationView mLocation;
TextView mTextDelay;
Trip mTrip;
CalendarManager mCalendarManager;
Date meetupTime;
#Before
public void setUp() {
Activity activity = Robolectric.buildActivity(TestActivity.class).create().get();
mLocation = (MyLocationView) LayoutInflater.from(activity).inflate(R.layout.v_mylocation, null);
mTextDelay = (TextView) mLocation.findViewById(R.id.order_statusTracking_txt_myDelay);
}

OK, 1 year later but here is the weird thing that worked for me:
MyCustomView mView = (MyCustomView) LayoutInflater
.from(RuntimeEnvironment.application)
.inflate(R.layout.my_custom_view_layout,
new MyCustomView(RuntimeEnvironment.application),
true);

Related

Android InflateException Custom View

Don't understand why my compound view doesn't want to work.
Caused by: android.view.InflateException: Binary XML file line #186: Error inflating class com.igor.customviews.CompoundEditText
There are constructors. As you see I didn't miss super and pass init() to all of them.
public class CompoundEditText extends RelativeLayout {
private String editTextHint;
private int resDrawableLeft, resDrawableRight, inputType;
private EditTextRegular editText;
private ImageView customDrawableLeft, customDrawableRight;
public CompoundEditText(Context context) {
super(context);
init();
}
public CompoundEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.CompoundEditText,
0, 0);
try {
editTextHint = a.getString(R.styleable.CompoundEditText_hint);
resDrawableLeft = a.getResourceId(R.styleable.CompoundEditText_left_drawable_src, 0);
resDrawableRight = a.getResourceId(R.styleable.CompoundEditText_right_drawable_src, 0);
editText.setHint(editTextHint);
customDrawableLeft.setImageResource(resDrawableLeft);
customDrawableRight.setImageResource(resDrawableRight);
} finally {
a.recycle();
}
}
public CompoundEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
inflate(getContext(), R.layout.compound_edit_text, this);
editText = (EditTextRegular) findViewById(R.id.edit_text);
customDrawableLeft = (ImageView) findViewById(R.id.drawable_left);
customDrawableRight = (ImageView) findViewById(R.id.drawable_right);
}
}
Error is mainly Caused by: android.view.InflateException: Binary XML file line #2: Error inflating class <unknown> as far, as I understand and it refers to this line: inflate(getContext(), R.layout.compound_edit_text, this);
I can't figure out why it doesn't help, because the same way I made another compound view and it works fine.
Hope your fresh look on the code will help to solve the problem. Thank you.
EDIT
Just try to add it to layout.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/main_bg_color"
android:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin">
...
<com.igor.customviews.CompoundEditText
android:id="#+id/test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>

Add hint text to programatically added TextInputLayout

Hi I have created custom component for TextInputLayout. I want to set hint text to TextInputLayout. I am adding custom class which extends TextInputLayout into my root layout. I tried it in following way:
<android.support.design.widget.TextInputLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="#color/gray"
android:theme="#style/editTextSelectedTheme"
android:layout_marginTop="#dimen/margin_10"
>
<EditText
android:id="#+id/form_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
public class FormEditText extends TextInputLayout
{
TextInputLayout view;
public FormEditText(Context context) {
super(context);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = (TextInputLayout)inflater.inflate(R.layout.form_edit_text, this, true);
initUI();
}
public FormEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FormEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
And finally in my fragment:
FormEditText gadgetName = new FormEditText(getContext());
gadgetName.setHint(getResources().getString(R.string.gadget_name_hint));
formContainer.addView(gadgetName);
Above code adds new TextInputLayout in my view but not adding hint text for it. Am I doing anything wrong? Need Some help. Thank you.
I did few changes as per comment below.
public class FormEditText extends LinearLayout
view = inflater.inflate(R.layout.form_edit_text, this, true);
Now it si showing hint text as per required.But now it is not showing my edit text value.It is not taking any input value.

android - use default preference height in custom preference

I have a custom preference:
public class ButtonPreference extends Preference {
public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ButtonPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected View onCreateView(ViewGroup parent) {
super.onCreateView(parent);
LayoutInflater li = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return li.inflate(R.layout.button_prederence, parent, false);
}
}
and xml button_prederence.xml:
<?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">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click me" />
</LinearLayout>
I want all my preferences to be in the same height, so I need to get the default preference height and apply it to the ButtonPreference.
How can I do that?
I get this problem too.
After some research, I found this workaround method. Try to add this line in your LinearLayout.
android:minHeight="?android:listPreferredItemHeight"
or you can check the original xml file for Preference at
sdk\platforms\android-24\data\res\layout\preferences.xml

The inflate method for my binding is not found (using Android, Data Binding.)

I'm using data binding to bind the layouts in my Android app.
I have set up my layout ( my_custom.xml ) and the binding class is generated (MyCustomBinding), but Android Studio does not seem to find the .inflate(...) method of the Binding class right away, marking it as an error ( red text!).
The code seems to be correct though, since it compiles and builds just fine into an APK.
How do I get Android Studio to update correctly ?
Code example:
This is my custom View code:
public class MyCustomView extends FrameLayout {
public MyCustomView(Context context) {
this(context, null, 0);
}
public MyCustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
MyCustomBinding binding = MyCustomBinding.inflate(inflater, this, true);
binding.aButton.setText("Whatever");
}
}
layout is defined as:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="#+id/a_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click me!"
android:padding="10dp"
android:background="#000"
android:textColor="#fff"
android:layout_gravity="center"
/>
</FrameLayout>
</layout>
And here's the issue: (highlighted red)
Something is not completing in Android Studio because you haven't actually implemented data binding.
Once you add a variable to your layout's data element, the inflate method will be found as you expect. That said, you're really not getting the benefit of databinding by setting the value of the text field directly through the binding. You should instead be setting a View Model in your binding, and then let the binding update the views accordingly. For example:
create a View Model:
public class MyViewModel {
public final ObservableField<String> name;
public MyViewModel(String name) {
this.name = new ObservableField<>(name);
}
}
and use it in your layout
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="model" type="com.victoriaschocolates.conceirge.MyViewModel" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{model.name}"
android:padding="10dp"
android:background="#000"
android:textColor="#fff"
android:layout_gravity="center"
/>
</FrameLayout>
</layout>
(note the variable declared in the data element, and how it is referenced in the TextView's text attribute)
then bind the two in your custom view:
public class MyCustomView extends FrameLayout {
public MyCustomView(Context context) {
this(context, null, 0);
}
public MyCustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
MyCustomBinding binding = MyCustomBinding.inflate(inflater, this, true);
MyViewModel model = new MyViewModel("Whatever");
binding.setModel(model);
}
}
Of course, it would probably be better still to have the data passed in through a setter in the custom view class, or even passed in from the container view (see http://developer.android.com/tools/data-binding/guide.html#includes)
You can try setting a custom class name to your Binding Layout, and referencing it in your custom view to make it clear which layout you using:
<layout>
<data class="MyCustomBinding">
</data>
</layout>
If this does not work, use DataBindingUtil instead of MyCustomBinding, which returns the base DataBinding class and has a inflate() method:
MyCustomBinding binding = DataBindingUtil.inflate(inflater, this, true);
From the docs:
Sometimes the binding cannot be known in advance. In such cases, the binding can be created using the DataBindingUtil class:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
this is updated answer based on latest databinding and androidx, i come here on this question to solve my own problem, after watching above answers i develop by own working code snippet. hope this helps
public class CustomActionBar1 extends RelativeLayout {
public MutableLiveData<String> title = new MutableLiveData<>("Sample text");
CustomActionBar1Binding binding;
public CustomActionBar1(Context context) {
super(context);
init(context, this);
}
public CustomActionBar1(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, this);
}
public CustomActionBar1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, this);
}
public CustomActionBar1(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, this);
}
private void init(Context context, ViewGroup viewGroup) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
binding = DataBindingUtil.inflate(inflater, R.layout.custom_action_bar_1, viewGroup, true);
}
// helper to change title
public void changeTitle(String title) {
if (title != null)
this.title.setValue(title);
}
public void setTitleVisibility(Boolean visibile){
binding.titleText.setVisibility(visibile ? View.VISIBLE : View.INVISIBLE);
}
}
Xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="customActionBar1"
type="com.actionBar.CustomActionBar1" />
</data>
<RelativeLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:background="#color/yellow"
android:layout_height="#dimen/action_bar_height"
android:id="#+id/main_header_relative">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="#{customActionBar1.title}"
android:layout_centerInParent="true"
android:gravity="center"
android:id="#+id/titleText"
android:visibility="visible"
android:textColor="#color/black" />
</RelativeLayout>
</layout>
You dont need that type of stuffs, DataBinding includes DataBindingUtils class here we go.
public MyCustomView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
MyCustomBinding binding = DataBindingUtil.inflate(inflater, this, true);
binding.aButton.setText("Whatever");
}

NumberFormatException when using styleable attrs

I created a custom view for Android which renders two inner views to store a key and a value in two columns. The class looks like this:
public class KeyValueRow extends RelativeLayout {
protected TextView mLabelTextView;
protected TextView mValueTextView;
public KeyValueRow(final Context context) {
super(context);
init(context, null, 0);
}
public KeyValueRow(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public KeyValueRow(final Context context,
final AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
protected void init(final Context context,
final AttributeSet attrs, int defStyle) {
View layout = ((LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.key_value_row, this, true); // line 46
mLabelTextView = (TextView) layout.findViewById(
R.id.key_value_row_label);
mValueTextView = (TextView) layout.findViewById(
R.id.key_value_row_value);
}
public void setLabelText(final String text) {
mLabelTextView.setText(text);
}
public void setValueText(final String text) {
mValueTextView.setText(text);
}
}
The associated layout file layout/key_value_row.xml looks like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="1.0">
<TextView
android:id="#+id/key_value_row_label"
style="#style/KeyValueLabel"
android:layout_weight=".55"
tools:text="Label"/>
<TextView
android:id="#+id/key_value_row_value"
style="#style/KeyValueValue"
android:layout_weight=".45"
tools:text="Value"/>
</LinearLayout>
This can be used as follows in a layout:
<com.example.myapp.customview.KeyValueRow
android:id="#+id/foobar"
style="#style/KeyValueRow" />
Until here everything works fine!
The challenge
Now, I want to allow custom settings for the layout_weight attributes of both inner TextViews. Therefore, I prepared the attributes in values/attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="KeyValueRow">
<attr name="label_layout_weight" format="float" />
<attr name="value_layout_weight" format="float" />
</declare-styleable>
</resources>
First question would be: is float the correct format for layout_weight?
Next, I would apply them in the layout file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="1.0">
<TextView
android:id="#+id/key_value_row_label"
style="#style/KeyValueLabel"
android:layout_weight="?label_layout_weight"
tools:text="Label"/>
<TextView
android:id="#+id/key_value_row_value"
style="#style/KeyValueValue"
android:layout_weight="?value_layout_weight"
tools:text="Value"/>
</LinearLayout>
Then they can be used in the example:
<com.example.myapp.customview.KeyValueRow
android:id="#+id/foobar"
style="#style/KeyValueRow"
custom:label_layout_weight=".25"
custom:value_layout_weight=".75" />
When I run this implementation the following exception is thrown:
Caused by: java.lang.NumberFormatException: Invalid float: "?2130772062"
at java.lang.StringToReal.invalidReal(StringToReal.java:63)
at java.lang.StringToReal.parseFloat(StringToReal.java:310)
at java.lang.Float.parseFloat(Float.java:300)
at android.content.res.TypedArray.getFloat(TypedArray.java:288)
at android.widget.LinearLayout$LayoutParams.<init>(LinearLayout.java:1835)
at android.widget.LinearLayout.generateLayoutParams(LinearLayout.java:1743)
at android.widget.LinearLayout.generateLayoutParams(LinearLayout.java:58)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:757)
at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
at com.example.myapp.customview.KeyValueRow.init(KeyValueRow.java:46)
at com.example.myapp.customview.KeyValueRow.<init>(KeyValueRow.java:28)
... 33 more
You can use
private void applyAttributes(Context context, AttributeSet attrs) {
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs, R.styleable.KeyValueRow, 0, 0);
try {
labelWeight = a.getFloat(
R.styleable.KeyValueRow_label_layout_weight, 0.55f);
valueWeight = a.getFloat(
R.styleable.KeyValueRow_value_layout_weight, 0.45f);
} finally {
a.recycle();
}
}
and after this you can apply this via:
mLabelTextView = (TextView) layout.findViewById(R.id.key_value_row_label);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, labelWeight);
mLabelTextView.setLayoutParams(layoutParams);
To get it running replace android:layout_weight="?label_layout_weight" with some default value such as android:layout_weight="0.5" in layout/key_value_row.xml. It will be overwritten.

Categories

Resources