Robolectric shadow activity.findViewById returns a null.
This is fairly simple exercise. All the tuts are similar to this, so should be simple. Wondering if anyone else is experiencing this. Code is below.
#Test
public void shouldFindViewsById(){
activity = Robolectric.buildActivity(DeckardActivity.class).create().visible().get();
assertThat(activity).isNotNull();
TextView textView = (TextView) activity.findViewById(R.id.textView);
assertThat(textView).isNotNull();
Button button = (Button) activity.findViewById(R.id.button);
assertThat(button).isNotNull();
EditText editText = (EditText) activity.findViewById(R.id.editText);
assertThat(editText).isNotNull();
}
I'm expecting you use the https://github.com/robolectric/deckard-gradle example based on your DeckardActivity?
The original layout only contains a view with id R.id.text
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello Espresso!"
/>
</LinearLayout>
check that you import the correct example R class import com.example.R; and try just following test snippet:
activity = Robolectric.buildActivity(DeckardActivity.class).create().visible().get();
assertThat(activity).isNotNull();
TextView textView = (TextView) activity.findViewById(R.id.text);
assertThat(textView).isNotNull();
if you like here is an extended example for robolectric tests https://github.com/nenick/android-gradle-template
Related
I have a layout where I include the same sub-layout multiple times, each one with a different role:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<include
android:id="#+id/settings_eco_seekarc"
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="#layout/settings_arc" />
<include
android:id="#+id/settings_comfort_seekarc"
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="#layout/settings_arc" />
</LinearLayout>
It works if I find the views in this way:
View eco = root.findViewById(R.id.settings_eco_seekarc);
mEcoSeekArc = (SeekArc) eco.findViewById(R.id.settings_seekarc);
mEcoLeaf = (ImageView) eco.findViewById(R.id.settings_leaf_img);
mEcoText = (TextView) eco.findViewById(R.id.settings_text);
View cmf = root.findViewById(R.id.settings_comfort_seekarc);
mComfortSeekArc = (SeekArc) cmf.findViewById(R.id.settings_seekarc);
mComfortLeaf = (ImageView) cmf.findViewById(R.id.settings_leaf_img);
mComfortText = (TextView) cmf.findViewById(R.id.settings_text);
I am introducing ButterKnife in my project now, and I hoped I could simply annotate each view (the following obviously doesn't work, and I can see why) and inject them later using each included layout root:
#InjectView(R.id.settings_seekarc)
SeekArc mEcoSeekArc;
#InjectView(R.id.settings_leaf_img)
ImageView mEcoLeaf;
#InjectView(R.id.settings_text)
TextView mEcoText;
#InjectView(R.id.settings_seekarc)
SeekArc mComfortSeekArc;
#InjectView(R.id.settings_leaf_img)
ImageView mComfortLeaf;
#InjectView(R.id.settings_text)
TextView mComfortText;
//then later...
View eco = root.findViewById(R.id.settings_eco_seekarc);
ButterKnife.inject(this, eco);
View cmf = root.findViewById(R.id.settings_comfort_seekarc);
ButterKnife.inject(this, cmf);
Doing it in this way, though, leads me to this error at the second injection:
Error:(81, 13) error: Attempt to use #InjectView for an already
injected ID 2131493185 on 'mEcoSeekArc'.
My question is: is there a way to use ButterKnife in this scenario?
you could use some type of sub-container like this:
public static class SettingsArcLayout {
#InjectView(R.id.settings_text) public TextView mEcoText;
#InjectView(R.id.settings_leaf_img) public ImageView mComfortLeaf;
// etc...
}
then you have it
SettingsArcLayout layout1 = new SettingsArcLayout();
SettingsArcLayout layout2 = new SettingsArcLayout();
and then:
ButterKnife.inject(this); // inject eco and cmf
ButterKnife.inject(layout1, eco);
ButterKnife.inject(layout2, cmf);
and throught this class you can use:
layout1.mEcoText.setText(... etc
The idea of my answer is the same as Budius proposed, I found it in a related issue on ButterKnife's github repo. Original Author is TomazMartins
The MainActivity:
public MainActivity extends AppCompatActivity {
// 1. First, we declare the layout that was included as a View objects.
#BindView(R.id.layout_1) View layout_1;
#BindView(R.id.layout_2) View layout_2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 2. In here, we bind the included layouts
ButterKnife.bind(this);
// 4. Then, we create objects of the type of the IncludedLayout.
// In this example the layout reuse the same layout twice, so, there are two
// IncludedLayouts.
IncludedLayout includedLayout_1 = new IncludedLayout();
IncludedLayout includedLayout_2 = new IncludedLayout();
// 5. We bind the elements of the included layouts.
ButerKnife.bind(includedLayout_1, layout_1);
ButerKnife.bind(includedLayout_2, layout_2);
// 6. And, finally, we use them.
includedLayout_1.displayed_text.setText("Hello");
includedLayout_2.displayed_text.setText("Hey!");
}
// 3. We create a static class that will be an container of the elements
// of the included layout. In here we declare the components that
// hold this. In this example, there is only one TextView.
static class IncludedLayout {
#BindView(R.id.displayed_text) TextView displayed_text;
}
}
The XML of the MainAcitvity:
<!--...-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<include android:id="#+id/layout_1" layout="#layout/included_layout" />
<include android:id="#+id/layout_2" layout="#layout/included_layout" />
</LinearLayout>
<!--...-->
The XML of the Included Layout:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/displayed_text"/>
</LinearLayout>
That's it!
When i ran it, although the id was the same, because I reused it, the text in the TextView was different.
Please bear with me as I am new to the use of Views and Layouts.
I am trying to create an application in which the user can enter a text field, press a button and have a new text field appear and they can continue adding text fields in this way.
My solution was to have the top level be a scrollview and then have a relative view as a child within that(this way I can then programatically insert more editviews in my code with the OnClick() listener.
I have seen and read a couple of other posts pertaining to relative views but it seems there is still something that I am missing. I have tried
Here is the xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_create_accounts"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.nic.mybudget.CreateAccountsActivity">
<RelativeLayout
android:id="#+id/activity_create_accounts_relativeLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/activity_name"
android:inputType="textAutoComplete"
android:layout_alignParentTop="true"
android:id="#+id/activity_createAccounts_relativeLayout_activityName"/>
<Button
android:text="+"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_below="#+id/activity_createAccounts_relativeLayout_activityName"
android:id="#+id/activity_create_accounts_relativeLayout_activityName_addButton" />
<Button
android:text="Save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_below="#+id/activity_create_accounts_relativeLayout_activityName_addButton"
android:layout_centerHorizontal="true"
android:id="#+id/activity_create_accounts_relativeLayout_activityName_saveButton" />
</RelativeLayout>
And here is the Code where I try to add new editviews.
public class CreateAccountsActivity extends Activity {
static private final String TAG = "MAIN-Activity";
int numAccounts = 0;
int lastAccountID;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_accounts);
final RelativeLayout Relative = (RelativeLayout) findViewById(R.id.activity_create_accounts_relativeLayout);
final TextView oldAccount = (TextView) findViewById(R.id.activity_createAccounts_relativeLayout_activityName);
final TextView newAccount = new TextView(this);
final Button addNewAccountButton = (Button) findViewById(R.id.activity_create_accounts_relativeLayout_activityName_addButton);
addNewAccountButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.i(TAG, "addNewAccountOnClick");
numAccounts = numAccounts+1;
int newAccountID = oldAccount.getId() + numAccounts;
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
rlp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
newAccount.setLayoutParams(rlp);
newAccount.setHint("Hint" );
newAccount.setId(newAccountID);
Relative.addView(newAccount);
RelativeLayout.LayoutParams blp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
blp.addRule(RelativeLayout.BELOW, newAccountID-1);
addNewAccountButton.setLayoutParams(blp);
}
});
}
}
As you can see what I am trying (and failing) to do is add the new edit view at the top of the page and simply push everything else down the page. What am I getting wrong here with the relative layout?
Any help is appreciated.
First thing View with id activity_createAccounts_relativeLayout_activityName is EditText and you are casting it with TextView so that is wrong cast it to EditText.
And to your actual problem:
You are using same EditText instance with variable newAccount and adding it again in relative layout if you want to add one more EditText in relative layout you have to initialise EditText inside onclicklistener.
Just add one line newAccount= new EditText(context)in your onclicklistener code before line numAccounts = numAccounts+1;
Happy Coding !
I have a layout where I include the same sub-layout multiple times, each one with a different role:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<include
android:id="#+id/settings_eco_seekarc"
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="#layout/settings_arc" />
<include
android:id="#+id/settings_comfort_seekarc"
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="#layout/settings_arc" />
</LinearLayout>
It works if I find the views in this way:
View eco = root.findViewById(R.id.settings_eco_seekarc);
mEcoSeekArc = (SeekArc) eco.findViewById(R.id.settings_seekarc);
mEcoLeaf = (ImageView) eco.findViewById(R.id.settings_leaf_img);
mEcoText = (TextView) eco.findViewById(R.id.settings_text);
View cmf = root.findViewById(R.id.settings_comfort_seekarc);
mComfortSeekArc = (SeekArc) cmf.findViewById(R.id.settings_seekarc);
mComfortLeaf = (ImageView) cmf.findViewById(R.id.settings_leaf_img);
mComfortText = (TextView) cmf.findViewById(R.id.settings_text);
I am introducing ButterKnife in my project now, and I hoped I could simply annotate each view (the following obviously doesn't work, and I can see why) and inject them later using each included layout root:
#InjectView(R.id.settings_seekarc)
SeekArc mEcoSeekArc;
#InjectView(R.id.settings_leaf_img)
ImageView mEcoLeaf;
#InjectView(R.id.settings_text)
TextView mEcoText;
#InjectView(R.id.settings_seekarc)
SeekArc mComfortSeekArc;
#InjectView(R.id.settings_leaf_img)
ImageView mComfortLeaf;
#InjectView(R.id.settings_text)
TextView mComfortText;
//then later...
View eco = root.findViewById(R.id.settings_eco_seekarc);
ButterKnife.inject(this, eco);
View cmf = root.findViewById(R.id.settings_comfort_seekarc);
ButterKnife.inject(this, cmf);
Doing it in this way, though, leads me to this error at the second injection:
Error:(81, 13) error: Attempt to use #InjectView for an already
injected ID 2131493185 on 'mEcoSeekArc'.
My question is: is there a way to use ButterKnife in this scenario?
you could use some type of sub-container like this:
public static class SettingsArcLayout {
#InjectView(R.id.settings_text) public TextView mEcoText;
#InjectView(R.id.settings_leaf_img) public ImageView mComfortLeaf;
// etc...
}
then you have it
SettingsArcLayout layout1 = new SettingsArcLayout();
SettingsArcLayout layout2 = new SettingsArcLayout();
and then:
ButterKnife.inject(this); // inject eco and cmf
ButterKnife.inject(layout1, eco);
ButterKnife.inject(layout2, cmf);
and throught this class you can use:
layout1.mEcoText.setText(... etc
The idea of my answer is the same as Budius proposed, I found it in a related issue on ButterKnife's github repo. Original Author is TomazMartins
The MainActivity:
public MainActivity extends AppCompatActivity {
// 1. First, we declare the layout that was included as a View objects.
#BindView(R.id.layout_1) View layout_1;
#BindView(R.id.layout_2) View layout_2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 2. In here, we bind the included layouts
ButterKnife.bind(this);
// 4. Then, we create objects of the type of the IncludedLayout.
// In this example the layout reuse the same layout twice, so, there are two
// IncludedLayouts.
IncludedLayout includedLayout_1 = new IncludedLayout();
IncludedLayout includedLayout_2 = new IncludedLayout();
// 5. We bind the elements of the included layouts.
ButerKnife.bind(includedLayout_1, layout_1);
ButerKnife.bind(includedLayout_2, layout_2);
// 6. And, finally, we use them.
includedLayout_1.displayed_text.setText("Hello");
includedLayout_2.displayed_text.setText("Hey!");
}
// 3. We create a static class that will be an container of the elements
// of the included layout. In here we declare the components that
// hold this. In this example, there is only one TextView.
static class IncludedLayout {
#BindView(R.id.displayed_text) TextView displayed_text;
}
}
The XML of the MainAcitvity:
<!--...-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<include android:id="#+id/layout_1" layout="#layout/included_layout" />
<include android:id="#+id/layout_2" layout="#layout/included_layout" />
</LinearLayout>
<!--...-->
The XML of the Included Layout:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/displayed_text"/>
</LinearLayout>
That's it!
When i ran it, although the id was the same, because I reused it, the text in the TextView was different.
so I have this getter function in a Class called ItemEnergyGreen:
public TextView getTextView(FrameLayout fl) {
textView = (TextView) fl.findViewById(R.id.textview_energyOrange_quantity);
return textView;
}
And I want now to call this methode from another Class:
//Item Energy Green
itemEnergyGreenFrameLayout = (FrameLayout) inflater.inflate(R.layout.item_energy_green_layout, null, false);
itemEnergyGreenQuantityTextView = itemEnergyGreen.getTextView(itemEnergyGreenFrameLayout);
btn_ItemEnergyGreen = (ImageButton) itemEnergyGreenFrameLayout.findViewById(R.id.btn_energyGreenItem_use);
btn_ItemEnergyGreen.setOnClickListener(this);
The function call is in Line 2, I am passing the frameLayout to the getter Function but textView in my ItemEnergyGreen class is always NULL.
The error occurs when i want to call setText on my view:
public void itemEnergyGreenUpdateNumber() {
int quantity = itemEnergyGreen.getQuantity();
String qstring = String.valueOf(quantity);
itemEnergyGreenQuantityTextView.setText(qstring);
}
This is the XML-FrameLayout that is get inflated:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/ItemEnergyGreenFrameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageButton
android:id="#+id/btn_energyGreenItem_use"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:src="#drawable/item_energydrink_green" >
</ImageButton>
<TextView
android:id="#+id/textview_energyGreen_quantity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#F00"
android:clickable="false"
android:padding="2dp"
android:textColor="#FFF"
android:textStyle="bold" >
</TextView>
</FrameLayout>
Why is that?
ps. At the point where the getter function is called, the itemEnergyGreen Object is already initialized.
Thank you for your answers, and sorry if that is a dump question but i´m going crazy about this.
textView in my ItemEnergyGreen class is always NULL.
findViewById looks for a view in the current view hierarchy. Probably TextView is not a child of FrameLayout. So initialization fails leading to NullPointerException.
The other reason could be that you referenced wrong id.
Edit:
Id referenced in wrong
<TextView
android:id="#+id/textview_energyGreen_quantity"
Change this
textView = (TextView) fl.findViewById(R.id.textview_energyOrange_quantity);
to
textView = (TextView) fl.findViewById(R.id.textview_energyGreen_quantity);
Try this
private View mView;
mView =inflater.inflate(R.layout.item_energy_green_layout, null, false);
itemEnergyGreenQuantityTextView = itemEnergyGreen.getTextView(mView);
I am having problem in starting a new activity on clicking a button, here's my code:
package test.project;
import android.app.Activity;
import android.os.Bundle;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
public class TestActivity extends Activity implements OnClickListener {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
View aboutButton = findViewById(R.id.about_content);
aboutButton.setOnClickListener(this);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.about_content:
Intent i = new Intent(this, testit.class);
startActivity(i);
break;
// More buttons go here (if any) ...
}
}
}
Can anyone please help me correct this error
Error Line
aboutButton.setOnClickListener(this);
Main.xml file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/hello" />
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click here" android:layout_gravity="center"
android:text="Click here" android:layout_gravity="center" android:layout_marginTop="30dip"/>
</LinearLayout>
XML file containing about_content is
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip" >
<TextView
android:id="#+id/about_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/about_text" />
</ScrollView>
about_content is already defined here
<TextView
android:id="#+id/about_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/about_text" />
Well, you didn't post your logcat output, but since this is such a common beginner's mistake, I'm going to take a wild guess and say that you are probably getting a NullPointerException.
Your call to findViewById is probably returning null, which means that the system could not find the view associated with the id given by R.id.about_content. I would double check your XML layout for typos.
Odds are you don't have anything with the id about_content in main.xml, which will create a NullPointerException.
Also, if aboutButton is supposed to be a traditional Button, then you should use this:
Button aboutButton = (Button) findViewById(R.id.about_content);
Addition
Since aboutButton is a TextView, use this:
TextView aboutButton = (TextView) findViewById(R.id.about_content);
but this TextView must be in the layout passed to setContentView() or findViewById() will return null.
That is because the "main.xml" which you have set your content view ... does not contain the about_content TextView, its in the other xml which you have posted ...
Note: You can access only those R.id's which are present in your setContentView(R.layout.yourlayout) xml ...
you make setContentView(R.layout.main); but main.xml does not include View have id = R.id.about_content. If you raplace by findViewById(R.id.button1); It will work.
This is the solution for
Button aboutButton = (Button)findViewById(R.id.about_content);
And dont forget to add testit Activity in Android Manifest