create Button instance throws NullPointerException in AnimatorSet.clone - android

I have an issue creating a Button programmatically. The button is supposed to be inserted in a pre-existing layout.
And since I need the dimensions of a specific container I created a global layout listener for that container and in the onGlobalLayout callback i check for a valid size and then instantiate a new Button.
The context used is the context from the container.
final View container = activity.findViewById(...);
container.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {
if (container.getWidth()>0 && container.getHeight()>0) {
Button button = new Button(container.getContext());
}
}
});
However, in rare cases - specifically when switching from one activity to another - the Button instanciation fails with a NullPointerException in the Android framework code.
java.lang.NullPointerException: Attempt to read from field 'android.animation.Animator android.animation.AnimatorSet$Node.mAnimation' on a null object reference
at android.animation.AnimatorSet.clone(AnimatorSet.java:725)
at android.animation.AnimatorSet.clone(AnimatorSet.java:682)
at android.animation.StateListAnimator.clone(StateListAnimator.java:148)
at android.animation.StateListAnimator$StateListAnimatorConstantState.newInstance(StateListAnimator.java:328)
at android.animation.StateListAnimator$StateListAnimatorConstantState.newInstance(StateListAnimator.java:327)
at android.content.res.ConstantState.newInstance(ConstantState.java:53)
at android.content.res.ConstantState.newInstance(ConstantState.java:61)
at android.content.res.ConfigurationBoundResourceCache.getInstance(ConfigurationBoundResourceCache.java:40)
at android.animation.AnimatorInflater.loadStateListAnimator(AnimatorInflater.java:163)
at android.view.View.<init>(View.java:4815)
at android.widget.TextView.<init>(TextView.java:995)
at android.widget.Button.<init>(Button.java:113)
at android.widget.Button.<init>(Button.java:106)
at android.widget.Button.<init>(Button.java:102)
at android.widget.Button.<init>(Button.java:98)
My assumption is that somehow the Context ist not valid any more but I can't put my finger on it..
I do remove the listener when the activity gets deactivated.
Any ideas?

As docs says OnGlobalLayoutListener
Interface definition for a callback to be invoked when the global layout state or the visibility of views within the view tree changes.
So when UI is destroing, you are getting "dying" View that causes NPE. You can try to unregister listener in onStop() to prevent that. Or if you need just to handle fully created View use
container.post(new Runnable() {
#Override
public void run() {
Button button = new Button(container.getContext());
}
});

The only solution I've found so far is to not create the button by calling
Button button = new Button(container.getContext());
but by creating a small layout xml file only containing the button and then instantiate the button like so:
LayoutInflater.from(context).inflate(R.layout.button, null)

Related

Shared element activity transition on android 5

I wanted to setup a shared element transition when going from one Activity to another.
The first Activity has a RecyclerView with items. When an item is clicked that item should animate to the new activity.
So i've set a
android:transitionName="item" on both the final activity views, as wel as the recycler-view item views.
I'm also using this code when going to the next activity:
this.startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, itemView, "boomrang_item").toBundle());
When clicking an item, it transitions properly and the new view is shown. It is really nice.
However when i click the back button. Sometimes it works fine, but most of the time my activity crashes with the following stacktrace:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.ViewGroup.transformMatrixToGlobal(android.graphics.Matrix)' on a null object reference
at android.view.GhostView.calculateMatrix(GhostView.java:95)
at android.app.ActivityTransitionCoordinator$GhostViewListeners.onPreDraw(ActivityTransitionCoordinator.java:845)
at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:847)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1956)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1054)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5779)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
at android.view.Choreographer.doCallbacks(Choreographer.java:580)
at android.view.Choreographer.doFrame(Choreographer.java:550)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5221)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
What am i doing wrong?
It looks like a bug in android 5
I encounter the same issue, and notice the crash happens if the original shared element is no longer visible on the previous screen when you go back (probably it is the last element on screen in portrait, but once switched to landscape it's no longer visible), and thus the transition has nowhere to put back the shared element.
My workaround is to remove the return transition (in the 2nd activity) if the screen has been rotated before going back, but I'm sure there must be a better way to handle this:
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mOrientationChanged = !mOrientationChanged;
}
#Override
public void supportFinishAfterTransition() {
if (mOrientationChanged) {
/**
* if orientation changed, finishing activity with shared element
* transition may cause NPE if the original element is not visible in the returned
* activity due to new orientation, we just finish without transition here
*/
finish();
} else {
super.supportFinishAfterTransition();
}
}
If you're using Proguard then try adding this into your rules file. I had the same issue and it appears to work?
-keep public class android.app.ActivityTransitionCoordinator
Try removing any merge xml tags that you might have on the final activity's view. I have noticed that transitioning to a view, that contains a merge tag, in which the transitioning element is a direct child of the merge tag, will cause this error, but should I replace the merge tag with a different container like CardView, the animation works just fine. Also make sure that there is a 1:1 relationship between the transitionNames in the views.
UPDATE:
I experienced this issue once more when doing an activity transition, clicking the back button to return to the initial activity, and then trying the transition again. I was accessing the direct parent of the 'transition component', (A RelativeLayout) by id, with a findViewById() call, and then calling removeAllViews(). I ended up changing the code to call 'removeAllViews()' on a greater ancestor than the parent, also removed a tag from the element that was to take the place of the 'transition component' after page load. This alleviated my issue.
Make sure the View you are Transitioning to in the Second Activity is not the root layout.
You can just wrap it in a FrameLayout with a transparent windowBackground.
I had this same issue, for me it was being caused by the recyclerview executing updates after/during the first exit transition. I think the shared element view was then sometimes getting recycled, meaning it would no longer be available for the transition animation, hence the crash (normally on the return transition but sometimes on the exit transition). I solved it by blocking updates if the activity is paused (used an isRunning flag) - note it was pausing but not stopping as it was still visible in the background. Additionally I blocked the update process if the transition was running. I found it enough to listen to this callback:
Transition sharedElementExitTransition = getWindow().getSharedElementExitTransition();
if (sharedElementExitTransition != null) {
sharedElementExitTransition.addListener(.....);
}
As a final measure, although i'm not sure if this made a difference, I also did recyclerView.setLayoutFrozen(true) / recyclerView.setLayoutFrozen(false) in the onTransitionStart / onTransitionEnd.
Be sure the "itemView" you are passing in the transition is the view clicked (received on your onClick() callback)
I have faced the same issue, actually I used firebase and I have list of information and when user tap it will call detailActivity with sharedAnimation in this activity I was updating it as seen using firebase so firebase event updating the list item as seen, in this case this problem is invoking because recycler view that screen layout was getting effected.
and it invoke an exception because that transition id which one we have passed it was no more, so I solve this issue using this method.
onPause() I have frozen the layout and onResume() set it as false;
#Override
public void onPause() {
super.onPause();
mRecycler.setLayoutFrozen(true);
}
#Override
public void onResume() {
super.onResume();
mRecycler.setLayoutFrozen(false);
}
And it's working.
What I came up with is to avoid transitioning back to Activity with RecyclerView, or changing back transition with something else.
Disable all return transitions:
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public void finishAfterTransition() {
finish();
}
Or, if you want to disable only shared elements return transition, and be able to set your own return transition:
// Track if finishAfterTransition() was called
private boolean mFinishingAfterTransition;
#Override
protected void onCreate(#Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFinishingAfterTransition = false;
}
public boolean isFinishingAfterTransition() {
return mFinishingAfterTransition;
}
#Override
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void finishAfterTransition() {
mFinishingAfterTransition = true;
super.finishAfterTransition();
}
public void clearSharedElementsOnReturn() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TransitionUtilsLollipop.clearSharedElementsOnReturn(this);
}
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static final class TransitionUtilsLollipop {
private TransitionUtilsLollipop() {
throw new UnsupportedOperationException();
}
static void clearSharedElementsOnReturn(#NonNull final BaseActivity activity) {
activity.setEnterSharedElementCallback(new SharedElementCallback() {
#Override
public void onMapSharedElements(final List<String> names,
final Map<String, View> sharedElements) {
super.onMapSharedElements(names, sharedElements);
if (activity.isFinishingAfterTransition()) {
names.clear();
sharedElements.clear();
}
}
});
}
With that implemented in base activity, you can easily use it in onCreate()
#Override
protected void onCreate(#Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
clearSharedElementsOnReturn(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// set your own transition
getWindow().setReturnTransition(new VerticalGateTransition());
}
}
I had this same error, mine was caused by the same reasoning behind hidro's answer but was caused by the keyboard hiding the shared element that the transition was going back to.
My workaround was to programmatically close the keyboard right before finishing the activity so the shared element on the previous activity isn't obscured.
View view = this.getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
supportFinishAfterTransition();
As #Fabio Rocha said, make sure that the itemView is retrieved from the ViewHolder.
You can get the ViewHolder by position via
mRecyclerView.findViewHolderForAdapterPosition(position);
The reason for this is actually quite simple:
When you Navigate back to the parent Activity or Fragment, the View is not there yet (could be for many reasons).
So, what you want to do is to postpone the Enter Transition until the View is available.
My work around is to call the following function in onCreate() in my Fragment (but works in Activity too):
private void checkBeforeTransition() {
// Postpone the transition until the window's decor view has
// finished its layout.
getActivity().supportPostponeEnterTransition();
final View decor = getActivity().getWindow().getDecorView();
decor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
decor.getViewTreeObserver().removeOnPreDrawListener(this);
getActivity().supportStartPostponedEnterTransition();
return true;
}
});
}
Got same issue, and it caused by recycler view updating in background, the recycler view will recreate view when notifyItemChanged(int index), so the share view was recycled and it got crash when come back.
My solution is call recyclerView.setItemAnimator(null);, and it will prevent recycler view from recreating view.

Activity not updating properly

I'm new to android, so maybe I'm doing something horribly wrong. I want to have a particular Activity that shows details about an instance of a "Creature" class for a game. Name, damage taken, that sort of thing.
I'm having a problem getting the creature data to be properly shown in the GUI objects. Both at initial creation (where it should copy the creature's name into the name field) and when a damage mark is added (where it doesn't update to show the proper image).
Here's my mini-example of what I have:
public class CreatureDetailActivity2 extends Activity
{
Creature creature;
public void addMark(View v)
{
// connected to the button via android:onClick="addMark" in the XML
creature.getTrack().addDamage(DamageType.Normal, 1);
refreshDisplay();
new AlertDialog.Builder(this).setTitle(creature.getName())
.setMessage(creature.getTrack().toString()).show();
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_creature_detail);
creature = new Creature("Example");
refreshDisplay();
}
public void refreshDisplay()
{
final View creatureDetailView = this.getLayoutInflater().inflate(
R.layout.activity_creature_detail, null);
final EditText nameField = (EditText) (creatureDetailView
.findViewById(R.id.textbox_creature_name));
nameField.setText(creature.getName());
final ImageView damageBox0 = (ImageView) (creatureDetailView.findViewById(R.id.damageBox0));
damageBox0.setImageResource(R.drawable.__n);
// in the full program this does the same for 0 through 9, but this is a sample
// also, in the full program, this is a dynamic lookup for the correct pic
// but again, this is just a sample version.
}
}
Now the problem is that the app will load up and start, but then none of the widgets will update properly. You can click the button, and it'll show the AlertDialog, and the text of the AlertDialog will change, but the textfield in the activity won't be changed, and the ImageView doesn't change at any point from what it starts as to the one it's supposed to change to.
So I'm very stumped. I can post more about the project's setup if I'm leaving out something important, but I'm not even sure what the problem going on is so I'm not sure what else to include in my question.
final View creatureDetailView = this.getLayoutInflater().inflate(
R.layout.activity_creature_detail, null);
Inflates your Activity's layout into basically nothing, just returning the View it inflated. setContentView is what actually inflates your layout into the Activity's View hierarchy.
Once you inflate your layout you don't need to do it again. Just use findViewById without the reference to a dangling unattached View.
Change your refreshDisplay method to this:
public void refreshDisplay()
{
final EditText nameField = (EditText) findViewById(R.id.textbox_creature_name);
nameField.setText(creature.getName());
final ImageView damageBox0 = (ImageView) findViewById(R.id.damageBox0);
damageBox0.setImageResource(R.drawable.__n);
// in the full program this does the same for 0 through 9, but this is a sample
// also, in the full program, this is a dynamic lookup for the correct pic
// but again, this is just a sample version.
}
Nothing changes because You do it completely wrong.
If You wish to update any view element of current activity You do it like this
View v = findViewById(R.id.element);
v.setText("text");
this is just simple example.
You would need to cast a returned element to correct type like to be able to access all available methods.
What You do wrong is trying to inflate a layout again.

Change button's background from inside element on listview, Android

I have a ListView lv, and i have a Button delete at the bottom of the Listview (the button is outside of the listview). There is a problem that how i can deal with the button (such as change background or set text for the button) from inside the listview. I have my own adapter, and i think that i have to handle the button's changing at the public void getView(int pos, View convertView, ViewGroup parent) function. Does anyone have any idea?
P/s: the main point of this question is: "How to deal with other elements from inside a list view?".
Any suggestion is welcome!
In the activity ,
Button delete = (Button) findViewById(R.id.delete);
CustomAdapter adapter = new CustomAdapter(delete);//add your argumente here
Now in the adapter you can set OnClickListener for the button.
Assuming you don't just save a reference to the Button from the Activity or Fragment's onCreate method, then inside getView you would need to implement a loop that walks up the ViewParent chain searching for the actual view you want. It could look something like this:
ViewParent nv = parent.getParent();
while(nv != null){
if(View.class.isInstance(nv)){
View button = findViewById(R.id.button_id);
if(button != null){
// FOUND IT!
// do something, then break;
break;
}
}
nv = nv.getParent();
}
Didn't compile or test that, but...something like that should do what you want. See the ViewParent.getParent() docs for the details.
I am guessing that you are using a custom arrayadapter. If so you could pass a reference to the button to the arrayadapter class.
I am not sure if this will let you edit the button though. If not you could send the ArrayAdapter a handler from the Activity. So in the activity create a handler with something like this:
protected Handler updateButtonHandler = new Handler() {
#Override
public void handleMessage(Message msg){
//Update button
}
};
And then in the arrayadapter have a reference and:
updateButtonHandler.sendEmptyMsg(0);

How to get a layout from activity that is linked with onClick attribute in XML?

I'm a beginner in Android and I'm testing my code using Android JUnit test.
So, I have a test activity that extends ActivityInstrumentationTestCase2<Activity>.
Activity has it's own layout in onCreate() method. (Main layout)
In my XML, I have a onClick attribute for a button that calls foo() method.
Back to Activity, in the foo(View v), I set my content view to a different layout.
I want to test that layout.
How do I get the layout though??
I know for the main layout, I can do this.
Activity act = getActivity();
View mainLayout = (View) act.findViewById(bla.bla.bla.R.id.main_layout);
How do I get the layout that I set in foo(View v)??
I've already tried doing,
fooLayout = (View) act.findViewById(bla.bla.bla.R.id.foo_layout);
and
act.setContentView(bla.bla.bla.R.layout.foo_layout);
fooLayout = (View) act.findViewById(bla.bla.bla.R.id.foo_layout);
I think I got NullPointerException for the first one and android.view.ViewRootImpl$CalledFromWrongThreadException
for the second one.
In your first try you get a NullPointerException because you are searching for your foo_layout wihtin your main_layout. findViewById is used to search for views wihtin a layout and not to find/inflate layouts. In your second try you get a CalledFromWrongThread Exception because you access the UI (setContentView()) from outside the UI thread. This is how you change the layout wihtin your test class:
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
getActivity().setContentView(R.layout.foo_layout);
}
});
getInstrumentation().waitForIdleSync();
// now you can access your views from your foo_layout via getActivity().findViewById(...)
I don't know what you mean by "I want to test my layout". Either you want to check if your layout got successfully loaded through a button click or you want to access the views of the new (loaded) layout. In both cases you can do something like the following:
public void testLayout() {
// get your button that changes the layout of your activity.
// that button is in your main_layout
final Button btChangeLayout = (Button) getActivity().findViewById(R.id.yourButtonThatChangesTheLayout);
// perform a click in order to change the layout
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
btChangeLayout.performClick();
}
});
getInstrumentation().waitForIdleSync();
// get a reference of a view thats in your foo_layout e.g. a Button
Button aButtonInYourFooLayout = (Button) getActivity().findViewById(R.id.aButtonInYourFooLayout);
// now you can do what your want with your button/view.
//if you just want to know wheter your layout has successfully been loaded
//or not you can test your view if it's null
assertNotNull(aButtonInYourFooLayout);
}
i'm not sure ... but i think the first problem is:
act.setContentView(bla.bla.bla.R.id.foo_layout);
change to:
act.setContentView(bla.bla.bla.R.layout.foo_layout);
because in res/layout/ you have your UI, isn't it?

Update ListView from user input after setContentView call

I want to show an empty list view, which is then populated by user input. I have the UI flow working, and I populate a list of my custom objects after the user enters some information via a view which is invoked through setContentView (i.e. no a new Activity).
I take the input and add it to a list, which I want to be summarised on the ListView. However, whenever I add to the list and/or the ArrayAdapter and call adapter.notifyDataSetChanged() it does not do what I want. The ListView is still empty. Argh! It's driving me insane!
#Override
public void onCreate(Bundle blah) {
ListView listView = (ListView) findViewById(R.id.results_list);
listView.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, list));
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.mnu_add:
final Activity act = this;
setContentView(R.layout.record_details);// the sub-view that takes the user input
// the button on the form to 'add' details:-
((Button) findViewById(R.id.recored_details_add_btn))
.setOnClickListener(
new OnClickListener() {
public void onClick(View v) {
// get input from widgets
list.add(someObject);
((ArrayAdapter<Object>) listView.getAdapter()).notifyDataSetChanged();
setContentView(R.layout.list_view);
}
}
);
((ArrayAdapter<Object>) listView.getAdapter()).notifyDataSetChanged();
break;
}
return true;
}
Please, save me from my misery and inform me of my stupidty?
Thanks in advance.
public void onClick(View v) {
// get input from widgets
list.add(someObject);
((ArrayAdapter<Object>) listView.getAdapter()).notifyDataSetChanged();
setContentView(R.layout.list_view);
Is it possible that this setContentView in the onClick handler is creating a new instance of the list view widget (with no adapter) or reinitializing the list view (clearing the adapter)?
Try putting something in the list initially in onCreate and then see if it disappears when you hit the button.
I haven't seen any code (although I'm a relative newbie) that switches views within the activity's lifetime to bring up essentially bring up different pages - most use a separate activity.
Edit:
OP asks:
Thanks...So how can I get what I want? The list I'm backing the adapter with is static; should I just use activities instead and rely on onCreate loading from the static field?
Some options:
Use separate activities
Re-associate the adapter (call setAdapter again) - probably a bad idea
Declare both layouts in the same file. You'll hide one and unhide the other to switch between views rather can calling setContentView. This is similar to how ListView layout works (one for when the list is empty and one for when it is not). I think I've seen an example of this somewhere on the net, but I don't have a reference right now.
You could relaunch the same activity by using Intent.FLAG_ACTIVITY_SINGLE_TOP flag while creating the intent and override the onNewIntent() method.
Inside the onNewIntent() you create the adapter with updated data and call setAdapter.
I think this will give you the intended behaviour.

Categories

Resources