Crash in Activity Transitions with SharedElement - android

I'm using Activity transitions from a ViewPager (in the calling activity) with a shared element and content transitions as well. I'm getting this crash when re-entering to the calling activity:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.os.ResultReceiver.send(int, android.os.Bundle)' on a null object reference
at android.app.EnterTransitionCoordinator.sendSharedElementDestination(EnterTransitionCoordinator.java:199)
at android.app.EnterTransitionCoordinator.viewsReady(EnterTransitionCoordinator.java:123)
at android.app.EnterTransitionCoordinator$2.onPreDraw(EnterTransitionCoordinator.java:148)
at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:895)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2153)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1180)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6558)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:777)
at android.view.Choreographer.doCallbacks(Choreographer.java:590)
at android.view.Choreographer.doFrame(Choreographer.java:560)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:763)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:5832)
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:1399)
Also, Once going back, the screen begins to flicker continuously with a white screen flashing in and out.
Here are my Transition flags:
<item name="android:windowContentTransitions">true</item>
<item name="android:windowActivityTransitions">true</item>
<item name="android:windowAllowReturnTransitionOverlap">false</item>
I tried setting Enter/Exit transitions both on the Calling and Called activity but no luck.

Try to get fragment from FragmentManager:
fragmentManager.findFragmentByTag("android:switcher:" + R.id.viewPager + ":" + position)
If fragment is null, try creating a new fragment.

Activity transitions with shared elements can sometimes result in crashes due to various reasons. Here are a few common causes of crashes and how to avoid them:
Timing issues: Make sure that the shared elements have been properly sized and laid out before the transition begins. Delaying the transition start until after the shared elements have been fully initialized can prevent crashes.
Inconsistent names: If you are using shared element transitions between activities, make sure that the names of the shared elements in both activities are the same. Mismatched names can result in a crash.
Improper use of View.setTransitionName(): When using shared element transitions, it's important to set a unique transition name for each shared element. If two shared elements have the same transition name, a crash can occur.
OutOfMemoryErrors: Large images or Bitmaps used as shared elements can cause OutOfMemoryErrors, resulting in a crash. To avoid this, make sure to resize the images or Bitmaps to a smaller size before using them as shared elements.
Missing transition in XML: If the transition between the two activities is not defined in the XML, a crash can occur. Make sure that the transition is properly defined in the XML and that the correct transition is being used for the shared elements.
Here's an best example of avoid timing issues when using shared elements:
private void startActivityWithSharedElement(Intent intent) {
final ImageView sharedImageView = findViewById(R.id.shared_image_view);
sharedImageView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
sharedImageView.getViewTreeObserver().removeOnPreDrawListener(this);
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(
MainActivity.this, sharedImageView, "shared_image").toBundle());
return true;
}
});
}

Related

How to save the state of views held in dynamic viewpager

I have an enhanced loop, which will dynamically inflate however many layouts relevant to the number of values held in my array.
This works perfectly however, there is a method being called on each iteration, which also works but there is a big bug that I need help resolving.
Imagine there are 5 items in my array, therefore 5 layouts are inflated, in these layouts there is a little scratchcard type section on the layout.
Now if the user is on page 1, uses the scratchcard, then moves on to page 2, uses the scratchcard etc etc, it works fine.
But if the user is on page 1 and then goes to say, page 5 and then back to page 1 (basically in a random order), the scratchcard doesn't work.
From my understanding, the reason for this is that the method is being called an implemented on each iteration and the view is losing its state if the user scrolls back or scrolls in random orders.
Therefore I need a way to save the created view state in my viewpager.
Is this possible for my scenario? I have tried my best to find a solution, but cannot find something that feels relevant to my question.
Here is a snippet of the code in question. Thanks for any guidance or suggestions!
for (String x : array1) {
//loop out the number of layouts relative to the number of questions held in x
View current_layout = LayoutInflater.from(getActivity()).inflate(R.layout.question_fragment, null);
//use the pageAdapter to add the layout to the users view
pagerAdapter.addView(current_layout);
//call method to add functionality to the scratchcard
isCorrect(current_layout);
}
public void isCorrect(View current_layout) {
ScratchoffController controller1 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view1), current_layout.findViewById(R.id.scratch_view_behind1));
ScratchoffController controller2 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view2), current_layout.findViewById(R.id.scratch_view_behind2));
ScratchoffController controller3 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view3), current_layout.findViewById(R.id.scratch_view_behind3));
ScratchoffController controller4 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view4), current_layout.findViewById(R.id.scratch_view_behind4));
}
I ussually use ViewPager with Fragments and what you mention has happend to me when I try to keep references to the Fragment instances (in my case) outside of the viewpager.
This happens because the viewpager may create new instances of the Fragment it contains when you re-vist the tab in the way you mention. When this happens, the instance reference you hold outside of the viewpager is not anymore what the viewpager is showing.
In your case , according to this question, you have to oveeride instatiateItem and destroyItem. I think you can use these methods to save state restore state, and also you could update any external reference when instantiateItem is called.

RecyclerView last item blinks every time an item is removed

I'm using a cursor loader with the RecyclerView, and everything else works just fine except every time I remove an item from the RecyclerView, the last item blinks, like this
last item blinks
my code for deletion is
public void deleteData(long id){
Uri uri = ContentUris.withAppendedId(URI, id);
getContentResolver().delete(uri, null, null);
}
code on swipe:
deleteData(viewHolder.getItemId());
note that I have another activity that's using the same RecyclerAdapter, ContentProvider and layout code, even most of the implementations and method approaches are the same, but that one works perfectly without any blinks, so it is a pretty weird situation for me.
Is there a specific reason that can cause this problem? I already tried disabling the animation like
recyclerView.getItemAnimator().setChangeDuration(0);
or
((DefaultItemAnimator) recyclerViewObject.getItemAnimator()).setSupportsChangeAnimations(false);
But none of that worked, not to mention that I do want the animations to work.
Edit: Break points upload:
onCreate:
"main#4668" prio=5 tid=0x2 nid=NA runnable
java.lang.Thread.State: RUNNABLE at com.jackz314.todo.HistoryActivity.onCreateLoader(HistoryActivity.java:803)
at
android.support.v4.app.LoaderManagerImpl.createLoader(LoaderManager.java:539)
at
android.support.v4.app.LoaderManagerImpl.createAndInstallLoader(LoaderManager.java:548) at
android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:603)
at
com.jackz314.todo.HistoryActivity.displayAllNotes(HistoryActivity.java:394)
at
com.jackz314.todo.HistoryActivity.deleteExpiredNotes(HistoryActivity.java:767)
at
com.jackz314.todo.HistoryActivity.onCreate(HistoryActivity.java:129)
at android.app.Activity.performCreate(Activity.java:6975) at
android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213)
at
android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770)
at
android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(ActivityThread.java:-1) at
android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105) at
android.os.Looper.loop(Looper.java:164) at
android.app.ActivityThread.main(ActivityThread.java:6541) at
java.lang.reflect.Method.invoke(Method.java:-1) at
com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
onDelete(remove item):
"main#4668" prio=5 tid=0x2 nid=NA runnable java.lang.Thread.State:
RUNNABLE at
com.jackz314.todo.HistoryActivity.deleteData(HistoryActivity.java:791)
at
com.jackz314.todo.HistoryActivity$4.onSwiped(HistoryActivity.java:434)
at
android.support.v7.widget.helper.ItemTouchHelper$4.run(ItemTouchHelper.java:686)
at android.os.Handler.handleCallback(Handler.java:789) at
android.os.Handler.dispatchMessage(Handler.java:98) at
android.os.Looper.loop(Looper.java:164) at
android.app.ActivityThread.main(ActivityThread.java:6541) at
java.lang.reflect.Method.invoke(Method.java:-1) at
com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Hope those helps
Edit: After a little investigation, I found out that the last item didn't actually blink, it seems that the RecyclerView first changed it's length/size, cuts off a section that has the equal size as the deleted item(deleting), so that the last item(depending on the size of the deleted item) is not visible for a while, and then the RecyclerView deletes the item that's supposed to be deleted at the beginning. I'm still working on it, trying to figure out whether it's because I messed up the order somewhere or it's some other reason.
Finally got it!
Yet another stupid mistake, it's not complicated at all, I kept thinking, why I'm having this problem only on this activity, but not my previous one, and I suddenly realized that the layout in the two activities are different, turns out I set the RecyclerView's height to wrap_content, so I suppose what happened is that, the layout told the system to wrap the content in the RecyclerView, and when you delete an item, it's technically not there anymore, so, even though I have animations for the deleted item, the item is removed immediately from the layout's level, that's why it seems like Android adjusted the RecyclerView's height first, and then delete the item, what actually happened is, since RecyclerView is set to wrap_content, it removes the position of the view(item) immediately after delete, not after the animation, and that's why it looks like a blink.
Anyways, the solution is just simply to change the RecyclerView's height from wrap_content to any other value, if you are using ConstraintLayout like I did, just change the height to 0dp, then constraint the RecyclerView to something else, or the edge.
Thanks to everyone who tried to help me solve my problem though, I admit this is one of the stupidest yet frustrating problems I ever had.
I believe there're others who also encountered the same problem, so just go and check if the height is set to wrap_content.
I'm stuck with the same thing as you. I believe the problem is that CursorLoader restarts every time we delete something from the database, so onLoadFinished is triggering every time and swapping cursors.
The problem can be "solved" if we load our data from the cursor into the List and pass that to the adapter. Still, i find this as a workaround, not a real solution.
Let place the break point at the data object, which bind with the RecyclerViewAdapter and check if the data is blinked or not. In the most of case like this situation, I realize the data maybe is effected and make view change uncontrollable. Because I don't know how you handle the data, so I can't give more detail answers for you.
Have you tried that, to disable animation?
adapter.setHasStableIds(true);
recyclerView.setItemAnimator(null);
Jackz this code below is in the DetailsActivity that has a btnDelete it fires and goes over to DBHelper and deletes the record and returns to DetailsActivity then makes a call to ListActivity to remove the item from the list based on position NOT record ID
private void addListenerOnButtonDelete() {
btnDelete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Calls the Method deleteDBRow in DatabaseHelper
// which acts on the TABLE_INFO to remove a record by getting the record ID
helper.deleteDBRow(String.valueOf(dbList.get(position).getID()));
ListActivity.removeListRow(position);
// Code line above calls Method in ListActivity to notify recycler view of changes
// NOTICE the List keeps items by position not ID <== READ
etWebSite.setText("");
etUN.setText("");
etPW.setText("");
etSecQuestion.setText("");
etSecAnswer.setText("");
etNotes.setText("");
Intent intent = new Intent(DetailsActivity.this, ListActivity.class);
startActivity(intent);
}
});
}
Here is the code that lives in the ListActivity that talks to the Adapter
// This method is called from DetailsActivity and notifies Recycler View that the DB was changed
// and the method makes the same changes to the Recycler View kind of a sync of DB and Recycler View
public static void removeListRow(int position) {
dbList.remove(position);
mAdapter.notifyItemRemoved(position);
mAdapter.notifyItemRangeChanged(position, dbList.size());
}
I exactly have the same issue and it makes me crazy.
Your solution does not work in my case, probably because my RecyclerViewer is inside a LinearLayout?
The only workaround I found so far is to disable animation like that
cardRecyclerView.itemAnimator = null
But I really want to re-enable animation since without it is a little bit choppy.
Here is my layout, has someone any idea to avoid the last item blinking when a card is removed? thank you in advance
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainLayout"
android:orientation="vertical"
android:layout_width="match_parent"
android:fitsSystemWindows="true"
android:layout_height="wrap_content">
<fragment
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.bendk97.platenumber.activities.main.Toolbar"/>
<fragment
android:id="#+id/search_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.bendk97.platenumber.activities.main.SearchBar"/>
<ProgressBar
style="#style/Widget.AppCompat.ProgressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminateTint="#android:color/white"
android:id="#+id/progressBar" android:visibility="invisible"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v7.widget.RecyclerView>
</LinearLayout>

Error in navigation after creating new Realm object

I'm using the react-native-navigation package as well as Realm in my project. My app has a realm object called Notebook that contains a list of Verse objects, which are another realm object.
The structure of the app is very simple, the first page shows a list of Notebooks and when one is selected, the app transitions to the second screen which is a list of Verses.
Here is my code to navigate from the notebook list to the verse list:
this.props.navigator.push({
screen: 'com.beyersapps.biblebinderrn.verselist',
title: notebook.name,
passProps: {notebook: notebook},
animated: true,
animationType: 'fade',
backButtonTitle: undefined,
backButtonHidden: false
})
This navigation works fine and I can move back and forth between the two screens. My problem comes when I create a new Verse and add it to the Notebook. Here is my code that lives in the second screen to create a new Verse:
realm.write(() => {
let newVerse = realm.create('Verse', {
reference: 'Genesis 1:1',
favorite: false,
memorized: false,
scripture: 'My favorite verse'
});
if (this.notebook != null) {
this.notebook.verses.push(newVerse);
}
});
This is where my problem starts. At this point, if I select the back button to go back to the list of Notebooks, then select a notebook again, I get this error:
Attempting to change value of a readonly property.
Exception in native call
java.lang.RuntimeException: Error calling RCTEventEmitter.receiveTouches
at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:31)
at android.os.Looper.loop(Looper.java:154)
at com.facebook.react.bridge.queue.MessageQueueThreadImpl$3.run(MessageQueueThreadImpl.java:199)
at java.lang.Thread.run(Thread.java:761)
Caused by: com.facebook.jni.CppException: Exception calling object as function: TypeError: Attempting to change value of a readonly property.
at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method) 
at android.os.Handler.handleCallback(Handler.java:751) 
at android.os.Handler.dispatchMessage(Handler.java:95) 
at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:31) 
at android.os.Looper.loop(Looper.java:154) 
at com.facebook.react.bridge.queue.MessageQueueThreadImpl$3.run(MessageQueueThreadImpl.java:199) 
at java.lang.Thread.run(Thread.java:761) 
There are a couple of things I can do to make this problem go away, but both make my app useless. Since they might help in determining the issue though I can either remove {notebook: notebook} from passProps when I navigate to the new screen (but then nothing shows on the Verse list screen since it does not know which Notebook was selected). Or, I can not add the newly created Verse to the selected Notebook (but then I can't add data).
Since these two changes are in two different components (Realm and react-native-navigation), I'm not sure which component is the source of the problem.
Objects passed as props (passProps) are frozen by React Native.
Try changing:
passProps: {notebook: notebook}
to:
passProps: {notebook: _.cloneDeep(notebook)}
If you need to reflect changes to the notebook in the previous screen, I think you should do so through the store.
Another option is to pass notebookId and get the correct notebook by id from state.

Testing ViewPager with multiple fragments using android espresso

I am trying to test my app which uses ViewPager. Each page contains fragments but these fragments are not always visible. I want to check visibility of a fragment in the currently visible page.
onView(withId(R.id.container_weather))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
But the problem is that espresso looks are all the pages not just the current page and I get the following error:
android.support.test.espresso.AmbiguousViewMatcherException: 'with id: eu.airpatrol.android:id/container_weather' matches multiple views in the hierarchy...
I had the same problem, however using the condition isCompletelyDisplayed() solved this problem as it only takes into account the on-screen views.
So, something like this should work:
onView(allOf(withId(R.id.container_weather), isCompletelyDisplayed()))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
Note: isDisplayed() works too in some cases but it also takes views off-screen into account and won't work if the ViewPager has any other page pr fragment loaded with the same view id.
Your tests are failing because of multiple elements with the same id. You can combine conditions using allOf(...). Then use isDisplayed() to check that matched view is visible on the screen. Below example can work:
onView(allOf(
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
withId(R.id.container_weather)))
.check(matches(isDisplayed()));
Ran into this exact same problem. I was fortunate because the view hierarchies in my ViewPager can be easily identified by their siblings, so I was able to solve this using the hasSibling matcher, like so:
onView(
allOf(
hasSibling(withId(R.id.some_sibling)),
withId(R.id.field_to_test)
)
).perform(replaceText("123"));
Not a perfect solution as it can be slightly brittle, but in my case I think it was an acceptable compromise.
I had similar problem, where I was reusing the button layout and it was giving me a matches multiple views in the hierarchy exception.
So the easy work around I did was to create 2 different screens and have 2 different methods with different text.
Withdraw Screen:
public WithdrawScreen clickWithdraw() {
onView(allOf(withId(R.id.save_button), withText("Withdraw")))
.perform(click());
return this;
}
Deposit Screen:
public DepositScreen clickDeposit() {
onView(allOf(withId(R.id.save_button), withText("Deposit")))
.perform(click());
return this;
}
and in my tests, I create a new instance of both screens and call the above methods based on screen reference which is a bit easy to test for.
WithdrawScreen withdrawInstance = new WithdrawScreen();
withdrawInstance.clickWithdraw();
DepositScreen depositInstance = new DepositScreen();
depositInstance.clickDeposit();
The point was they were using same id - R.id.save_button for button and I was replacing text of button based on visibility of the fragment we are on.
Hope it helps.

Android: setImgVisibility() error

In my app I have a header with icon hidden, I have a adapter with a listview when I click the listview I go to a login screen using listener, when the login is successful is should come back to listview(adapter) and icon should get visible on header.
In the login activity I have the following code:
public void onClick(View v) {
String password = etPassword.getText().toString();
if(password.equals("guest")){
SearchAdapter.setImgVisibility();
} else {
//-----
}
finish();
}
In my adapter I am calling the setImgVisibility() as follows, but it is not working
public static void setImgVisibility() {
img.setVisibility(View.VISIBLE);
}
I am getting a Nullpointerexception near the line img.setVisibility(View.VISIBLE);
I am stuck here and don't know what I am doing wrong. Any suggestions or help is appreciated
I would imagine that img is null. You need to look at where this value is set and make sure happens before you call the method setImgVisibility.
Show more of your complete code for people to help further.
Additionally, i've just noticed you've used a static reference to your search adapter, you should be really careful using statics, especially where any referencing of images is concerned as images can be bound to the context, as such unless you nullify the static you will end up with a memory leak. (this used to be an old problem, not sure its still valid, but i would still avoid using a static reference).
Without more code we're not likely to be able to properly help you. For example are you switching activities when logging in? If you are, this won't work at all.
[given the comment below] If you switch activities then your activity containing the list view is going to be destroyed and then rebuilt then you navigate back to it. or it will at least go through the activity lifecycle. This means you can set the icon during the instantiation of the header img.
You could store your logged in state as a property of the Application or a preference. Grab this value when you set the header image and set the image accordingly.
your img object is null. Is your img object is same as View v then you can pass v in setImgVisibility() and then set v.setVisibility(View.VISIBLE)

Categories

Resources