Why are the height and width of the content View zero in Robolectric? - android-layout

Here's a failing test:
#RunWith(RobolectricTestRunner.class)
public class FailTest {
#Test
public void heightAndWidth_shouldNotBeZero() {
TestActivity testActivity = Robolectric.buildActivity(TestActivity.class).create().resume().visible().get();
View contentView = testActivity.findViewById(69);
Assertions.assertThat(contentView.getWidth()).isNotZero();
Assertions.assertThat(contentView.getHeight()).isNotZero();
}
private static class TestActivity extends Activity {
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout contentView = new LinearLayout(this);
contentView.setId(69);
contentView.setLayoutParams(new LayoutParams(666, 666));
setContentView(contentView);
}
}
}
As you can see, I'm calling the visible() method on the ActivityController and driving the Activity lifecycle the correct way. Quoting the documentation:
Wait, What's This visible() Nonsense?
Turns out that in a real Android app, the view hierarchy of an
Activity is not attached to the Window until sometime after onCreate()
is called. Until this happens, the Activity's views do not report as
visible. This means you can't click on them (amongst other unexpected
behavior). The Activity's hierarchy is attached to the Window on a
device or emulator after onPostResume() on the Activity. Rather than
make assumptions about when the visibility should be updated,
Robolectric puts the power in the developer's hands when writing
tests.
So when do you call it? Whenever you're interacting with the views
inside the Activity. Methods like Robolectric.clickOn() require that
the view is visible and properly attached in order to function. You
should call visible() after create().
It seems as though I'm doing all I need to do. So why am I getting no height/width?

There is no layout pass in Robolectric, hence the view dimensions are always zero.
https://github.com/robolectric/robolectric/issues/819

Related

How to fix setOnClickListener code that causes crash

I am having difficulty in figuring out what is wrong with my code, My code runs when the onclick listener is not yet implemented but once I implement the onclick listener it crashes.
public class menu extends AppCompatActivity implements View.OnClickListener {
private CardView assess, profile, chatbot, breathing;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.menu);
assess = (CardView) findViewById(R.id.assess);
profile = (CardView) findViewById(R.id.profile);
chatbot = (CardView) findViewById(R.id.chatbot);
breathing = (CardView) findViewById(R.id.breathing);
// assess.setOnClickListener(this);
// profile.setOnClickListener(this);
// chatbot.setOnClickListener(this);
// breathing.setOnClickListener(this);
}
#Override
public void onClick(View v) {
// Intent i;
//
// switch (v.getId()){
//
//
// case R.id.assess :
// i = new Intent(this,depression_assessment.class);
// startActivity(i);
// break;
}
}
//}
When I tried debugging the codes, these lines are the cause of the crash.
// assess.setOnClickListener(this);
// profile.setOnClickListener(this);
// chatbot.setOnClickListener(this);
// breathing.setOnClickListener(this);
It is where the problem is starting because the code works even though the onclick is blank. When I checked the logs it shows this error
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.example.thesis/com.example.thesis.menu}:
java.lang.NullPointerException: Attempt to invoke virtual method 'void
androidx.cardview.widget.CardView.setOnClickListener(android.view.View$OnClickListener)'
on a null object reference
You haven't shown enough code, but this could be due to serveral reasons:
This line of code breathing = (CardView) findViewById(R.id.breathing); will look for a view with id breathing inside your activity's layout, and according to the error it is null, which means it did not find it within the same activity's layout, so make sure your cardView is in this activity's layout. Another possible reason is that you might have duplicate Ids in your xml files, in this case,find the duplicate and rename the Ids.

Overlay toolbar with other toolbar when item is selected in RecyclerView which is inside a fragment

To illustrate what I mean with this, it is similar to WhatsApp, where various options are displayed in the toolbar when a chat is selected.
I have a similar layout, so a MainActivity with Fragments containing RecyclerViews. Now when an item in a RecyclerView is selected I would like to get a similar behaviour as in WhatsApp. The RecyclerViews have an Adapter that implements an OnClickListener.
However, from this Adapter I do not seem to have access to Views from the MainActivity. I tried the following (inside the OnClick method in the Adapter), but it did not work since the view could not be found.
View view = getActivity().findViewById(R.id.toolbar_main_activity);
if( view instanceof Toolbar) {
Toolbar toolbar = (Toolbar) view;
toolbar.setTitle("TestTitle");
}
Does anyone know how to get the intended behavior or have a reference to a tutorial?
UPDATE: for who is also stuck with this and this is still quite confusing, here is how I solved it in my own words
My Fragment contains the Interface by adding the following code to it;
OnItemsSelected mCallBack;
public interface OnItemsSelected {
void onToolbarOptions(String title);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
mCallback = (OnItemsSelected) getActivity();
}
Also I passed 'mCallback' to the adapter like this;
MyAdapter adapter = new MyAdapter(myList, mCallback);
The RecyclerView adapter implements OnClickListener. In the OnClick method I called; 'mCallBack.onToolbarOptions("someTitle");'. And finally I made my MainActivity implement the method; 'implements myFragment.onItemsSelected' and I added the following code to it also;
#Override
public void onToolbarOptions(String title) {
toolbar.setTitle(title);
}
With this, only the title is changed, but from this it is quite easy to make other changes to the toolbar, such as changing the menu items.
Inside your Fragment you make an Interface and a global variable like this:
OnItemsSelected mCallBack;
public interface OnItemsSelected {
public void onToolbarOptions();
}
Then when in your RecyclerView items are selected or clicked you call:
mCallBack.onToolbarOptions();
In your Activity implement the Interface like this plus the method onToolbarOptions():
public static class YourActivityName extends AppCompatActivity
implements YourFragmentName.OnItemsSelected {
public void onToolbarOptions(){
// CHANGE YOUR TOOLBAR HERE
}
//.....OTHER STUFFS IN YOUR ACTIVITY
}

Dialog values with MvvmCross, ActionBarSherlock and DialogFragment

I have a bunch of different libraries trying to work together and I'm pretty close but there's just one issue.
I have created a class called SherlockDialogFragment inheriting from SherlockFragment (rather than using the SherlockListFragment - this is because of the issue with the keyboard that's covered here). Here is my code:
public class SherlockDialogFragment : SherlockFragment
{
public RootElement Root
{
get { return View.FindViewById<LinearDialogScrollView>(Android.Resource.Id.List).Root; }
set { View.FindViewById<LinearDialogScrollView>(Android.Resource.Id.List).Root = value; }
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var ignored = base.OnCreateView(inflater, container, savedInstanceState);
var layout = new LinearLayout(Activity) { Orientation = Orientation.Vertical };
var scroll_view = new LinearDialogScrollView(Activity)
{
Id = Android.Resource.Id.List,
LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.FillParent)
};
layout.AddView(scroll_view);
return layout;
}
}
I then do the regular thing of creating an eventsource class which inherits from this but also uses IMvxEventSourceFragment, then the actual fragment class (which I call MvxSherlockDialogFragment) which inherits the eventsource class, as well as IMvxFragmentView.
That all works fine (and indeed I've created a SherlockDialogActivity the same way and it's fine), however the one issue is when I use this fragment on a screen with tabs (i'm using a class I made similarly to above called MvxSherlockFragmentActivity). When switching to the tab with the dialog, the view appears fine, even with pre-populated data. However the issue is when I switch away from that fragment/tab, and then back to it, the dialog elements all have the same value.
In my particular example, it's a login page with a username and password. When I first go into the fragment, everything is fine. When I go out and back in, the password value is in both the username field and the password field.
I'm sure it's got something to do with the SherlockDialogFragment class - in the SherlockDialogActivity class I also have this bit:
public override void OnContentChanged()
{
base.OnContentChanged();
var list = FindViewById<LinearDialogScrollView>(Android.Resource.Id.List);
if (list == null)
{
throw new RuntimeException("Your content must have a ViewGroup whose id attribute is Android.Resource.Id.List and is of type LinearDialogScrollView");
}
list.AddViews();
}
However this doesn't work in a fragment because there's no OnContentChanged event. Also, another difference is that in the SherlockDialogActivity, the layout is being created ONCE in the OnCreate event - however in the SherlockFragmentActivity I've got it being created each time the fragment is viewed. I know that's probably not the best way, but I tried to do it in OnCreate and save it in a variable and then return that variable in OnCreateView, but android didn't like that...
Ok I feel like an idiot. I was creating/binding on OnViewCreated - however I need to do all my binding in OnStart - I think I was following some (possibly old) sample code from Stuart.
Obviously for a regular activity I'm using OnCreate but this doesn't work on a fragment, because the view is not initialised there - it's initialised at OnCreateView.
So for future reference - do all binding in OnStart!

getText from one layout and (setting it) setText in an other layout

i call this method SolveUpdation (from button- onclickAction Listener) from mainAcitivity with main layout. i use other layout to get value from user and set it as button title in the main layout and that is only instruction that does not works for me
private void SolveUpdation() { //this function call is generated from the main Activity with main layout
setContentView(R.layout.updateappliance); //this is 2nd layout to get values from user and use them as buttonText in the main layout
btnSaveApp = (Button) findViewById(R.id.Bupdatenow);
btnSaveApp.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
mOutEditText = (EditText) findViewById(R.id.edit_text_1);
TextView view1 = (TextView) findViewById(R.id.edit_text_1);
final String TitleApp1 = view1.getText().toString(); //the value is read properly here
// if (App1.length() > 0) {
// byte[] send = App1.getBytes();
// }
btnSaveApp.setText(TitleApp1); //this works fine
startActivity(new Intent(HomeScreen.this, HomeScreen.class));//this the main activity for main layout
setContentView(R.layout.main); //this is the main layout and this instruction works
buttonLED1.setText(TitleApp1); //buttonLED1 (a Togglebutton or can be simple) is defined in main layout and this does not works and this is what i am stuck with
SaveAppNamesToast(TitleApp1); //this is just to toast the value and it works fine.
}});
So plz can any one guide me why this instruction buttonLED1.setText(TitleApp1); does not works ??? Any help will be appreciatable.. thanks
No offense, but the way you write your code is not a good practice.
My advise: Stop calling another setContentView in your Main Activity. You should rather implement all needed Buttons and EditTexts in one layout and set their visiblity to gone or visible depending on which button was clicked.
If you don't wanna do this you should create a second class that handles the input of the user. After pressing the save button you initialize your intent for the main activity and give it via intent.putExtra("KEY", value) the input of the user.
Your Main Activity can receive this value via getIntent().getExtras().getInt("KEY").
By the way: I think your current code doesn't work because of the new Activity you start. Through this everything gets initialized again so the buttonLED1 that you see isn't the same buttonLED1 that gets the text.

Why would MonoTouch not garbage collect my custom UIButton unless I call button.RemoveFromSuperview()?

There seems to be something holding a reference to my custom button, MyButton (which inherits from UIButton), causing it not to be garbage collected unless I remove it from the superview. This, in turn, would cause the view controller that it is on to also not be finalized and collected.
In my example, I have my custom button but I also have a standard UIButton on the view controller which does not need to be removed from the superview in order to be collected. What's the difference? Looks pretty similar to me.
See this code. The irrelevant lines were removed for example's sake. Some things to note about the sample:
-MyButton is pretty empty. Just a constructor and nothing else overridden.
-Imagine MyViewController being on a UINavigationController
-LoadView() just creates the buttons, hooks up an event for each and adds it to the view
-Touching _button would push another MyViewController to the nav controller
-I'm doing some reference cleanup when popping view controllers off the nav controller in ViewDidAppear()
-In CleanUpRefs() you'll see that I have to remove _myButton from superview in order for all the objects to be garbage collected. _button, on the other hand does not need to be removed.
-I'm expecting the entire MyViewController to be collected, including all subviews, when popping from the nav controller but commenting out _myButton.RemoveFromSuperview() stops this from happening.
public class MyViewController : UIViewController
{
private UIButton _button;
private MyButton _myButton;
private MyViewController _nextController;
public override void LoadView()
{
base.LoadView();
_button = UIButton.FromType(UIButtonType.RoundedRect);
_button.TouchUpInside += PushNewController;
View.AddSubview(_button);
_myButton = new MyButton();
_myButton.TouchUpInside += MyButtonTouched;
View.AddSubview(_myButton);
}
private void PushNewController(object sender, EventArgs e)
{
_nextController = new MyViewController();
NavigationController.PushViewController(_nextController, true);
}
private void MyButtonTouched(object sender, EventArgs e)
{
Console.WriteLine("MyButton touched");
}
public void CleanUpRefs()
{
//_button.RemoveFromSuperview();
_myButton.RemoveFromSuperview();
// remove reference from hooking up event handler
_button.TouchUpInside -= PushNewController;
_myButton.TouchUpInside -= MyButtonTouched;
_button = null;
_myButton = null;
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
if(_nextController != null)
{
_nextController.CleanUpRefs();
_nextController = null;
}
}
}
It seems as if there's something different with the fact that MyButton isn't a straight UIButton in that it is inherited. But then again, why would there be an extra reference count to it that's being removed by calling RemoveFromSuperview() especially when there's a UIButton just like it that doesn't need to be removed?
(I apologize for the really bad layout, stackoverflow seems to have problems laying out bullets right above code snippets)
Update: I filed a bug report with the MonoTouch team. You can download the sample project from there if you want to run it. Bug 92.
The reason for not garbage collecting in that scenario is just a bug in MonoTouch.
The upcoming MonoTouch release will contain a fix for this. If you are in a hurry, you can replace your /Developer/MonoTouch/usr/lib/mono/2.1/monotouch.dll with the copy I placed here:
http://tirania.org/tmp/monotouch.dll
I would make a backup, in case I did something wrong in my work-in-progress library.

Resources